From e43ad858d80660460531477c07a5dd1c0f4d5c67 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Fri, 12 Sep 2025 21:37:24 -0600 Subject: [PATCH 001/721] Add debug methods for visually annotating ranges (#38097) This allows you to write `buffer_snapshot.debug(ranges, value)` and it will be displayed in the buffer (or multibuffer!) until that callsite runs again. `ranges` can be any position (`usize`, `Anchor`, etc), any range, or a slice or vec of those. `value` just needs a `Debug` impl. These are stored in a mutable global for convenience, and this is only available in debug builds. For example, using this to visualize the captures of the brackets Tree-sitter query: image Release Notes: - N/A --- crates/editor/src/element.rs | 57 +++++++ crates/multi_buffer/src/multi_buffer.rs | 111 ++++++++++++++ crates/text/src/text.rs | 189 ++++++++++++++++++++++++ 3 files changed, 357 insertions(+) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a9c24a4b3e67de21a72fbe53b4e19fa5c8974d17..603dcdece22053d131441929fe4270371e803463 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1505,6 +1505,15 @@ impl EditorElement { selections.push((player, layouts)); } }); + + #[cfg(debug_assertions)] + Self::layout_debug_ranges( + &mut selections, + start_anchor..end_anchor, + &snapshot.display_snapshot, + cx, + ); + (selections, active_rows, newest_selection_head) } @@ -7294,6 +7303,54 @@ impl EditorElement { unstaged == unstaged_hollow } + + #[cfg(debug_assertions)] + fn layout_debug_ranges( + selections: &mut Vec<(PlayerColor, Vec)>, + anchor_range: Range, + display_snapshot: &DisplaySnapshot, + cx: &App, + ) { + let theme = cx.theme(); + text::debug::GlobalDebugRanges::with_locked(|debug_ranges| { + if debug_ranges.ranges.is_empty() { + return; + } + let buffer_snapshot = &display_snapshot.buffer_snapshot; + for (buffer, buffer_range, excerpt_id) in + buffer_snapshot.range_to_buffer_ranges(anchor_range) + { + let buffer_range = + buffer.anchor_after(buffer_range.start)..buffer.anchor_before(buffer_range.end); + selections.extend(debug_ranges.ranges.iter().flat_map(|debug_range| { + let player_color = theme + .players() + .color_for_participant(debug_range.occurrence_index as u32 + 1); + debug_range.ranges.iter().filter_map(move |range| { + if range.start.buffer_id != Some(buffer.remote_id()) { + return None; + } + let clipped_start = range.start.max(&buffer_range.start, buffer); + let clipped_end = range.end.min(&buffer_range.end, buffer); + let range = buffer_snapshot.anchor_in_excerpt(excerpt_id, clipped_start)? + ..buffer_snapshot.anchor_in_excerpt(excerpt_id, clipped_end)?; + let start = range.start.to_display_point(display_snapshot); + let end = range.end.to_display_point(display_snapshot); + let selection_layout = SelectionLayout { + head: start, + range: start..end, + cursor_shape: CursorShape::Bar, + is_newest: false, + is_local: false, + active_rows: start.row()..end.row(), + user_name: Some(SharedString::new(debug_range.value.clone())), + }; + Some((player_color, vec![selection_layout])) + }) + })); + } + }); + } } fn header_jump_data( diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 5637f9af1a8ec8d3a064b29c4f8a18a6aff074a4..f9d99473fbd196152418376d1bfbf63e21ad8b00 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -6402,6 +6402,45 @@ impl MultiBufferSnapshot { pub fn diff_for_buffer_id(&self, buffer_id: BufferId) -> Option<&BufferDiffSnapshot> { self.diffs.get(&buffer_id) } + + /// Visually annotates a position or range with the `Debug` representation of a value. The + /// callsite of this function is used as a key - previous annotations will be removed. + #[cfg(debug_assertions)] + #[track_caller] + pub fn debug(&self, ranges: &R, value: V) + where + R: debug::ToMultiBufferDebugRanges, + V: std::fmt::Debug, + { + self.debug_with_key(std::panic::Location::caller(), ranges, value); + } + + /// Visually annotates a position or range with the `Debug` representation of a value. Previous + /// debug annotations with the same key will be removed. The key is also used to determine the + /// annotation's color. + #[cfg(debug_assertions)] + #[track_caller] + pub fn debug_with_key(&self, key: &K, ranges: &R, value: V) + where + K: std::hash::Hash + 'static, + R: debug::ToMultiBufferDebugRanges, + V: std::fmt::Debug, + { + let text_ranges = ranges + .to_multi_buffer_debug_ranges(self) + .into_iter() + .flat_map(|range| { + self.range_to_buffer_ranges(range).into_iter().map( + |(buffer, range, _excerpt_id)| { + buffer.anchor_after(range.start)..buffer.anchor_before(range.end) + }, + ) + }) + .collect(); + text::debug::GlobalDebugRanges::with_locked(|debug_ranges| { + debug_ranges.insert(key, text_ranges, format!("{value:?}").into()) + }); + } } #[cfg(any(test, feature = "test-support"))] @@ -7983,3 +8022,75 @@ impl From for EntityId { EntityId::from(id.0 as u64) } } + +#[cfg(debug_assertions)] +pub mod debug { + use super::*; + + pub trait ToMultiBufferDebugRanges { + fn to_multi_buffer_debug_ranges(&self, snapshot: &MultiBufferSnapshot) + -> Vec>; + } + + impl ToMultiBufferDebugRanges for T { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + [self.to_offset(snapshot)].to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for Range { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + [self.start.to_offset(snapshot)..self.end.to_offset(snapshot)] + .to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for Vec { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.as_slice().to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for Vec> { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.as_slice().to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for [T] { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.iter() + .map(|item| { + let offset = item.to_offset(snapshot); + offset..offset + }) + .collect() + } + } + + impl ToMultiBufferDebugRanges for [Range] { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.iter() + .map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot)) + .collect() + } + } +} diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 8fb6f56222b503360a3d2dd6f4a6b27d1ac728e3..db282e5c30de562441f5076157a8db4a269aea9d 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2593,6 +2593,38 @@ impl BufferSnapshot { last_old_end + new_offset.saturating_sub(last_new_end) }) } + + /// Visually annotates a position or range with the `Debug` representation of a value. The + /// callsite of this function is used as a key - previous annotations will be removed. + #[cfg(debug_assertions)] + #[track_caller] + pub fn debug(&self, ranges: &R, value: V) + where + R: debug::ToDebugRanges, + V: std::fmt::Debug, + { + self.debug_with_key(std::panic::Location::caller(), ranges, value); + } + + /// Visually annotates a position or range with the `Debug` representation of a value. Previous + /// debug annotations with the same key will be removed. The key is also used to determine the + /// annotation's color. + #[cfg(debug_assertions)] + pub fn debug_with_key(&self, key: &K, ranges: &R, value: V) + where + K: std::hash::Hash + 'static, + R: debug::ToDebugRanges, + V: std::fmt::Debug, + { + let ranges = ranges + .to_debug_ranges(self) + .into_iter() + .map(|range| self.anchor_after(range.start)..self.anchor_before(range.end)) + .collect(); + debug::GlobalDebugRanges::with_locked(|debug_ranges| { + debug_ranges.insert(key, ranges, format!("{value:?}").into()); + }); + } } struct RopeBuilder<'a> { @@ -3247,3 +3279,160 @@ impl LineEnding { } } } + +#[cfg(debug_assertions)] +pub mod debug { + use super::*; + use parking_lot::Mutex; + use std::any::TypeId; + use std::hash::{Hash, Hasher}; + + static GLOBAL_DEBUG_RANGES: Mutex> = Mutex::new(None); + + pub struct GlobalDebugRanges { + pub ranges: Vec, + key_to_occurrence_index: HashMap, + next_occurrence_index: usize, + } + + pub struct DebugRange { + key: Key, + pub ranges: Vec>, + pub value: Arc, + pub occurrence_index: usize, + } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + struct Key { + type_id: TypeId, + hash: u64, + } + + impl GlobalDebugRanges { + pub fn with_locked(f: impl FnOnce(&mut Self) -> R) -> R { + let mut state = GLOBAL_DEBUG_RANGES.lock(); + if state.is_none() { + *state = Some(GlobalDebugRanges { + ranges: Vec::new(), + key_to_occurrence_index: HashMap::default(), + next_occurrence_index: 0, + }); + } + if let Some(global_debug_ranges) = state.as_mut() { + f(global_debug_ranges) + } else { + unreachable!() + } + } + + pub fn insert( + &mut self, + key: &K, + ranges: Vec>, + value: Arc, + ) { + let occurrence_index = *self + .key_to_occurrence_index + .entry(Key::new(key)) + .or_insert_with(|| { + let occurrence_index = self.next_occurrence_index; + self.next_occurrence_index += 1; + occurrence_index + }); + let key = Key::new(key); + let existing = self + .ranges + .iter() + .enumerate() + .rfind(|(_, existing)| existing.key == key); + if let Some((existing_ix, _)) = existing { + self.ranges.remove(existing_ix); + } + self.ranges.push(DebugRange { + ranges, + key, + value, + occurrence_index, + }); + } + + pub fn remove(&mut self, key: &K) { + self.remove_impl(&Key::new(key)); + } + + fn remove_impl(&mut self, key: &Key) { + let existing = self + .ranges + .iter() + .enumerate() + .rfind(|(_, existing)| &existing.key == key); + if let Some((existing_ix, _)) = existing { + self.ranges.remove(existing_ix); + } + } + + pub fn remove_all_with_key_type(&mut self) { + self.ranges + .retain(|item| item.key.type_id != TypeId::of::()); + } + } + + impl Key { + fn new(key: &K) -> Self { + let type_id = TypeId::of::(); + let mut hasher = collections::FxHasher::default(); + key.hash(&mut hasher); + Key { + type_id, + hash: hasher.finish(), + } + } + } + + pub trait ToDebugRanges { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec>; + } + + impl ToDebugRanges for T { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + [self.to_offset(snapshot)].to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for Range { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + [self.clone()].to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for Vec { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.as_slice().to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for Vec> { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.as_slice().to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for [T] { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.iter() + .map(|item| { + let offset = item.to_offset(snapshot); + offset..offset + }) + .collect() + } + } + + impl ToDebugRanges for [Range] { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.iter() + .map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot)) + .collect() + } + } +} From d046016ef560b4829c0a4d5bd035cb85f9a66ba4 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 13 Sep 2025 00:13:04 -0400 Subject: [PATCH 002/721] collab: Add Orb cancellation date to `billing_subscriptions` table (#38098) This PR adds an `orb_cancellation_date` column to the `billing_subscriptions` table. Release Notes: - N/A --- ...35238_add_orb_cancellation_date_to_billing_subscriptions.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 crates/collab/migrations/20250913035238_add_orb_cancellation_date_to_billing_subscriptions.sql diff --git a/crates/collab/migrations/20250913035238_add_orb_cancellation_date_to_billing_subscriptions.sql b/crates/collab/migrations/20250913035238_add_orb_cancellation_date_to_billing_subscriptions.sql new file mode 100644 index 0000000000000000000000000000000000000000..56144237421d49fa68545f9689bdb1688603739a --- /dev/null +++ b/crates/collab/migrations/20250913035238_add_orb_cancellation_date_to_billing_subscriptions.sql @@ -0,0 +1,2 @@ +alter table billing_subscriptions + add column orb_cancellation_date timestamp without time zone; From 01f181339f4ea284d21c694ad3f1a3bccd167feb Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Sat, 13 Sep 2025 23:47:13 +0530 Subject: [PATCH 003/721] language_models: Remove unnecessary LM Studio connection refused log (#37277) In zed logs you can see these logs of lmstudio connection refused. Currently zed connects to lmstudio by default as there is no credential mechanism to check if the user has enabled lmstudio previously or not like we do with other providers using api keys. This pr removes the below annoying log and makes the zed logs less polluted. ``` 2025-09-01T02:11:33+05:30 ERROR [language_models] Other(error sending request for url (http://localhost:1234/api/v0/models) Caused by: 0: client error (Connect) 1: tcp connect error: Connection refused (os error 61) 2: Connection refused (os error 61)) ``` Release Notes: - N/A --------- Signed-off-by: Umesh Yadav --- crates/agent2/src/agent.rs | 62 +++++++++++-------- crates/language_model/src/language_model.rs | 2 + .../language_models/src/provider/lmstudio.rs | 25 +++++++- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index d72216208769de98c6ad408fec0c17133090b79b..44d4075b89306b6c6dd81d6de503888c036e6fbf 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -166,33 +166,41 @@ impl LanguageModels { cx.background_spawn(async move { for (provider_id, provider_name, authenticate_task) in authenticate_all_providers { if let Err(err) = authenticate_task.await { - if matches!(err, language_model::AuthenticateError::CredentialsNotFound) { - // Since we're authenticating these providers in the - // background for the purposes of populating the - // language selector, we don't care about providers - // where the credentials are not found. - } else { - // Some providers have noisy failure states that we - // don't want to spam the logs with every time the - // language model selector is initialized. - // - // Ideally these should have more clear failure modes - // that we know are safe to ignore here, like what we do - // with `CredentialsNotFound` above. - match provider_id.0.as_ref() { - "lmstudio" | "ollama" => { - // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated". - // - // These fail noisily, so we don't log them. - } - "copilot_chat" => { - // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors. - } - _ => { - log::error!( - "Failed to authenticate provider: {}: {err}", - provider_name.0 - ); + match err { + language_model::AuthenticateError::CredentialsNotFound => { + // Since we're authenticating these providers in the + // background for the purposes of populating the + // language selector, we don't care about providers + // where the credentials are not found. + } + language_model::AuthenticateError::ConnectionRefused => { + // Not logging connection refused errors as they are mostly from LM Studio's noisy auth failures. + // LM Studio only has one auth method (endpoint call) which fails for users who haven't enabled it. + // TODO: Better manage LM Studio auth logic to avoid these noisy failures. + } + _ => { + // Some providers have noisy failure states that we + // don't want to spam the logs with every time the + // language model selector is initialized. + // + // Ideally these should have more clear failure modes + // that we know are safe to ignore here, like what we do + // with `CredentialsNotFound` above. + match provider_id.0.as_ref() { + "lmstudio" | "ollama" => { + // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated". + // + // These fail noisily, so we don't log them. + } + "copilot_chat" => { + // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors. + } + _ => { + log::error!( + "Failed to authenticate provider: {}: {err}", + provider_name.0 + ); + } } } } diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index fac302104fd9a4da82f5a383d5cd86b64fde4731..fb35a1cae109490620268b11382c3070a6ef6da2 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -681,6 +681,8 @@ pub trait LanguageModelTool: 'static + DeserializeOwned + JsonSchema { /// An error that occurred when trying to authenticate the language model provider. #[derive(Debug, Error)] pub enum AuthenticateError { + #[error("connection refused")] + ConnectionRefused, #[error("credentials not found")] CredentialsNotFound, #[error(transparent)] diff --git a/crates/language_models/src/provider/lmstudio.rs b/crates/language_models/src/provider/lmstudio.rs index 80b28a396b958ab20de3faa0a0f6919c57011e5c..e6e1726ff5b1f0a2edd51d5fde32da9734e56f9d 100644 --- a/crates/language_models/src/provider/lmstudio.rs +++ b/crates/language_models/src/provider/lmstudio.rs @@ -111,7 +111,30 @@ impl State { } let fetch_models_task = self.fetch_models(cx); - cx.spawn(async move |_this, _cx| Ok(fetch_models_task.await?)) + cx.spawn(async move |_this, _cx| { + match fetch_models_task.await { + Ok(()) => Ok(()), + Err(err) => { + // If any cause in the error chain is an std::io::Error with + // ErrorKind::ConnectionRefused, treat this as "credentials not found" + // (i.e. LM Studio not running). + let mut connection_refused = false; + for cause in err.chain() { + if let Some(io_err) = cause.downcast_ref::() { + if io_err.kind() == std::io::ErrorKind::ConnectionRefused { + connection_refused = true; + break; + } + } + } + if connection_refused { + Err(AuthenticateError::ConnectionRefused) + } else { + Err(AuthenticateError::Other(err)) + } + } + } + }) } } From 13113ab31174ca95d318eca0fd917dcb9d863834 Mon Sep 17 00:00:00 2001 From: Ben Gubler Date: Sat, 13 Sep 2025 16:54:00 -0600 Subject: [PATCH 004/721] Add setting to show/hide title bar (#37428) Closes #5120 Release Notes: - Added settings for hiding and showing title bar https://github.com/user-attachments/assets/aaed52d0-6278-4544-8932-c6bab531512a --- assets/settings/default.json | 2 + crates/title_bar/src/title_bar.rs | 49 ++++++++++++++++++++-- crates/title_bar/src/title_bar_settings.rs | 13 ++++++ crates/workspace/src/workspace.rs | 5 +++ docs/src/configuring-zed.md | 1 + docs/src/visual-customization.md | 1 + 6 files changed, 67 insertions(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index b83025116cc4e2d9358b4274e33f598ebe198b7c..4cb779b5a0bacbee3f74126abf89bfeb77aecc49 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -391,6 +391,8 @@ "use_system_window_tabs": false, // Titlebar related settings "title_bar": { + // When to show the title bar: "always" | "never" | "hide_in_full_screen". + "show": "always", // Whether to show the branch icon beside branch switcher in the titlebar. "show_branch_icon": false, // Whether to show the branch name button in the titlebar. diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 4d451dd41f162d5ea41c10b7e1f85f825b9a7385..a6285e1d1d8593c73b2b0c6a79913cb0f16f6e00 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -4,7 +4,7 @@ mod onboarding_banner; pub mod platform_title_bar; mod platforms; mod system_window_tabs; -mod title_bar_settings; +pub mod title_bar_settings; #[cfg(feature = "stories")] mod stories; @@ -35,7 +35,7 @@ use remote::RemoteConnectionOptions; use settings::{Settings, SettingsLocation}; use std::{path::Path, sync::Arc}; use theme::ActiveTheme; -use title_bar_settings::TitleBarSettings; +use title_bar_settings::{TitleBarSettings, TitleBarVisibility}; use ui::{ Avatar, Button, ButtonLike, ButtonStyle, Chip, ContextMenu, Icon, IconName, IconSize, IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, h_flex, prelude::*, @@ -73,8 +73,49 @@ pub fn init(cx: &mut App) { let Some(window) = window else { return; }; - let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); - workspace.set_titlebar_item(item.into(), window, cx); + let should_show = match TitleBarSettings::get_global(cx).show { + TitleBarVisibility::Always => true, + TitleBarVisibility::Never => false, + TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(), + }; + if should_show { + let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); + workspace.set_titlebar_item(item.into(), window, cx); + } + + cx.observe_global_in::(window, |workspace, window, cx| { + let should_show = match TitleBarSettings::get_global(cx).show { + TitleBarVisibility::Always => true, + TitleBarVisibility::Never => false, + TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(), + }; + if should_show { + if workspace.titlebar_item().is_none() { + let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); + workspace.set_titlebar_item(item.into(), window, cx); + } + } else { + workspace.clear_titlebar_item(window, cx); + } + }) + .detach(); + + cx.observe_window_bounds(window, |workspace, window, cx| { + let should_show = match TitleBarSettings::get_global(cx).show { + TitleBarVisibility::Always => true, + TitleBarVisibility::Never => false, + TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(), + }; + if should_show { + if workspace.titlebar_item().is_none() { + let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); + workspace.set_titlebar_item(item.into(), window, cx); + } + } else { + workspace.clear_titlebar_item(window, cx); + } + }) + .detach(); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, action: &OpenApplicationMenu, window, cx| { diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 38e529098bd3e97a11ecefac684c1734302f4261..240c1fd74f0b6f49feef09ce20a81e5f54adfcb1 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -3,8 +3,17 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +#[derive(Copy, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] +#[serde(rename_all = "snake_case")] +pub enum TitleBarVisibility { + Always, + Never, + HideInFullScreen, +} + #[derive(Copy, Clone, Deserialize, Debug)] pub struct TitleBarSettings { + pub show: TitleBarVisibility, pub show_branch_icon: bool, pub show_onboarding_banner: bool, pub show_user_picture: bool, @@ -20,6 +29,10 @@ pub struct TitleBarSettings { #[settings_ui(group = "Title Bar")] #[settings_key(key = "title_bar")] pub struct TitleBarSettingsContent { + /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen". + /// + /// Default: "always" + pub show: Option, /// Whether to show the branch icon beside branch switcher in the title bar. /// /// Default: false diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e6a30e92d977ecf4781f48ce92f5f7483fe810b2..23d611964c3cd1b0284521c9444b6952748a5db9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2043,6 +2043,11 @@ impl Workspace { cx.notify(); } + pub fn clear_titlebar_item(&mut self, _: &mut Window, cx: &mut Context) { + self.titlebar_item = None; + cx.notify(); + } + pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) { self.on_prompt_for_new_path = Some(prompt) } diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 5c12b6aaa470c1f23c82367d4c5c8c7e6247e789..4a95bb50437f5b37a30c2658b41d28fc0f3a1708 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -4106,6 +4106,7 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a ```json "title_bar": { + "show": "always", "show_branch_icon": false, "show_branch_name": true, "show_project_items": true, diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index 27802888c90b29d7998d47c1668c4015d307476d..150b701168f49980844ea37c223efe00b6dc06cc 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -108,6 +108,7 @@ To disable this behavior use: ```json // Control which items are shown/hidden in the title bar "title_bar": { + "show": "always", // When to show: always | never | hide_in_full_screen "show_branch_icon": false, // Show/hide branch icon beside branch switcher "show_branch_name": true, // Show/hide branch name "show_project_items": true, // Show/hide project host and name From c50b561e1c826e707e0d89bd7d82373c27f2fe32 Mon Sep 17 00:00:00 2001 From: Casper van Elteren Date: Sun, 14 Sep 2025 01:17:44 +0200 Subject: [PATCH 005/721] Expose REPL Settings (#37927) Closes #37829 This PR introduces and exposes `REPLSettings` to control the number of lines and columns in the REPL. These settings are integrated into the existing configuration system, allowing for customization and management through the standard settings interface. #### Changes - Added `REPLSettings` struct with `max_number_of_lines` and `max_number_of_columns` fields. - Integrated `REPLSettings` with the settings system by implementing the `Settings` trait. - Ensured compatibility with the workspace and existing settings infrastructure. Release Notes: - Add configuration "repl" to settings to configure max lines and columns for repl. --------- Co-authored-by: Marshall Bowers Co-authored-by: Kirill Bulatov --- Cargo.lock | 1 + assets/settings/default.json | 9 ++++++ crates/repl/Cargo.toml | 1 + crates/repl/src/outputs/plain.rs | 8 ++--- crates/repl/src/repl.rs | 3 ++ crates/repl/src/repl_settings.rs | 55 ++++++++++++++++++++++++++++++++ docs/src/configuring-zed.md | 17 ++++++++++ 7 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 crates/repl/src/repl_settings.rs diff --git a/Cargo.lock b/Cargo.lock index 8252dc5fe867fc0668ea94b41cee502b47e3aa84..c3002f449719306f3714754b1fd94811b073b150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13623,6 +13623,7 @@ dependencies = [ "runtimelib", "schemars", "serde", + "serde_derive", "serde_json", "settings", "smol", diff --git a/assets/settings/default.json b/assets/settings/default.json index 4cb779b5a0bacbee3f74126abf89bfeb77aecc49..68aef5cccc92d9cc4133ae8db65bfeb9d4e39923 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1844,6 +1844,15 @@ // "typescript": "deno" // } }, + // Repl settings. + "repl": { + // Maximum number of columns to keep in REPL's scrollback buffer. + // Clamped with [20, 512] range. + "max_columns": 128, + // Maximum number of lines to keep in REPL's scrollback buffer. + // Clamped with [4, 256] range. + "max_lines": 32 + }, // Vim settings "vim": { "default_mode": "normal", diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index 5821bc6297266cffba19234d82efec3f6190c310..dea04243b2ed8f6c0611f17077a00e0c02c13693 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -40,6 +40,7 @@ project.workspace = true runtimelib.workspace = true schemars.workspace = true serde.workspace = true +serde_derive.workspace = true serde_json.workspace = true settings.workspace = true smol.workspace = true diff --git a/crates/repl/src/outputs/plain.rs b/crates/repl/src/outputs/plain.rs index 268224fda35d6db78135f0b0154966159570dc4c..844bf6ed132fbe004b1aa22ddffb78ba39911855 100644 --- a/crates/repl/src/outputs/plain.rs +++ b/crates/repl/src/outputs/plain.rs @@ -31,6 +31,7 @@ use theme::ThemeSettings; use ui::{IntoElement, prelude::*}; use crate::outputs::OutputContent; +use crate::repl_settings::ReplSettings; /// The `TerminalOutput` struct handles the parsing and rendering of text input, /// simulating a basic terminal environment within REPL output. @@ -53,9 +54,6 @@ pub struct TerminalOutput { handler: alacritty_terminal::Term, } -const DEFAULT_NUM_LINES: usize = 32; -const DEFAULT_NUM_COLUMNS: usize = 128; - /// Returns the default text style for the terminal output. pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle { let settings = ThemeSettings::get_global(cx).clone(); @@ -99,8 +97,8 @@ pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalBou .unwrap() .width; - let num_lines = DEFAULT_NUM_LINES; - let columns = DEFAULT_NUM_COLUMNS; + let num_lines = ReplSettings::get_global(cx).max_lines; + let columns = ReplSettings::get_global(cx).max_columns; // Reversed math from terminal::TerminalSize to get pixel width according to terminal width let width = columns as f32 * cell_width; diff --git a/crates/repl/src/repl.rs b/crates/repl/src/repl.rs index 7c17b5fac2a3aa9107a44543416f6210dcaa80c7..f6005f1ed73ac07697af48297643b30be113c83c 100644 --- a/crates/repl/src/repl.rs +++ b/crates/repl/src/repl.rs @@ -5,6 +5,7 @@ pub mod notebook; mod outputs; mod repl_editor; mod repl_sessions_ui; +mod repl_settings; mod repl_store; mod session; @@ -22,6 +23,7 @@ pub use crate::repl_editor::*; pub use crate::repl_sessions_ui::{ ClearOutputs, Interrupt, ReplSessionsPage, Restart, Run, Sessions, Shutdown, }; +pub use crate::repl_settings::ReplSettings; use crate::repl_store::ReplStore; pub use crate::session::Session; @@ -31,6 +33,7 @@ pub fn init(fs: Arc, cx: &mut App) { set_dispatcher(zed_dispatcher(cx)); JupyterSettings::register(cx); ::editor::init_settings(cx); + ReplSettings::register(cx); repl_sessions_ui::init(cx); ReplStore::init(fs, cx); } diff --git a/crates/repl/src/repl_settings.rs b/crates/repl/src/repl_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..541455c7fc3adc16cbb1e48fb811a2ac3a30bbbc --- /dev/null +++ b/crates/repl/src/repl_settings.rs @@ -0,0 +1,55 @@ +use gpui::App; +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; + +/// Settings for configuring REPL display and behavior. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] +#[settings_key(key = "repl")] +pub struct ReplSettings { + /// Maximum number of lines to keep in REPL's scrollback buffer. + /// Clamped with [4, 256] range. + /// + /// Default: 32 + #[serde(default = "default_max_lines")] + pub max_lines: usize, + /// Maximum number of columns to keep in REPL's scrollback buffer. + /// Clamped with [20, 512] range. + /// + /// Default: 128 + #[serde(default = "default_max_columns")] + pub max_columns: usize, +} + +impl Settings for ReplSettings { + type FileContent = Self; + + fn load(sources: SettingsSources, _cx: &mut App) -> anyhow::Result { + let mut settings: ReplSettings = sources.json_merge()?; + settings.max_columns = settings.max_columns.clamp(20, 512); + settings.max_lines = settings.max_lines.clamp(4, 256); + Ok(settings) + } + + fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} +} + +const DEFAULT_NUM_LINES: usize = 32; +const DEFAULT_NUM_COLUMNS: usize = 128; + +fn default_max_lines() -> usize { + DEFAULT_NUM_LINES +} + +fn default_max_columns() -> usize { + DEFAULT_NUM_COLUMNS +} + +impl Default for ReplSettings { + fn default() -> Self { + ReplSettings { + max_lines: DEFAULT_NUM_LINES, + max_columns: DEFAULT_NUM_COLUMNS, + } + } +} diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 4a95bb50437f5b37a30c2658b41d28fc0f3a1708..a7b89dc5207e0acea422401b0ce77946c7d484c6 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -4026,6 +4026,23 @@ Example command to set the title: `echo -e "\e]2;New Title\007";` } ``` +## REPL + +- Description: Repl settings. +- Setting: `repl` +- Default: + +```json +"repl": { + // Maximum number of columns to keep in REPL's scrollback buffer. + // Clamped with [20, 512] range. + "max_columns": 128, + // Maximum number of lines to keep in REPL's scrollback buffer. + // Clamped with [4, 256] range. + "max_lines": 32 +}, +``` + ## Theme - Description: The theme setting can be specified in two forms - either as the name of a theme or as an object containing the `mode`, `dark`, and `light` themes for the Zed UI. From 89e527c23b8ea1f9b233f64b92c9ea5a689218ca Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 13 Sep 2025 22:39:34 -0400 Subject: [PATCH 006/721] Fix typo in default settings (#38123) This PR fixes a typo in the default settings file. Release Notes: - N/A --- assets/settings/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 68aef5cccc92d9cc4133ae8db65bfeb9d4e39923..5dcb418184f592e941d23763f7fe39898413cb79 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1844,7 +1844,7 @@ // "typescript": "deno" // } }, - // Repl settings. + // REPL settings. "repl": { // Maximum number of columns to keep in REPL's scrollback buffer. // Clamped with [20, 512] range. From e40a950bc4c0cd3e29f1571920b29005618d2c6c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 13 Sep 2025 22:42:39 -0400 Subject: [PATCH 007/721] collab: Add `orb_portal_url` column to `billing_customers` table (#38124) This PR adds an `orb_portal_url` column to the `billing_customers` table. Release Notes: - N/A --- .../20250914022147_add_orb_portal_url_to_billing_customers.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 crates/collab/migrations/20250914022147_add_orb_portal_url_to_billing_customers.sql diff --git a/crates/collab/migrations/20250914022147_add_orb_portal_url_to_billing_customers.sql b/crates/collab/migrations/20250914022147_add_orb_portal_url_to_billing_customers.sql new file mode 100644 index 0000000000000000000000000000000000000000..2de05740410f5d13f7cd510a85af24dd2ff171b6 --- /dev/null +++ b/crates/collab/migrations/20250914022147_add_orb_portal_url_to_billing_customers.sql @@ -0,0 +1,2 @@ +alter table billing_customers + add column orb_portal_url text; From 813a9bb0bcb40a24a9fc44c48fdba0cc6ab38308 Mon Sep 17 00:00:00 2001 From: Romans Malinovskis Date: Sun, 14 Sep 2025 09:32:12 +0100 Subject: [PATCH 008/721] Fix select in Helix mode (#38117) Hotfixes issue I have introduced in #37748. Without this, helix mode select not working at all in `main` branch. Release Notes: - N/A --- assets/keymaps/vim.json | 2 +- crates/vim/src/helix.rs | 74 ++++++++++++++++++++++++++++++++++++++++ crates/vim/src/motion.rs | 5 ++- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index ed79b00c16a7586f4a121c3fbd6e71f883dbb2d4..8464e03d251afc166ac45a349894ecf2f7247944 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -328,7 +328,7 @@ { "context": "vim_mode == helix_select", "bindings": { - "escape": "vim::NormalBefore", + "v": "vim::NormalBefore", ";": "vim::HelixCollapseSelection", "~": "vim::ChangeCase", "ctrl-a": "vim::Increment", diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 9f5580d75cc4f3588e4211d1ef2bbfb7df49c92f..369ec2df11e2d5cf99a0b3d7a72ccd5619161f2f 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -53,6 +53,35 @@ impl Vim { self.helix_move_cursor(motion, times, window, cx); } + pub fn helix_select_motion( + &mut self, + motion: Motion, + times: Option, + window: &mut Window, + cx: &mut Context, + ) { + self.update_editor(cx, |_, editor, cx| { + let text_layout_details = editor.text_layout_details(window); + editor.change_selections(Default::default(), window, cx, |s| { + s.move_with(|map, selection| { + let current_head = selection.head(); + + let Some((new_head, goal)) = motion.move_point( + map, + current_head, + selection.goal, + times, + &text_layout_details, + ) else { + return; + }; + + selection.set_head(new_head, goal); + }) + }); + }); + } + /// Updates all selections based on where the cursors are. fn helix_new_selections( &mut self, @@ -1033,4 +1062,49 @@ mod test { Mode::HelixNormal, ); } + + #[gpui::test] + async fn test_helix_select_mode_motion(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + assert_eq!(cx.mode(), Mode::Normal); + cx.enable_helix(); + + cx.set_state("ˇhello", Mode::HelixNormal); + cx.simulate_keystrokes("l v l l"); + cx.assert_state("h«ellˇ»o", Mode::HelixSelect); + } + + #[gpui::test] + async fn test_helix_select_mode_motion_multiple_cursors(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + assert_eq!(cx.mode(), Mode::Normal); + cx.enable_helix(); + + // Start with multiple cursors (no selections) + cx.set_state("ˇhello\nˇworld", Mode::HelixNormal); + + // Enter select mode and move right twice + cx.simulate_keystrokes("v l l"); + + // Each cursor should independently create and extend its own selection + cx.assert_state("«helˇ»lo\n«worˇ»ld", Mode::HelixSelect); + } + + #[gpui::test] + async fn test_helix_select_word_motions(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state("ˇone two", Mode::Normal); + cx.simulate_keystrokes("v w"); + cx.assert_state("«one tˇ»wo", Mode::Visual); + + // In Vim, this selects "t". In helix selections stops just before "t" + + cx.enable_helix(); + cx.set_state("ˇone two", Mode::HelixNormal); + cx.simulate_keystrokes("v w"); + cx.assert_state("«one ˇ»two", Mode::HelixSelect); + } } diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index e2dfb24008c2f23044ca1f3633dcc02930b41e54..829e8bf1117fea3dc5d6c98b9c59effbf9c3f4c1 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -726,9 +726,8 @@ impl Vim { self.visual_motion(motion, count, window, cx) } - Mode::HelixNormal | Mode::HelixSelect => { - self.helix_normal_motion(motion, count, window, cx) - } + Mode::HelixNormal => self.helix_normal_motion(motion, count, window, cx), + Mode::HelixSelect => self.helix_select_motion(motion, count, window, cx), } self.clear_operator(window, cx); if let Some(operator) = waiting_operator { From 2b1f7d5763438cba951388e942e77afc0e917e06 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Sun, 14 Sep 2025 04:58:51 -0700 Subject: [PATCH 009/721] project_panel: Fix primary and secondary click on blank area (#38139) Follow up https://github.com/zed-industries/zed/pull/38008 Release Notes: - N/A --- crates/project_panel/src/project_panel.rs | 44 +++++++++++++---------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 70ceb639acb5d76b35c852654d31093839006e23..b53dfe6fdf8bdf81761299c075eee302a06a8956 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5203,14 +5203,6 @@ impl Render for ProjectPanel { this.refresh_drag_cursor_style(&event.modifiers, window, cx); }, )) - .on_click(cx.listener(|this, event, _, cx| { - if matches!(event, gpui::ClickEvent::Keyboard(_)) { - return; - } - cx.stop_propagation(); - this.selection = None; - this.marked_entries.clear(); - })) .key_context(self.dispatch_context(window, cx)) .on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_previous)) @@ -5280,16 +5272,6 @@ impl Render for ProjectPanel { .when(project.is_via_remote_server(), |el| { el.on_action(cx.listener(Self::open_in_terminal)) }) - .on_mouse_down( - MouseButton::Right, - cx.listener(move |this, event: &MouseDownEvent, window, cx| { - // When deploying the context menu anywhere below the last project entry, - // act as if the user clicked the root of the last worktree. - if let Some(entry_id) = this.last_worktree_root_id { - this.deploy_context_menu(event.position, entry_id, window, cx); - } - }), - ) .track_focus(&self.focus_handle(cx)) .child( v_flex() @@ -5507,6 +5489,7 @@ impl Render for ProjectPanel { ) .child( div() + .id("project-panel-blank-area") .block_mouse_except_scroll() .flex_grow() .when( @@ -5588,7 +5571,30 @@ impl Render for ProjectPanel { } cx.stop_propagation(); }, - )), + )) + .on_click(cx.listener(|this, event, _, cx| { + if matches!(event, gpui::ClickEvent::Keyboard(_)) { + return; + } + cx.stop_propagation(); + this.selection = None; + this.marked_entries.clear(); + })) + .on_mouse_down( + MouseButton::Right, + cx.listener(move |this, event: &MouseDownEvent, window, cx| { + // When deploying the context menu anywhere below the last project entry, + // act as if the user clicked the root of the last worktree. + if let Some(entry_id) = this.last_worktree_root_id { + this.deploy_context_menu( + event.position, + entry_id, + window, + cx, + ); + } + }), + ), ) .size_full(), ) From 37239fd66bfdb104bdd8c4c81c797ed65f6cd13d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:01:04 +0200 Subject: [PATCH 010/721] Use serde 1.0.221 instead of serde_derive hackery (#38137) serde 1.0.221 introduced serde_core into the build graph, which should render explicitly depending on serde_derive for faster build times an obsolote method. Besides, I'm not even sure if that worked for us. My hunch is that at least one of our deps would have `serde` with derive feature enabled.. and then, most of the crates using `serde_derive` explicitly were also depending on gpui, which depended on `serde`.. thus, we wouldn't have gained anything from explicit dep on `serde_derive` Release Notes: - N/A --- Cargo.lock | 38 +++++++++---------- Cargo.toml | 5 +-- crates/auto_update_ui/src/auto_update_ui.rs | 4 ++ crates/call/Cargo.toml | 1 - crates/call/src/call_settings.rs | 2 +- crates/collab/Cargo.toml | 1 - .../src/tests/random_channel_buffer_tests.rs | 2 +- crates/collab/src/user_backfiller.rs | 4 ++ crates/collab_ui/Cargo.toml | 1 - crates/collab_ui/src/collab_panel.rs | 2 +- crates/context_server/src/client.rs | 4 ++ crates/eval/src/examples/mod.rs | 4 ++ crates/file_finder/Cargo.toml | 1 - .../file_finder/src/file_finder_settings.rs | 2 +- .../src/providers/codeberg.rs | 28 ++++++++++++++ .../src/providers/github.rs | 16 ++++++++ crates/git_ui/Cargo.toml | 1 - crates/git_ui/src/git_panel_settings.rs | 2 +- crates/gpui/Cargo.toml | 1 - crates/gpui/src/gpui.rs | 1 - crates/gpui/src/text_system/font_fallbacks.rs | 2 +- crates/gpui/tests/action_macros.rs | 4 +- crates/project_panel/Cargo.toml | 1 - .../src/project_panel_settings.rs | 2 +- crates/repl/Cargo.toml | 1 - crates/repl/src/repl_settings.rs | 2 +- crates/settings/Cargo.toml | 1 - crates/settings/src/settings_store.rs | 4 +- crates/supermaven_api/src/supermaven_api.rs | 8 ++++ crates/terminal/Cargo.toml | 1 - crates/terminal/src/terminal_settings.rs | 2 +- crates/theme/Cargo.toml | 1 - crates/theme/src/styles/accents.rs | 2 +- crates/theme/src/styles/players.rs | 2 +- crates/theme_importer/src/main.rs | 16 ++++++++ crates/theme_importer/src/vscode/theme.rs | 20 ++++++++++ crates/vim/Cargo.toml | 1 - crates/vim/src/normal/search.rs | 2 +- crates/vim/src/vim.rs | 2 +- tooling/workspace-hack/Cargo.toml | 3 +- 40 files changed, 144 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3002f449719306f3714754b1fd94811b073b150..65e57c3c9bc922edd5d3658b590e5627a63e1d9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2646,7 +2646,6 @@ dependencies = [ "project", "schemars", "serde", - "serde_derive", "settings", "telemetry", "util", @@ -3357,7 +3356,6 @@ dependencies = [ "semantic_version", "semver", "serde", - "serde_derive", "serde_json", "session", "settings", @@ -3413,7 +3411,6 @@ dependencies = [ "rpc", "schemars", "serde", - "serde_derive", "serde_json", "settings", "smallvec", @@ -5783,7 +5780,6 @@ dependencies = [ "schemars", "search", "serde", - "serde_derive", "serde_json", "settings", "text", @@ -6503,7 +6499,6 @@ dependencies = [ "project", "schemars", "serde", - "serde_derive", "serde_json", "settings", "strum 0.27.1", @@ -7474,7 +7469,6 @@ dependencies = [ "seahash", "semantic_version", "serde", - "serde_derive", "serde_json", "slotmap", "smallvec", @@ -12706,7 +12700,6 @@ dependencies = [ "schemars", "search", "serde", - "serde_derive", "serde_json", "settings", "smallvec", @@ -13623,7 +13616,6 @@ dependencies = [ "runtimelib", "schemars", "serde", - "serde_derive", "serde_json", "settings", "smol", @@ -14730,18 +14722,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.221" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "341877e04a22458705eb4e131a1508483c877dca2792b3781d4e5d8a6019ec43" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.221" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "0c459bc0a14c840cb403fc14b148620de1e0778c96ecd6e0c8c3cacb6d8d00fe" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.221" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d6185cf75117e20e62b1ff867b9518577271e58abe0037c40bb4794969355ab0" dependencies = [ "proc-macro2", "quote", @@ -14770,15 +14772,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "56177480b00303e689183f110b4e727bb4211d692c62d4fcd16d02be93077d40" dependencies = [ "indexmap", "itoa", "memchr", "ryu", - "serde", + "serde_core", ] [[package]] @@ -14878,7 +14880,6 @@ dependencies = [ "rust-embed", "schemars", "serde", - "serde_derive", "serde_json", "serde_json_lenient", "serde_path_to_error", @@ -16465,7 +16466,6 @@ dependencies = [ "release_channel", "schemars", "serde", - "serde_derive", "settings", "smol", "sysinfo", @@ -16568,7 +16568,6 @@ dependencies = [ "refineable", "schemars", "serde", - "serde_derive", "serde_json", "serde_json_lenient", "serde_repr", @@ -18068,7 +18067,6 @@ dependencies = [ "schemars", "search", "serde", - "serde_derive", "serde_json", "settings", "task", @@ -20098,7 +20096,7 @@ dependencies = [ "security-framework-sys", "semver", "serde", - "serde_derive", + "serde_core", "serde_json", "sha1", "simd-adler32", diff --git a/Cargo.toml b/Cargo.toml index 042bd8cf421b737130344242c2a58ad4cfb2a7f2..de1d216561b9d3074eecff5c36d6405a29c4f7d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -617,9 +617,8 @@ rustls-platform-verifier = "0.5.0" scap = { git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7", default-features = false } schemars = { version = "1.0", features = ["indexmap2"] } semver = "1.0" -serde = { version = "1.0", features = ["derive", "rc"] } -serde_derive = { version = "1.0", features = ["deserialize_in_place"] } -serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } +serde = { version = "1.0.221", features = ["derive", "rc"] } +serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] } serde_json_lenient = { version = "0.2", features = [ "preserve_order", "raw_value", diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index efac14968ea48d93ae35089d239916de1f0a5253..aeaa6ae93e635a6cab1487400fb58bd7be1bc6e1 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -31,6 +31,10 @@ pub fn init(cx: &mut App) { #[derive(Deserialize)] struct ReleaseNotesBody { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] title: String, release_notes: String, } diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index ad3d569d61482ad71ee98e636db8c20274d56820..9dfb4acbb424724ce976f918f3bf5124a450e372 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -37,7 +37,6 @@ postage.workspace = true project.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true settings.workspace = true telemetry.workspace = true util.workspace = true diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index b0677e3c3bcb5112fdd9ad2abc4bf188b225aeac..c458c17e9fcd8788aae2661fbaaa0d45b117e10f 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -1,7 +1,7 @@ use anyhow::Result; use gpui::App; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; #[derive(Deserialize, Debug)] diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 4fccd3be7ff8b4d44daf5f761695bdba81bd2ad8..2d431cdf8ce3046d684dc6022dc29682416c0c9a 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -52,7 +52,6 @@ sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-postgres", "postgres-array semantic_version.workspace = true semver.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true sha2.workspace = true sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] } diff --git a/crates/collab/src/tests/random_channel_buffer_tests.rs b/crates/collab/src/tests/random_channel_buffer_tests.rs index 9451090af2198117ddb20241b99be5b208daa729..cd4a575bb2f2bd17d9abd747132416c0be16c6a6 100644 --- a/crates/collab/src/tests/random_channel_buffer_tests.rs +++ b/crates/collab/src/tests/random_channel_buffer_tests.rs @@ -5,7 +5,7 @@ use anyhow::Result; use async_trait::async_trait; use gpui::{BackgroundExecutor, SharedString, TestAppContext}; use rand::prelude::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::{ ops::{Deref, DerefMut, Range}, rc::Rc, diff --git a/crates/collab/src/user_backfiller.rs b/crates/collab/src/user_backfiller.rs index 569a298c9cd5bca6c65e5b7a39b45a784635ad35..fdb9ef67c2f1d04bf0a1919045f91d75a14ef834 100644 --- a/crates/collab/src/user_backfiller.rs +++ b/crates/collab/src/user_backfiller.rs @@ -157,5 +157,9 @@ impl UserBackfiller { struct GithubUser { id: i32, created_at: DateTime, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] name: Option, } diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 34e40d767ea5a9cab115b4186a642ee234337845..b3b0f1b0e37207582293fcc6d8930cc004ad7405 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -49,7 +49,6 @@ release_channel.workspace = true rpc.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings.workspace = true smallvec.workspace = true diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 82e0f84105b57baa47999db9e086542a4f99adf7..1698fcb89376787bef52c0ee69b5d799976eda3b 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -24,7 +24,7 @@ use rpc::{ ErrorCode, ErrorExt, proto::{self, ChannelVisibility, PeerId}, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::Settings; use smallvec::SmallVec; use std::{mem, sync::Arc}; diff --git a/crates/context_server/src/client.rs b/crates/context_server/src/client.rs index b3b44dbde67d92ce620d85a39a0925f27a4e2086..7dc174bfaab4770ff90623f048223fc2177acde9 100644 --- a/crates/context_server/src/client.rs +++ b/crates/context_server/src/client.rs @@ -127,6 +127,10 @@ struct Notification<'a, T> { #[derive(Debug, Clone, Deserialize)] struct AnyNotification<'a> { + #[expect( + unused, + reason = "Part of the JSON-RPC protocol - we expect the field to be present in a valid JSON-RPC notification" + )] jsonrpc: &'a str, method: String, #[serde(default)] diff --git a/crates/eval/src/examples/mod.rs b/crates/eval/src/examples/mod.rs index d74fbdb937bb3fce089ac1e0dd1b4abb75657ca3..6d47513e373f44b011743c398556a709cf71348e 100644 --- a/crates/eval/src/examples/mod.rs +++ b/crates/eval/src/examples/mod.rs @@ -115,6 +115,10 @@ pub struct ExampleToml { pub url: String, pub revision: String, pub language_extension: Option, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub insert_id: Option, #[serde(default = "default_true")] pub require_lsp: bool, diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index aabfa4362afcf644e5d7e882ef9f9c1b97d261cb..edb7031f939f117ae5d5ec126a1edec58cb157c3 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -27,7 +27,6 @@ schemars.workspace = true search.workspace = true settings.workspace = true serde.workspace = true -serde_derive.workspace = true text.workspace = true theme.workspace = true ui.workspace = true diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 6a6b98b8ea3e1c7e7f0e3cc0385fdd7f413b659f..05f77ee55e79efb25a822d0aab4e051deeeafce7 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -1,6 +1,6 @@ use anyhow::Result; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] diff --git a/crates/git_hosting_providers/src/providers/codeberg.rs b/crates/git_hosting_providers/src/providers/codeberg.rs index b9f2542d5b00d32b476e20e4925b7805c886d636..2cba745f4970de5bb22bdc84f7a76a28998c385d 100644 --- a/crates/git_hosting_providers/src/providers/codeberg.rs +++ b/crates/git_hosting_providers/src/providers/codeberg.rs @@ -16,25 +16,53 @@ use git::{ #[derive(Debug, Deserialize)] struct CommitDetails { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] commit: Commit, author: Option, } #[derive(Debug, Deserialize)] struct Commit { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] author: Author, } #[derive(Debug, Deserialize)] struct Author { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] name: String, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] email: String, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] date: String, } #[derive(Debug, Deserialize)] struct User { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub login: String, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub id: u64, pub avatar_url: String, } diff --git a/crates/git_hosting_providers/src/providers/github.rs b/crates/git_hosting_providers/src/providers/github.rs index 4475afeb495f41e89273ce0336d830db4cc869cf..28c8f7973b87d784de3497345c6db6ecb11eca1c 100644 --- a/crates/git_hosting_providers/src/providers/github.rs +++ b/crates/git_hosting_providers/src/providers/github.rs @@ -25,22 +25,38 @@ fn pull_request_number_regex() -> &'static Regex { #[derive(Debug, Deserialize)] struct CommitDetails { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] commit: Commit, author: Option, } #[derive(Debug, Deserialize)] struct Commit { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] author: Author, } #[derive(Debug, Deserialize)] struct Author { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] email: String, } #[derive(Debug, Deserialize)] struct User { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub id: u64, pub avatar_url: String, } diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index 35f7a603544ae72134a2c6c1b08dcb8a0119b79b..52577d033a94fb819b320d3b18fd384431fe5a1f 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -48,7 +48,6 @@ postage.workspace = true project.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings.workspace = true strum.workspace = true diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index 74e8e25927f07ec1f159d12023669fab9e518114..be26061b0d2cbeee9a0a75f52daf56fb9fcb94f4 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -1,7 +1,7 @@ use editor::EditorSettings; use gpui::Pixels; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; use workspace::dock::DockPosition; diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index a6e8d090f0bd1f4eac7218ae1adff5d5f65b05b5..44f819c135298dc991ad6036ad9948b5eaf609a4 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -114,7 +114,6 @@ schemars.workspace = true seahash = "4.1" semantic_version.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true slotmap = "1.0.6" smallvec.workspace = true diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 7cc56265bd0401eac67305b636ba29ccec6df9ea..eda10a41abc4722f41151c88b531251020bf0326 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -112,7 +112,6 @@ pub mod private { pub use inventory; pub use schemars; pub use serde; - pub use serde_derive; pub use serde_json; } diff --git a/crates/gpui/src/text_system/font_fallbacks.rs b/crates/gpui/src/text_system/font_fallbacks.rs index 2be17e0021e42a809547188861ee00641535b110..63dc89ba41ea9cf44bdffb7d81d3015d696f4413 100644 --- a/crates/gpui/src/text_system/font_fallbacks.rs +++ b/crates/gpui/src/text_system/font_fallbacks.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; /// The fallback fonts that can be configured for a given font. /// Fallback fonts family names are stored here. diff --git a/crates/gpui/tests/action_macros.rs b/crates/gpui/tests/action_macros.rs index 7bff3a97b1c3d22e6c0e9841a26f2adf5d7f3a70..66ef6fba2c9b980e27150bb3bf8d9d07f35ab030 100644 --- a/crates/gpui/tests/action_macros.rs +++ b/crates/gpui/tests/action_macros.rs @@ -1,7 +1,7 @@ use gpui::{Action, actions}; use gpui_macros::register_action; use schemars::JsonSchema; -use serde_derive::Deserialize; +use serde::Deserialize; #[test] fn test_action_macros() { @@ -19,7 +19,7 @@ fn test_action_macros() { #[serde(deny_unknown_fields)] struct AnotherAction; - #[derive(PartialEq, Clone, gpui::private::serde_derive::Deserialize)] + #[derive(PartialEq, Clone, gpui::private::serde::Deserialize)] #[serde(deny_unknown_fields)] struct RegisterableAction {} diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 6ad3c4c2cd1d6f45180ba752d3ef357aa0c6c2ac..736395913a1c15e88af6be2a308cd3aa522374b2 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -29,7 +29,6 @@ project.workspace = true schemars.workspace = true search.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings.workspace = true smallvec.workspace = true diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 09e450c149ab5580e6c86241f8c3e30515b93a4f..2a01d10a02449d47cad8a1c89bb9269f25db2725 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -1,7 +1,7 @@ use editor::EditorSettings; use gpui::Pixels; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index dea04243b2ed8f6c0611f17077a00e0c02c13693..5821bc6297266cffba19234d82efec3f6190c310 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -40,7 +40,6 @@ project.workspace = true runtimelib.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings.workspace = true smol.workspace = true diff --git a/crates/repl/src/repl_settings.rs b/crates/repl/src/repl_settings.rs index 541455c7fc3adc16cbb1e48fb811a2ac3a30bbbc..2fbf4b63731009a30691f04067bf96fbb8250880 100644 --- a/crates/repl/src/repl_settings.rs +++ b/crates/repl/src/repl_settings.rs @@ -1,6 +1,6 @@ use gpui::App; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; /// Settings for configuring REPL display and behavior. diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index d9b8d7275f9abdd60df85443988595f025bf26c0..4748644604ace11febddd5dcf34b43954dc361c6 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -29,7 +29,6 @@ release_channel.workspace = true rust-embed.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings_ui_macros.workspace = true serde_json_lenient.workspace = true diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index f2270711938c9bcbf27cdcb3d9271d5d2baeec27..abce84daf7cf2b0adc304894476d9c763e5e1a3d 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1588,7 +1588,7 @@ mod tests { use super::*; // This is so the SettingsUi macro can still work properly use crate as settings; - use serde_derive::Deserialize; + use serde::Deserialize; use settings_ui_macros::{SettingsKey, SettingsUi}; use unindent::Unindent; @@ -2232,7 +2232,9 @@ mod tests { #[derive(Debug, Deserialize)] struct JournalSettings { + #[expect(unused)] pub path: String, + #[expect(unused)] pub hour_format: HourFormat, } diff --git a/crates/supermaven_api/src/supermaven_api.rs b/crates/supermaven_api/src/supermaven_api.rs index c4b1409d646b0d402684d3c883a0ea633d12bbb5..539826c8176d0adf0e8ed1fb11ffdec8c15347a9 100644 --- a/crates/supermaven_api/src/supermaven_api.rs +++ b/crates/supermaven_api/src/supermaven_api.rs @@ -56,7 +56,15 @@ pub struct SupermavenDownloadResponse { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct SupermavenUser { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] id: String, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] email: String, api_key: String, } diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index de27b5e11746576c5923b8a2fca584b39161234d..48a0a1aadd3efe2239754c5c4971492bec6a5612 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -29,7 +29,6 @@ libc.workspace = true release_channel.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true settings.workspace = true sysinfo.workspace = true smol.workspace = true diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 0ab92a0f26d35710da7fd0a2e88542a98c7affed..d0a25257b91eb60b60390cde751d1af8af5866e1 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -4,7 +4,7 @@ use alacritty_terminal::vte::ansi::{ use collections::HashMap; use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px}; use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use settings::{SettingsKey, SettingsSources, SettingsUi}; use std::path::PathBuf; diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 998d31bb3c5473de324dbfd34a3b276c81d95aba..00f8e1868e5ddf00fa1fdaadb042c23d6013f5d8 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -31,7 +31,6 @@ parking_lot.workspace = true refineable.workspace = true schemars = { workspace = true, features = ["indexmap2"] } serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true serde_repr.workspace = true diff --git a/crates/theme/src/styles/accents.rs b/crates/theme/src/styles/accents.rs index cda0ef778a6b974d715cfbdb8b0c8f9067b411cc..52244fee46ce12edbbe361c3bec9bb80139f1167 100644 --- a/crates/theme/src/styles/accents.rs +++ b/crates/theme/src/styles/accents.rs @@ -1,5 +1,5 @@ use gpui::Hsla; -use serde_derive::Deserialize; +use serde::Deserialize; use crate::{ AccentContent, amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple, diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index 4b1f0976b64b28a06bb7873b994d636a159bb396..d10ac61ba6bfeabbf0642d832e68841c39176546 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] use gpui::Hsla; -use serde_derive::Deserialize; +use serde::Deserialize; use crate::{ PlayerColorContent, amber, blue, jade, lime, orange, pink, purple, red, try_parse_color, diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index ebb2840d0401d05b2bcac4e8c001dc30424f0fe5..339c88adea016dc260867ebce1128962bcd563e1 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -21,8 +21,20 @@ const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/schema/themes/v0.2.0.json"; #[derive(Debug, Deserialize)] struct FamilyMetadata { + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub name: String, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub author: String, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub themes: Vec, /// Overrides for specific syntax tokens. @@ -31,6 +43,10 @@ struct FamilyMetadata { /// to an exact set of scopes when it is not otherwise possible /// to rely on the default mappings in the theme importer. #[serde(default)] + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub syntax: IndexMap>, } diff --git a/crates/theme_importer/src/vscode/theme.rs b/crates/theme_importer/src/vscode/theme.rs index b6b073b4467fa412e023babd8ef3459827ae4083..2479dd312ecc231096e9a26c3a5babd8649133a1 100644 --- a/crates/theme_importer/src/vscode/theme.rs +++ b/crates/theme_importer/src/vscode/theme.rs @@ -6,12 +6,32 @@ use crate::vscode::VsCodeTokenColor; #[derive(Deserialize, Debug)] pub struct VsCodeTheme { #[serde(rename = "$schema")] + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub schema: Option, pub name: Option, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub author: Option, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub maintainers: Option>, #[serde(rename = "semanticClass")] + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] pub semantic_class: Option, + #[expect( + unused, + reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" + )] #[serde(rename = "semanticHighlighting")] pub semantic_highlighting: Option, pub colors: Colors, diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 434b14b07cd64f6ed1e8e658fde68cff855ec633..abe92dd58594f05d8cf71dbde4fb129aafa26a03 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -38,7 +38,6 @@ regex.workspace = true schemars.workspace = true search.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings.workspace = true task.workspace = true diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 43ec12d9cde762804a2b5c6bfc1cbae08b545403..a92ec7f6d3a05b26d31feca567e7a2e08f7f2b20 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -3,7 +3,7 @@ use gpui::{Action, Context, Window, actions}; use language::Point; use schemars::JsonSchema; use search::{BufferSearchBar, SearchOptions, buffer_search}; -use serde_derive::Deserialize; +use serde::Deserialize; use settings::Settings; use std::{iter::Peekable, str::Chars}; use util::serde::default_true; diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 7f388acd0c4bb342a4a7f1e39142cf70c9047f95..7faff54e73b179bdfa944798a9c87fafa245f732 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -38,7 +38,7 @@ use normal::search::SearchSubmit; use object::Object; use schemars::JsonSchema; use serde::Deserialize; -use serde_derive::Serialize; +use serde::Serialize; use settings::{ Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi, update_settings_file, }; diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 54379064460e30818781539d9d28c9f2a53a63bc..7201dedb451c3db27998695b75b62847223e6b72 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -114,6 +114,7 @@ sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] } semver = { version = "1", features = ["serde"] } serde = { version = "1", features = ["alloc", "derive", "rc"] } +serde_core = { version = "1", default-features = false, features = ["alloc", "rc", "result", "std"] } serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] } sha1 = { version = "0.10", features = ["compress"] } simd-adler32 = { version = "0.3" } @@ -249,7 +250,7 @@ sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] } semver = { version = "1", features = ["serde"] } serde = { version = "1", features = ["alloc", "derive", "rc"] } -serde_derive = { version = "1", features = ["deserialize_in_place"] } +serde_core = { version = "1", default-features = false, features = ["alloc", "rc", "result", "std"] } serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] } sha1 = { version = "0.10", features = ["compress"] } simd-adler32 = { version = "0.3" } From 85aa458b9c49b8b9043f42b113944c5eb915035a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 14 Sep 2025 17:49:56 +0200 Subject: [PATCH 011/721] helix: Drop back to normal mode after yanking in select mode (#38133) Follow-up to https://github.com/zed-industries/zed/pull/38117. @romaninsh I'd appreciate if you could have a look :-) Release Notes: - N/A --- crates/vim/src/helix.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 369ec2df11e2d5cf99a0b3d7a72ccd5619161f2f..cc235d67ae6efcae2fb5a5c5d899b9f7776cbda4 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -346,6 +346,9 @@ impl Vim { ); } }); + + // Drop back to normal mode after yanking + self.switch_mode(Mode::HelixNormal, true, window, cx); } fn helix_insert(&mut self, _: &HelixInsert, window: &mut Window, cx: &mut Context) { @@ -842,7 +845,16 @@ mod test { cx.simulate_keystrokes("y"); cx.shared_clipboard().assert_eq("worl"); cx.assert_state("hello «worlˇ»d", Mode::HelixNormal); + + // Test yanking in select mode character by character + cx.set_state("hello ˇworld", Mode::HelixNormal); + cx.simulate_keystroke("v"); + cx.assert_state("hello «wˇ»orld", Mode::HelixSelect); + cx.simulate_keystroke("y"); + cx.assert_state("hello «wˇ»orld", Mode::HelixNormal); + cx.shared_clipboard().assert_eq("w"); } + #[gpui::test] async fn test_shift_r_paste(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; From 256a91019aadec923bacf4c860b07a5888852201 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Sun, 14 Sep 2025 09:01:00 -0700 Subject: [PATCH 012/721] ci: Move doctests to a separate parallel job (#38111) Follow on from #37851 This may reduce CI time by running doctests in parallel with other tests. It also makes it easier to find the results. Example output: https://github.com/zed-industries/zed/actions/runs/17698218116/job/50300398669?pr=38111 At least on this run, the doctests finished before the main Linux tests, which makes sense because there are many fewer doctests. So they should not be on the critical path. Thanks @maxdeviant for the prompt. image Release Notes: - N/A --- .github/actions/run_tests/action.yml | 5 ---- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index 7a4d3e948bb4d205412ca93fa5cc0e1351e65ebd..e46bc26945e4b5f94ad9f98a882aaa51fc6189af 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -21,8 +21,3 @@ runs: - name: Run tests shell: bash -euxo pipefail {0} run: cargo nextest run --workspace --no-fail-fast - - - name: Run doctests - shell: bash -euxo pipefail {0} - # Nextest currently doesn't support doctests - run: cargo test --workspace --doc --no-fail-fast diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d416b4af0eedf38da249e39181bd8017b57f752c..34ef3c20cc3497d27ece10b1b8f1619b23f95f97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -373,6 +373,46 @@ jobs: if: always() run: rm -rf ./../.cargo + doctests: + # Nextest currently doesn't support doctests, so run them separately and in parallel. + timeout-minutes: 60 + name: (Linux) Run doctests + needs: [job_spec] + if: | + github.repository_owner == 'zed-industries' && + needs.job_spec.outputs.run_tests == 'true' + runs-on: + - namespace-profile-16x32-ubuntu-2204 + steps: + - name: Add Rust to the PATH + run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + clean: false + + - name: Cache dependencies + uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + # cache-provider: "buildjet" + + - name: Install Linux dependencies + run: ./script/linux + + - name: Configure CI + run: | + mkdir -p ./../.cargo + cp ./.cargo/ci-config.toml ./../.cargo/config.toml + + - name: Run doctests + run: cargo test --workspace --doc --no-fail-fast + + - name: Clean CI config file + if: always() + run: rm -rf ./../.cargo + build_remote_server: timeout-minutes: 60 name: (Linux) Build Remote Server From 1c27a6dbc2f754ed0b1ce67f25b15bade9297ce0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 14 Sep 2025 23:05:03 +0700 Subject: [PATCH 013/721] Do not escape glob pattern in dynamic Jest/Vitest test names (#36999) Related to #35090 Release Notes: - javascript: Fixed name escaping in dynamic jest/vitest task names --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- Cargo.lock | 1 + crates/languages/Cargo.toml | 1 + crates/languages/src/typescript.rs | 46 ++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65e57c3c9bc922edd5d3658b590e5627a63e1d9d..db61ba7b121773c6e6941dd940201ec0a9e82f73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9292,6 +9292,7 @@ dependencies = [ "futures 0.3.31", "gpui", "http_client", + "itertools 0.14.0", "language", "log", "lsp", diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 44b0cb58653adff27e31c6edd7fb4208e0bb4bbf..7ebafd8fdd9e2310c207d47a7d911516a498d00c 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -45,6 +45,7 @@ dap.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true +itertools.workspace = true language.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index bc939f0b727f5ca60a851be99aaf0f3e61ee3a93..edfddd3f76e374e9ae0d1ce71cfb0b7b3c586c4d 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -5,6 +5,7 @@ use collections::HashMap; use futures::future::join_all; use gpui::{App, AppContext, AsyncApp, Task}; use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url}; +use itertools::Itertools as _; use language::{ ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain, @@ -18,7 +19,7 @@ use std::{ borrow::Cow, ffi::OsString, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, VariableName}; use util::merge_json_value_into; @@ -511,9 +512,9 @@ fn eslint_server_binary_arguments(server_path: &Path) -> Vec { } fn replace_test_name_parameters(test_name: &str) -> String { - let pattern = regex::Regex::new(r"(%|\$)[0-9a-zA-Z]+").unwrap(); - - regex::escape(&pattern.replace_all(test_name, "(.+?)")) + static PATTERN: LazyLock = + LazyLock::new(|| regex::Regex::new(r"(\$([A-Za-z0-9_\.]+|[\#])|%[psdifjo#\$%])").unwrap()); + PATTERN.split(test_name).map(regex::escape).join("(.+?)") } pub struct TypeScriptLspAdapter { @@ -1015,7 +1016,9 @@ mod tests { use unindent::Unindent; use util::path; - use crate::typescript::{PackageJsonData, TypeScriptContextProvider}; + use crate::typescript::{ + PackageJsonData, TypeScriptContextProvider, replace_test_name_parameters, + }; #[gpui::test] async fn test_outline(cx: &mut TestAppContext) { @@ -1227,4 +1230,37 @@ mod tests { ] ); } + #[test] + fn test_escaping_name() { + let cases = [ + ("plain test name", "plain test name"), + ("test name with $param_name", "test name with (.+?)"), + ("test name with $nested.param.name", "test name with (.+?)"), + ("test name with $#", "test name with (.+?)"), + ("test name with $##", "test name with (.+?)\\#"), + ("test name with %p", "test name with (.+?)"), + ("test name with %s", "test name with (.+?)"), + ("test name with %d", "test name with (.+?)"), + ("test name with %i", "test name with (.+?)"), + ("test name with %f", "test name with (.+?)"), + ("test name with %j", "test name with (.+?)"), + ("test name with %o", "test name with (.+?)"), + ("test name with %#", "test name with (.+?)"), + ("test name with %$", "test name with (.+?)"), + ("test name with %%", "test name with (.+?)"), + ("test name with %q", "test name with %q"), + ( + "test name with regex chars .*+?^${}()|[]\\", + "test name with regex chars \\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\", + ), + ( + "test name with multiple $params and %pretty and %b and (.+?)", + "test name with multiple (.+?) and (.+?)retty and %b and \\(\\.\\+\\?\\)", + ), + ]; + + for (input, expected) in cases { + assert_eq!(replace_test_name_parameters(input), expected); + } + } } From 99b71677c61588031bcb6b4c0543a2491962906f Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Sun, 14 Sep 2025 11:36:26 -0500 Subject: [PATCH 014/721] Ability to update JSON arrays (#38087) Closes #ISSUE Adds the ability to our JSON updating code to update arrays within other objects. Previously updating of arrays was limited to just top level arrays (i.e. `keymap.json`) however this PR makes it so nested arrays are supported as well using `#{index}` syntax as a key. This PR also fixes an issue with the array updating code that meant that updating empty json values `""` or an empty `keymap.json` file in the case of the Keymap Editor would fail instead of creating a new array. Release Notes: - Fixed an issue where keybindings would fail to save in the Keymap Editor if the `keymap.json` file was completely empty --- crates/settings/src/keymap_file.rs | 77 +-- crates/settings/src/settings_json.rs | 907 +++++++++++++++++++++++++-- 2 files changed, 898 insertions(+), 86 deletions(-) diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index be44d519449ecc003746a8a41513669bb4e90329..c52040f38b0544cdce4ece7bd8afdaa9bfdf4fbc 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -678,8 +678,7 @@ impl KeymapFile { None, index, tab_size, - ) - .context("Failed to remove keybinding")?; + ); keymap_contents.replace_range(replace_range, &replace_value); return Ok(keymap_contents); } @@ -699,16 +698,14 @@ impl KeymapFile { // if we are only changing the keybinding (common case) // not the context, etc. Then just update the binding in place - let (replace_range, replace_value) = - replace_top_level_array_value_in_json_text( - &keymap_contents, - &["bindings", keystrokes_str], - Some(&source_action_value), - Some(&source.keystrokes_unparsed()), - index, - tab_size, - ) - .context("Failed to replace keybinding")?; + let (replace_range, replace_value) = replace_top_level_array_value_in_json_text( + &keymap_contents, + &["bindings", keystrokes_str], + Some(&source_action_value), + Some(&source.keystrokes_unparsed()), + index, + tab_size, + ); keymap_contents.replace_range(replace_range, &replace_value); return Ok(keymap_contents); @@ -721,28 +718,24 @@ impl KeymapFile { // just update the section in place, updating the context // and the binding - let (replace_range, replace_value) = - replace_top_level_array_value_in_json_text( - &keymap_contents, - &["bindings", keystrokes_str], - Some(&source_action_value), - Some(&source.keystrokes_unparsed()), - index, - tab_size, - ) - .context("Failed to replace keybinding")?; + let (replace_range, replace_value) = replace_top_level_array_value_in_json_text( + &keymap_contents, + &["bindings", keystrokes_str], + Some(&source_action_value), + Some(&source.keystrokes_unparsed()), + index, + tab_size, + ); keymap_contents.replace_range(replace_range, &replace_value); - let (replace_range, replace_value) = - replace_top_level_array_value_in_json_text( - &keymap_contents, - &["context"], - source.context.map(Into::into).as_ref(), - None, - index, - tab_size, - ) - .context("Failed to replace keybinding")?; + let (replace_range, replace_value) = replace_top_level_array_value_in_json_text( + &keymap_contents, + &["context"], + source.context.map(Into::into).as_ref(), + None, + index, + tab_size, + ); keymap_contents.replace_range(replace_range, &replace_value); return Ok(keymap_contents); } else { @@ -751,16 +744,14 @@ impl KeymapFile { // section, then treat this operation as an add operation of the // new binding with the updated context. - let (replace_range, replace_value) = - replace_top_level_array_value_in_json_text( - &keymap_contents, - &["bindings", keystrokes_str], - None, - None, - index, - tab_size, - ) - .context("Failed to replace keybinding")?; + let (replace_range, replace_value) = replace_top_level_array_value_in_json_text( + &keymap_contents, + &["bindings", keystrokes_str], + None, + None, + index, + tab_size, + ); keymap_contents.replace_range(replace_range, &replace_value); operation = KeybindUpdateOperation::Add { source, @@ -811,7 +802,7 @@ impl KeymapFile { &keymap_contents, &value.into(), tab_size, - )?; + ); keymap_contents.replace_range(replace_range, &replace_value); } return Ok(keymap_contents); diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs index 70c76e23c402b8debcb5e406cc86fa7125e78c5a..614f5eed813a22d96eafe0ab0696161dc21b233f 100644 --- a/crates/settings/src/settings_json.rs +++ b/crates/settings/src/settings_json.rs @@ -140,8 +140,9 @@ pub fn replace_value_in_json_text>( let found_key = text .get(key_range.clone()) - .and_then(|key_text| { - serde_json::to_string(key_path[depth].as_ref()) + .zip(key_path.get(depth)) + .and_then(|(key_text, key_path_value)| { + serde_json::to_string(key_path_value.as_ref()) .ok() .map(|key_path| depth < key_path.len() && key_text == key_path) }) @@ -157,6 +158,18 @@ pub fn replace_value_in_json_text>( break; } + if let Some(array_replacement) = handle_possible_array_value( + &mat.captures[0].node, + &mat.captures[1].node, + text, + &key_path[depth..], + new_value, + replace_key, + tab_size, + ) { + return array_replacement; + } + first_key_start = None; } } @@ -227,17 +240,12 @@ pub fn replace_value_in_json_text>( (removal_start..removal_end, String::new()) } } else { - // We have key paths, construct the sub objects - let new_key = key_path[depth].as_ref(); - - // We don't have the key, construct the nested objects - let mut new_value = - serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap(); - for key in key_path[(depth + 1)..].iter().rev() { - new_value = serde_json::json!({ key.as_ref().to_string(): new_value }); - } - if let Some(first_key_start) = first_key_start { + // We have key paths, construct the sub objects + let new_key = key_path[depth].as_ref(); + // We don't have the key, construct the nested objects + let new_value = construct_json_value(&key_path[(depth + 1)..], new_value); + let mut row = 0; let mut column = 0; for (ix, char) in text.char_indices() { @@ -265,7 +273,8 @@ pub fn replace_value_in_json_text>( (first_key_start..first_key_start, content) } } else { - new_value = serde_json::json!({ new_key.to_string(): new_value }); + // We don't have the key, construct the nested objects + let new_value = construct_json_value(&key_path[depth..], new_value); let indent_prefix_len = 4 * depth; let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); if depth == 0 { @@ -297,35 +306,124 @@ pub fn replace_value_in_json_text>( } } +fn construct_json_value( + key_path: &[impl AsRef], + new_value: Option<&serde_json::Value>, +) -> serde_json::Value { + let mut new_value = + serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap(); + for key in key_path.iter().rev() { + if parse_index_key(key.as_ref()).is_some() { + new_value = serde_json::json!([new_value]); + } else { + new_value = serde_json::json!({ key.as_ref().to_string(): new_value }); + } + } + return new_value; +} + +fn parse_index_key(index_key: &str) -> Option { + index_key.strip_prefix('#')?.parse().ok() +} + +fn handle_possible_array_value( + key_node: &tree_sitter::Node, + value_node: &tree_sitter::Node, + text: &str, + remaining_key_path: &[impl AsRef], + new_value: Option<&Value>, + replace_key: Option<&str>, + tab_size: usize, +) -> Option<(Range, String)> { + if remaining_key_path.is_empty() { + return None; + } + let key_path = remaining_key_path; + let index = parse_index_key(key_path[0].as_ref())?; + + let value_is_array = value_node.kind() == TS_ARRAY_KIND; + + let array_str = if value_is_array { + &text[value_node.byte_range()] + } else { + "" + }; + + let (mut replace_range, mut replace_value) = replace_top_level_array_value_in_json_text( + array_str, + &key_path[1..], + new_value, + replace_key, + index, + tab_size, + ); + + if value_is_array { + replace_range.start += value_node.start_byte(); + replace_range.end += value_node.start_byte(); + } else { + // replace the full value if it wasn't an array + replace_range = value_node.byte_range(); + } + let non_whitespace_char_count = replace_value.len() + - replace_value + .chars() + .filter(char::is_ascii_whitespace) + .count(); + let needs_indent = replace_value.ends_with('\n') + || (replace_value + .chars() + .zip(replace_value.chars().skip(1)) + .any(|(c, next_c)| c == '\n' && !next_c.is_ascii_whitespace())); + let contains_comment = (replace_value.contains("//") && replace_value.contains('\n')) + || (replace_value.contains("/*") && replace_value.contains("*/")); + if needs_indent { + let indent_width = key_node.start_position().column; + let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); + replace_value = replace_value.replace('\n', &increased_indent); + } else if non_whitespace_char_count < 32 && !contains_comment { + // remove indentation + while let Some(idx) = replace_value.find("\n ") { + replace_value.remove(idx); + } + while let Some(idx) = replace_value.find(" ") { + replace_value.remove(idx); + } + } + return Some((replace_range, replace_value)); +} + 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, - key_path: &[&str], + key_path: &[impl AsRef], new_value: Option<&Value>, replace_key: Option<&str>, array_index: usize, tab_size: usize, -) -> Result<(Range, String)> { +) -> (Range, String) { let mut parser = tree_sitter::Parser::new(); parser .set_language(&tree_sitter_json::LANGUAGE.into()) .unwrap(); + let syntax_tree = parser.parse(text, None).unwrap(); let mut cursor = syntax_tree.walk(); if cursor.node().kind() == TS_DOCUMENT_KIND { - anyhow::ensure!( - cursor.goto_first_child(), - "Document empty - No top level array" - ); + cursor.goto_first_child(); } while cursor.node().kind() != TS_ARRAY_KIND { - anyhow::ensure!(cursor.goto_next_sibling(), "EOF - No top level array"); + if !cursor.goto_next_sibling() { + let json_value = construct_json_value(key_path, new_value); + let json_value = serde_json::json!([json_value]); + return (0..text.len(), to_pretty_json(&json_value, tab_size, 0)); + } } // false if no children @@ -350,7 +448,7 @@ pub fn replace_top_level_array_value_in_json_text( if let Some(new_value) = new_value { return append_top_level_array_value_in_json_text(text, new_value, tab_size); } else { - return Ok((0..0, String::new())); + return (0..0, String::new()); } } } @@ -386,8 +484,19 @@ pub fn replace_top_level_array_value_in_json_text( remove_range.start = cursor.node().range().start_byte; } } - Ok((remove_range, String::new())) + (remove_range, String::new()) } else { + if let Some(array_replacement) = handle_possible_array_value( + &cursor.node(), + &cursor.node(), + text, + key_path, + new_value, + replace_key, + tab_size, + ) { + return array_replacement; + } let (mut replace_range, mut replace_value) = replace_value_in_json_text(value_str, key_path, tab_size, new_value, replace_key); @@ -397,7 +506,6 @@ pub fn replace_top_level_array_value_in_json_text( if needs_indent { let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); replace_value = replace_value.replace('\n', &increased_indent); - // replace_value.push('\n'); } else { while let Some(idx) = replace_value.find("\n ") { replace_value.remove(idx + 1); @@ -407,7 +515,7 @@ pub fn replace_top_level_array_value_in_json_text( } } - Ok((replace_range, replace_value)) + (replace_range, replace_value) } } @@ -415,7 +523,7 @@ pub fn append_top_level_array_value_in_json_text( text: &str, new_value: &Value, tab_size: usize, -) -> Result<(Range, String)> { +) -> (Range, String) { let mut parser = tree_sitter::Parser::new(); parser .set_language(&tree_sitter_json::LANGUAGE.into()) @@ -425,21 +533,21 @@ pub fn append_top_level_array_value_in_json_text( let mut cursor = syntax_tree.walk(); if cursor.node().kind() == TS_DOCUMENT_KIND { - anyhow::ensure!( - cursor.goto_first_child(), - "Document empty - No top level array" - ); + cursor.goto_first_child(); } while cursor.node().kind() != TS_ARRAY_KIND { - anyhow::ensure!(cursor.goto_next_sibling(), "EOF - No top level array"); + if !cursor.goto_next_sibling() { + let json_value = serde_json::json!([new_value]); + return (0..text.len(), to_pretty_json(&json_value, tab_size, 0)); + } } - anyhow::ensure!( - cursor.goto_last_child(), + let went_to_last_child = cursor.goto_last_child(); + debug_assert!( + went_to_last_child && cursor.node().kind() == "]", "Malformed JSON syntax tree, expected `]` at end of array" ); - debug_assert_eq!(cursor.node().kind(), "]"); let close_bracket_start = cursor.node().start_byte(); while cursor.goto_previous_sibling() && (cursor.node().is_extra() || cursor.node().is_missing()) @@ -508,7 +616,7 @@ pub fn append_top_level_array_value_in_json_text( if comma_range.is_none() { replace_value.insert(0, ','); } - } else { + } else if replace_value.contains('\n') || text.contains('\n') { if let Some(prev_newline) = text[..replace_range.start].rfind('\n') && text[prev_newline..replace_range.start].trim().is_empty() { @@ -519,7 +627,7 @@ pub fn append_top_level_array_value_in_json_text( replace_value.insert_str(0, &indent); replace_value.push('\n'); } - return Ok((replace_range, replace_value)); + return (replace_range, replace_value); fn is_error_of_kind(cursor: &mut tree_sitter::TreeCursor<'_>, kind: &str) -> bool { if cursor.node().kind() != "ERROR" { @@ -1045,6 +1153,689 @@ mod tests { ); } + #[test] + fn object_replace_array() { + // Tests replacing values within arrays that are nested inside objects. + // Uses "#N" syntax in key paths to indicate array indices. + #[track_caller] + fn check_object_replace_array( + input: String, + key_path: &[&str], + value: Option, + expected: String, + ) { + let result = replace_value_in_json_text(&input, key_path, 4, value.as_ref(), None); + let mut result_str = input; + result_str.replace_range(result.0, &result.1); + pretty_assertions::assert_eq!(expected, result_str); + } + + // Basic array element replacement + check_object_replace_array( + r#"{ + "a": [1, 3], + }"# + .unindent(), + &["a", "#1"], + Some(json!(2)), + r#"{ + "a": [1, 2], + }"# + .unindent(), + ); + + // Replace first element + check_object_replace_array( + r#"{ + "items": [1, 2, 3] + }"# + .unindent(), + &["items", "#0"], + Some(json!(10)), + r#"{ + "items": [10, 2, 3] + }"# + .unindent(), + ); + + // Replace last element + check_object_replace_array( + r#"{ + "items": [1, 2, 3] + }"# + .unindent(), + &["items", "#2"], + Some(json!(30)), + r#"{ + "items": [1, 2, 30] + }"# + .unindent(), + ); + + // Replace string in array + check_object_replace_array( + r#"{ + "names": ["alice", "bob", "charlie"] + }"# + .unindent(), + &["names", "#1"], + Some(json!("robert")), + r#"{ + "names": ["alice", "robert", "charlie"] + }"# + .unindent(), + ); + + // Replace boolean + check_object_replace_array( + r#"{ + "flags": [true, false, true] + }"# + .unindent(), + &["flags", "#0"], + Some(json!(false)), + r#"{ + "flags": [false, false, true] + }"# + .unindent(), + ); + + // Replace null with value + check_object_replace_array( + r#"{ + "values": [null, 2, null] + }"# + .unindent(), + &["values", "#0"], + Some(json!(1)), + r#"{ + "values": [1, 2, null] + }"# + .unindent(), + ); + + // Replace value with null + check_object_replace_array( + r#"{ + "data": [1, 2, 3] + }"# + .unindent(), + &["data", "#1"], + Some(json!(null)), + r#"{ + "data": [1, null, 3] + }"# + .unindent(), + ); + + // Replace simple value with object + check_object_replace_array( + r#"{ + "list": [1, 2, 3] + }"# + .unindent(), + &["list", "#1"], + Some(json!({"value": 2, "label": "two"})), + r#"{ + "list": [1, { "value": 2, "label": "two" }, 3] + }"# + .unindent(), + ); + + // Replace simple value with nested array + check_object_replace_array( + r#"{ + "matrix": [1, 2, 3] + }"# + .unindent(), + &["matrix", "#1"], + Some(json!([20, 21, 22])), + r#"{ + "matrix": [1, [ 20, 21, 22 ], 3] + }"# + .unindent(), + ); + + // Replace object in array + check_object_replace_array( + r#"{ + "users": [ + {"name": "alice"}, + {"name": "bob"}, + {"name": "charlie"} + ] + }"# + .unindent(), + &["users", "#1"], + Some(json!({"name": "robert", "age": 30})), + r#"{ + "users": [ + {"name": "alice"}, + { "name": "robert", "age": 30 }, + {"name": "charlie"} + ] + }"# + .unindent(), + ); + + // Replace property within object in array + check_object_replace_array( + r#"{ + "users": [ + {"name": "alice", "age": 25}, + {"name": "bob", "age": 30}, + {"name": "charlie", "age": 35} + ] + }"# + .unindent(), + &["users", "#1", "age"], + Some(json!(31)), + r#"{ + "users": [ + {"name": "alice", "age": 25}, + {"name": "bob", "age": 31}, + {"name": "charlie", "age": 35} + ] + }"# + .unindent(), + ); + + // Add new property to object in array + check_object_replace_array( + r#"{ + "items": [ + {"id": 1}, + {"id": 2}, + {"id": 3} + ] + }"# + .unindent(), + &["items", "#1", "name"], + Some(json!("Item Two")), + r#"{ + "items": [ + {"id": 1}, + {"name": "Item Two", "id": 2}, + {"id": 3} + ] + }"# + .unindent(), + ); + + // Remove property from object in array + check_object_replace_array( + r#"{ + "items": [ + {"id": 1, "name": "one"}, + {"id": 2, "name": "two"}, + {"id": 3, "name": "three"} + ] + }"# + .unindent(), + &["items", "#1", "name"], + None, + r#"{ + "items": [ + {"id": 1, "name": "one"}, + {"id": 2}, + {"id": 3, "name": "three"} + ] + }"# + .unindent(), + ); + + // Deeply nested: array in object in array + check_object_replace_array( + r#"{ + "data": [ + { + "values": [1, 2, 3] + }, + { + "values": [4, 5, 6] + } + ] + }"# + .unindent(), + &["data", "#0", "values", "#1"], + Some(json!(20)), + r#"{ + "data": [ + { + "values": [1, 20, 3] + }, + { + "values": [4, 5, 6] + } + ] + }"# + .unindent(), + ); + + // Multiple levels of nesting + check_object_replace_array( + r#"{ + "root": { + "level1": [ + { + "level2": { + "level3": [10, 20, 30] + } + } + ] + } + }"# + .unindent(), + &["root", "level1", "#0", "level2", "level3", "#2"], + Some(json!(300)), + r#"{ + "root": { + "level1": [ + { + "level2": { + "level3": [10, 20, 300] + } + } + ] + } + }"# + .unindent(), + ); + + // Array with mixed types + check_object_replace_array( + r#"{ + "mixed": [1, "two", true, null, {"five": 5}] + }"# + .unindent(), + &["mixed", "#3"], + Some(json!({"four": 4})), + r#"{ + "mixed": [1, "two", true, { "four": 4 }, {"five": 5}] + }"# + .unindent(), + ); + + // Replace with complex object + check_object_replace_array( + r#"{ + "config": [ + "simple", + "values" + ] + }"# + .unindent(), + &["config", "#0"], + Some(json!({ + "type": "complex", + "settings": { + "enabled": true, + "level": 5 + } + })), + r#"{ + "config": [ + { + "type": "complex", + "settings": { + "enabled": true, + "level": 5 + } + }, + "values" + ] + }"# + .unindent(), + ); + + // Array with trailing comma + check_object_replace_array( + r#"{ + "items": [ + 1, + 2, + 3, + ] + }"# + .unindent(), + &["items", "#1"], + Some(json!(20)), + r#"{ + "items": [ + 1, + 20, + 3, + ] + }"# + .unindent(), + ); + + // Array with comments + check_object_replace_array( + r#"{ + "items": [ + 1, // first item + 2, // second item + 3 // third item + ] + }"# + .unindent(), + &["items", "#1"], + Some(json!(20)), + r#"{ + "items": [ + 1, // first item + 20, // second item + 3 // third item + ] + }"# + .unindent(), + ); + + // Multiple arrays in object + check_object_replace_array( + r#"{ + "first": [1, 2, 3], + "second": [4, 5, 6], + "third": [7, 8, 9] + }"# + .unindent(), + &["second", "#1"], + Some(json!(50)), + r#"{ + "first": [1, 2, 3], + "second": [4, 50, 6], + "third": [7, 8, 9] + }"# + .unindent(), + ); + + // Empty array - add first element + check_object_replace_array( + r#"{ + "empty": [] + }"# + .unindent(), + &["empty", "#0"], + Some(json!("first")), + r#"{ + "empty": ["first"] + }"# + .unindent(), + ); + + // Array of arrays + check_object_replace_array( + r#"{ + "matrix": [ + [1, 2], + [3, 4], + [5, 6] + ] + }"# + .unindent(), + &["matrix", "#1", "#0"], + Some(json!(30)), + r#"{ + "matrix": [ + [1, 2], + [30, 4], + [5, 6] + ] + }"# + .unindent(), + ); + + // Replace nested object property in array element + check_object_replace_array( + r#"{ + "users": [ + { + "name": "alice", + "address": { + "city": "NYC", + "zip": "10001" + } + } + ] + }"# + .unindent(), + &["users", "#0", "address", "city"], + Some(json!("Boston")), + r#"{ + "users": [ + { + "name": "alice", + "address": { + "city": "Boston", + "zip": "10001" + } + } + ] + }"# + .unindent(), + ); + + // Add element past end of array + check_object_replace_array( + r#"{ + "items": [1, 2] + }"# + .unindent(), + &["items", "#5"], + Some(json!(6)), + r#"{ + "items": [1, 2, 6] + }"# + .unindent(), + ); + + // Complex nested structure + check_object_replace_array( + r#"{ + "app": { + "modules": [ + { + "name": "auth", + "routes": [ + {"path": "/login", "method": "POST"}, + {"path": "/logout", "method": "POST"} + ] + }, + { + "name": "api", + "routes": [ + {"path": "/users", "method": "GET"}, + {"path": "/users", "method": "POST"} + ] + } + ] + } + }"# + .unindent(), + &["app", "modules", "#1", "routes", "#0", "method"], + Some(json!("PUT")), + r#"{ + "app": { + "modules": [ + { + "name": "auth", + "routes": [ + {"path": "/login", "method": "POST"}, + {"path": "/logout", "method": "POST"} + ] + }, + { + "name": "api", + "routes": [ + {"path": "/users", "method": "PUT"}, + {"path": "/users", "method": "POST"} + ] + } + ] + } + }"# + .unindent(), + ); + + // Escaped strings in array + check_object_replace_array( + r#"{ + "messages": ["hello", "world"] + }"# + .unindent(), + &["messages", "#0"], + Some(json!("hello \"quoted\" world")), + r#"{ + "messages": ["hello \"quoted\" world", "world"] + }"# + .unindent(), + ); + + // Block comments + check_object_replace_array( + r#"{ + "data": [ + /* first */ 1, + /* second */ 2, + /* third */ 3 + ] + }"# + .unindent(), + &["data", "#1"], + Some(json!(20)), + r#"{ + "data": [ + /* first */ 1, + /* second */ 20, + /* third */ 3 + ] + }"# + .unindent(), + ); + + // Inline array + check_object_replace_array( + r#"{"items": [1, 2, 3], "count": 3}"#.to_string(), + &["items", "#1"], + Some(json!(20)), + r#"{"items": [1, 20, 3], "count": 3}"#.to_string(), + ); + + // Single element array + check_object_replace_array( + r#"{ + "single": [42] + }"# + .unindent(), + &["single", "#0"], + Some(json!(100)), + r#"{ + "single": [100] + }"# + .unindent(), + ); + + // Inconsistent formatting + check_object_replace_array( + r#"{ + "messy": [1, + 2, + 3, + 4] + }"# + .unindent(), + &["messy", "#2"], + Some(json!(30)), + r#"{ + "messy": [1, + 2, + 30, + 4] + }"# + .unindent(), + ); + + // Creates array if has numbered key + check_object_replace_array( + r#"{ + "array": {"foo": "bar"} + }"# + .unindent(), + &["array", "#3"], + Some(json!(4)), + r#"{ + "array": [ + 4 + ] + }"# + .unindent(), + ); + + // Replace non-array element within array with array + check_object_replace_array( + r#"{ + "matrix": [ + [1, 2], + [3, 4], + [5, 6] + ] + }"# + .unindent(), + &["matrix", "#1", "#0"], + Some(json!(["foo", "bar"])), + r#"{ + "matrix": [ + [1, 2], + [[ "foo", "bar" ], 4], + [5, 6] + ] + }"# + .unindent(), + ); + // Replace non-array element within array with array + check_object_replace_array( + r#"{ + "matrix": [ + [1, 2], + [3, 4], + [5, 6] + ] + }"# + .unindent(), + &["matrix", "#1", "#0", "#3"], + Some(json!(["foo", "bar"])), + r#"{ + "matrix": [ + [1, 2], + [[ [ "foo", "bar" ] ], 4], + [5, 6] + ] + }"# + .unindent(), + ); + + // Create array in key that doesn't exist + check_object_replace_array( + r#"{ + "foo": {} + }"# + .unindent(), + &["foo", "bar", "#0"], + Some(json!({"is_object": true})), + r#"{ + "foo": { + "bar": [ + { + "is_object": true + } + ] + } + }"# + .unindent(), + ); + } + #[test] fn array_replace() { #[track_caller] @@ -1063,8 +1854,7 @@ mod tests { None, index, 4, - ) - .expect("replace succeeded"); + ); let mut result_str = input; result_str.replace_range(result.0, &result.1); pretty_assertions::assert_eq!(expected.to_string(), result_str); @@ -1228,10 +2018,7 @@ mod tests { 0, &[], Some(json!("first")), - r#"[ - "first" - ]"# - .unindent(), + r#"["first"]"#.unindent(), ); // Test array with leading comments @@ -1411,6 +2198,32 @@ mod tests { ]"# .unindent(), ); + + check_array_replace( + r#""#, + 2, + &[], + Some(json!(42)), + r#"[ + 42 + ]"# + .unindent(), + ); + + check_array_replace( + r#""#, + 2, + &["foo", "bar"], + Some(json!(42)), + r#"[ + { + "foo": { + "bar": 42 + } + } + ]"# + .unindent(), + ); } #[test] @@ -1418,8 +2231,7 @@ mod tests { #[track_caller] fn check_array_append(input: impl ToString, value: Value, expected: impl ToString) { let input = input.to_string(); - let result = append_top_level_array_value_in_json_text(&input, &value, 4) - .expect("append succeeded"); + let result = append_top_level_array_value_in_json_text(&input, &value, 4); let mut result_str = input; result_str.replace_range(result.0, &result.1); pretty_assertions::assert_eq!(expected.to_string(), result_str); @@ -1677,5 +2489,14 @@ mod tests { ]"# .unindent(), ); + + check_array_append( + r#""#, + json!(42), + r#"[ + 42 + ]"# + .unindent(), + ) } } From 0adc6ddaada73f47949076f71be68eeab95d206d Mon Sep 17 00:00:00 2001 From: Alvaro Parker <64918109+AlvaroParker@users.noreply.github.com> Date: Sun, 14 Sep 2025 15:39:23 -0300 Subject: [PATCH 015/721] ui: Fix scrollbar showing despite being disabled by tracked setting (#38152) Closes #38147 The scrollbar's `show_state` field was always being initialized to `VisibilityState::Visible`, ignoring the `show_setting` value. Release Notes: - N/A --- crates/ui/src/components/scrollbar.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 35dd52e357480d0a82c1c474b609c49bf2c53db0..e4c2937be775eed4d9ae673da763e1eda5a65419 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -521,6 +521,7 @@ impl ScrollbarState { cx.observe_global_in::(window, Self::settings_changed) .detach(); + let show_setting = S::get_value(cx).visibility(cx); ScrollbarState { thumb_state: Default::default(), notify_id: config.tracked_entity.map(|id| id.unwrap_or(parent_id)), @@ -529,8 +530,8 @@ impl ScrollbarState { width: config.scrollbar_width, visibility: config.visibility, tracked_setting: PhantomData, - show_setting: S::get_value(cx).visibility(cx), - show_state: VisibilityState::Visible, + show_setting, + show_state: VisibilityState::from_show_setting(show_setting), mouse_in_parent: true, last_prepaint_state: None, _auto_hide_task: None, From f3e49e1b05a022a3f4b116721035790fd839b367 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sun, 14 Sep 2025 13:09:15 -0600 Subject: [PATCH 016/721] x11: Don't skip consecutive same key press events in the same batch (#38154) This has noticeable misbehavior when framerates are low (in my case this sometimes happens when CPUs are throttled and compilation is happening), as now a batch of x11 events can contain events over the span of 100s of millis. So in that case, key press repetitions with quite normal typing are skipped. Under normal operating conditions it can be reproduced by running this and quickly switching to Zed: > sleep 1; for i in {1..5}; do xdotool type --delay 5 "aaaaaa "; xdotool key Return; done Output before looks like: ``` aaa aaaaa aaa aaa aaaa ``` Output after looks like: ``` aaaaaa aaaaaa aaaaaa aaaaaa aaaaaa ``` This behavior was added in #13955. Release Notes: - N/A --- crates/gpui/src/platform/linux/x11/client.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 42c59701d3ee644b99bc8bb58002b429265c1a45..e6af7c0d17a54223ecee7a9801eb7a1e59fd6a1a 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -28,7 +28,7 @@ use x11rb::{ protocol::xkb::ConnectionExt as _, protocol::xproto::{ AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, - ConnectionExt as _, EventMask, KeyPressEvent, Visibility, + ConnectionExt as _, EventMask, Visibility, }, protocol::{Event, randr, render, xinput, xkb, xproto}, resource_manager::Database, @@ -525,7 +525,6 @@ impl X11Client { let mut windows_to_refresh = HashSet::new(); let mut last_key_release = None; - let mut last_key_press: Option = None; // event handlers for new keyboard / remapping refresh the state without using event // details, this deduplicates them. @@ -545,7 +544,6 @@ impl X11Client { if let Some(last_key_release) = last_key_release.take() { events.push(last_key_release); } - last_key_press = None; events.push(last_keymap_change_event); } @@ -558,16 +556,9 @@ impl X11Client { if let Some(last_key_release) = last_key_release.take() { events.push(last_key_release); } - last_key_press = None; events.push(last_keymap_change_event); } - if let Some(last_press) = last_key_press.as_ref() - && last_press.detail == key_press.detail - { - continue; - } - if let Some(Event::KeyRelease(key_release)) = last_key_release.take() { @@ -580,7 +571,6 @@ impl X11Client { } } events.push(Event::KeyPress(key_press)); - last_key_press = Some(key_press); } Event::XkbNewKeyboardNotify(_) | Event::XkbMapNotify(_) => { if let Some(release_event) = last_key_release.take() { From be7b22b0dcb5d6dabdb8d35c4641c1883c53f524 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sun, 14 Sep 2025 13:38:48 -0600 Subject: [PATCH 017/721] Show docs for all documented actions in `keymap.json` (#38156) Release Notes: - Fixed a bug where action documentation while editing `keymap.json` was only shown for actions that take input. --- crates/settings/src/keymap_file.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index c52040f38b0544cdce4ece7bd8afdaa9bfdf4fbc..6e9ab5c34d5749e3cdaf0f144930353f2323f17b 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -434,11 +434,13 @@ impl KeymapFile { let mut generator = Self::action_schema_generator(); let action_schemas = cx.action_schemas(&mut generator); + let action_documentation = cx.action_documentation(); let deprecations = cx.deprecated_actions_to_preferred_actions(); let deprecation_messages = cx.action_deprecation_messages(); KeymapFile::generate_json_schema( generator, action_schemas, + action_documentation, deprecations, deprecation_messages, ) @@ -447,6 +449,7 @@ impl KeymapFile { fn generate_json_schema( mut generator: schemars::SchemaGenerator, action_schemas: Vec<(&'static str, Option)>, + action_documentation: &HashMap<&'static str, &'static str>, deprecations: &HashMap<&'static str, &'static str>, deprecation_messages: &HashMap<&'static str, &'static str>, ) -> serde_json::Value { @@ -499,14 +502,6 @@ impl KeymapFile { let mut empty_schema_action_names = vec![]; for (name, action_schema) in action_schemas.into_iter() { - let description = action_schema.as_ref().and_then(|schema| { - schema - .as_object() - .and_then(|obj| obj.get("description")) - .and_then(|v| v.as_str()) - .map(|s| s.to_string()) - }); - let deprecation = if name == NoAction.name() { Some("null") } else { @@ -523,6 +518,7 @@ impl KeymapFile { } else if let Some(new_name) = deprecation { add_deprecation_preferred_name(&mut plain_action, new_name); } + let description = action_documentation.get(name); if let Some(description) = &description { add_description(&mut plain_action, description); } From 1090c47a90c586b397dc3fdd0cd09b530f37e5a0 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Sun, 14 Sep 2025 14:53:07 -0500 Subject: [PATCH 018/721] Move Keymap Editor Table component to UI crate (#38157) Closes #ISSUE Move the data table component created for the Keymap Editor to the UI crate. Additionally includes simplifications to the scrollbar component in UI necessary for the table component to support scrollbar configurations, and a fix for an issue with the table component where when used with the `.row` API instead of `uniform_list` the rows would render on top of each other. Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/keymap_editor/src/keymap_editor.rs | 29 ++--- crates/keymap_editor/src/ui_components/mod.rs | 1 - crates/ui/src/components.rs | 2 + .../src/components/data_table.rs} | 101 +++++++++------- crates/ui/src/components/scrollbar.rs | 110 +++++++----------- 5 files changed, 121 insertions(+), 122 deletions(-) rename crates/{keymap_editor/src/ui_components/table.rs => ui/src/components/data_table.rs} (94%) diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index 51524f92eacbf6227877f1203050bb6f964033b3..8a1e494d09aec024c57fb8acf480e8aed7efff82 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -27,7 +27,8 @@ use settings::{BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAss use ui::{ ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, IconButtonShape, Indicator, Modal, ModalFooter, ModalHeader, ParentElement as _, Render, Section, SharedString, - Styled as _, Tooltip, Window, prelude::*, right_click_menu, + Styled as _, Table, TableColumnWidths, TableInteractionState, TableResizeBehavior, Tooltip, + Window, prelude::*, right_click_menu, }; use ui_input::SingleLineInput; use util::ResultExt; @@ -41,9 +42,8 @@ use zed_actions::OpenKeymapEditor; use crate::{ persistence::KEYBINDING_EDITORS, - ui_components::{ - keystroke_input::{ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording}, - table::{ColumnWidths, ResizeBehavior, Table, TableInteractionState}, + ui_components::keystroke_input::{ + ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording, }, }; @@ -369,7 +369,7 @@ struct KeymapEditor { context_menu: Option<(Entity, Point, Subscription)>, previous_edit: Option, humanized_action_names: HumanizedActionNameCache, - current_widths: Entity>, + current_widths: Entity>, show_hover_menus: bool, /// In order for the JSON LSP to run in the actions arguments editor, we /// require a backing file In order to avoid issues (primarily log spam) @@ -425,7 +425,10 @@ impl KeymapEditor { fn new(workspace: WeakEntity, window: &mut Window, cx: &mut Context) -> Self { let _keymap_subscription = cx.observe_global_in::(window, Self::on_keymap_changed); - let table_interaction_state = TableInteractionState::new(cx); + let table_interaction_state = cx.new(|cx| { + TableInteractionState::new(cx) + .with_custom_scrollbar(ui::Scrollbars::for_settings::()) + }); let keystroke_editor = cx.new(|cx| { let mut keystroke_editor = KeystrokeInput::new(None, window, cx); @@ -496,7 +499,7 @@ impl KeymapEditor { show_hover_menus: true, action_args_temp_dir: None, action_args_temp_dir_worktree: None, - current_widths: cx.new(|cx| ColumnWidths::new(cx)), + current_widths: cx.new(|cx| TableColumnWidths::new(cx)), }; this.on_keymap_changed(window, cx); @@ -1780,12 +1783,12 @@ impl Render for KeymapEditor { ]) .resizable_columns( [ - ResizeBehavior::None, - ResizeBehavior::Resizable, - ResizeBehavior::Resizable, - ResizeBehavior::Resizable, - ResizeBehavior::Resizable, - ResizeBehavior::Resizable, // this column doesn't matter + TableResizeBehavior::None, + TableResizeBehavior::Resizable, + TableResizeBehavior::Resizable, + TableResizeBehavior::Resizable, + TableResizeBehavior::Resizable, + TableResizeBehavior::Resizable, // this column doesn't matter ], &self.current_widths, cx, diff --git a/crates/keymap_editor/src/ui_components/mod.rs b/crates/keymap_editor/src/ui_components/mod.rs index 5d6463a61a21afd5208b75af0362f6f7956f5e56..c093bab554d9e4f3f2f74818d5016a0480053903 100644 --- a/crates/keymap_editor/src/ui_components/mod.rs +++ b/crates/keymap_editor/src/ui_components/mod.rs @@ -1,2 +1 @@ pub mod keystroke_input; -pub mod table; diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index 486673e73354b488753cead1f187a5f7ce7687cc..0b7061581518a16d49c677de10dd62ece65b24b4 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -6,6 +6,7 @@ mod callout; mod chip; mod content_group; mod context_menu; +mod data_table; mod disclosure; mod divider; mod dropdown_menu; @@ -49,6 +50,7 @@ pub use callout::*; pub use chip::*; pub use content_group::*; pub use context_menu::*; +pub use data_table::*; pub use disclosure::*; pub use divider::*; pub use dropdown_menu::*; diff --git a/crates/keymap_editor/src/ui_components/table.rs b/crates/ui/src/components/data_table.rs similarity index 94% rename from crates/keymap_editor/src/ui_components/table.rs rename to crates/ui/src/components/data_table.rs index cb0332c868032724d555f3fc5c57e33eaeda624b..4a1f4939cca2eb85bb7a549d06af1e9ea8cf04d0 100644 --- a/crates/keymap_editor/src/ui_components/table.rs +++ b/crates/ui/src/components/data_table.rs @@ -1,14 +1,12 @@ use std::{ops::Range, rc::Rc}; -use editor::EditorSettings; use gpui::{ AbsoluteLength, AppContext, Context, DefiniteLength, DragMoveEvent, Entity, EntityId, FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, Point, Stateful, UniformListScrollHandle, WeakEntity, transparent_black, uniform_list, }; -use itertools::intersperse_with; -use ui::{ +use crate::{ ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component, ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce, @@ -16,6 +14,7 @@ use ui::{ StyledTypography, Window, WithScrollbar, div, example_group_with_title, h_flex, px, single_example, v_flex, }; +use itertools::intersperse_with; const RESIZE_COLUMN_WIDTH: f32 = 8.0; @@ -56,14 +55,21 @@ impl TableContents { pub struct TableInteractionState { pub focus_handle: FocusHandle, pub scroll_handle: UniformListScrollHandle, + pub custom_scrollbar: Option, } impl TableInteractionState { - pub fn new(cx: &mut App) -> Entity { - cx.new(|cx| Self { + pub fn new(cx: &mut App) -> Self { + Self { focus_handle: cx.focus_handle(), scroll_handle: UniformListScrollHandle::new(), - }) + custom_scrollbar: None, + } + } + + pub fn with_custom_scrollbar(mut self, custom_scrollbar: Scrollbars) -> Self { + self.custom_scrollbar = Some(custom_scrollbar); + self } pub fn scroll_offset(&self) -> Point { @@ -87,9 +93,9 @@ impl TableInteractionState { fn render_resize_handles( &self, column_widths: &[Length; COLS], - resizable_columns: &[ResizeBehavior; COLS], + resizable_columns: &[TableResizeBehavior; COLS], initial_sizes: [DefiniteLength; COLS], - columns: Option>>, + columns: Option>>, window: &mut Window, cx: &mut App, ) -> AnyElement { @@ -121,7 +127,7 @@ impl TableInteractionState { if resizable_columns .next() - .is_some_and(ResizeBehavior::is_resizable) + .is_some_and(TableResizeBehavior::is_resizable) { let hovered = window.use_state(cx, |_window, _cx| false); @@ -169,34 +175,34 @@ impl TableInteractionState { } #[derive(Debug, Copy, Clone, PartialEq)] -pub enum ResizeBehavior { +pub enum TableResizeBehavior { None, Resizable, MinSize(f32), } -impl ResizeBehavior { +impl TableResizeBehavior { pub fn is_resizable(&self) -> bool { - *self != ResizeBehavior::None + *self != TableResizeBehavior::None } pub fn min_size(&self) -> Option { match self { - ResizeBehavior::None => None, - ResizeBehavior::Resizable => Some(0.05), - ResizeBehavior::MinSize(min_size) => Some(*min_size), + TableResizeBehavior::None => None, + TableResizeBehavior::Resizable => Some(0.05), + TableResizeBehavior::MinSize(min_size) => Some(*min_size), } } } -pub struct ColumnWidths { +pub struct TableColumnWidths { widths: [DefiniteLength; COLS], visible_widths: [DefiniteLength; COLS], cached_bounds_width: Pixels, initialized: bool, } -impl ColumnWidths { +impl TableColumnWidths { pub fn new(_: &mut App) -> Self { Self { widths: [DefiniteLength::default(); COLS], @@ -220,7 +226,7 @@ impl ColumnWidths { &mut self, double_click_position: usize, initial_sizes: &[DefiniteLength; COLS], - resize_behavior: &[ResizeBehavior; COLS], + resize_behavior: &[TableResizeBehavior; COLS], window: &mut Window, ) { let bounds_width = self.cached_bounds_width; @@ -245,7 +251,7 @@ impl ColumnWidths { col_idx: usize, mut widths: [f32; COLS], initial_sizes: [f32; COLS], - resize_behavior: &[ResizeBehavior; COLS], + resize_behavior: &[TableResizeBehavior; COLS], ) -> [f32; COLS] { // RESET: // Part 1: @@ -331,7 +337,7 @@ impl ColumnWidths { fn on_drag_move( &mut self, drag_event: &DragMoveEvent, - resize_behavior: &[ResizeBehavior; COLS], + resize_behavior: &[TableResizeBehavior; COLS], window: &mut Window, cx: &mut Context, ) { @@ -376,7 +382,7 @@ impl ColumnWidths { diff: f32, col_idx: usize, widths: &mut [f32; COLS], - resize_behavior: &[ResizeBehavior; COLS], + resize_behavior: &[TableResizeBehavior; COLS], ) { // if diff > 0.0 then go right if diff > 0.0 { @@ -390,7 +396,7 @@ impl ColumnWidths { diff: f32, col_idx: usize, widths: &mut [f32; COLS], - resize_behavior: &[ResizeBehavior; COLS], + resize_behavior: &[TableResizeBehavior; COLS], direction: i8, ) -> f32 { let mut diff_remaining = diff; @@ -446,8 +452,8 @@ impl ColumnWidths { pub struct TableWidths { initial: [DefiniteLength; COLS], - current: Option>>, - resizable: [ResizeBehavior; COLS], + current: Option>>, + resizable: [TableResizeBehavior; COLS], } impl TableWidths { @@ -457,7 +463,7 @@ impl TableWidths { TableWidths { initial: widths, current: None, - resizable: [ResizeBehavior::None; COLS], + resizable: [TableResizeBehavior::None; COLS], } } @@ -563,8 +569,8 @@ impl Table { pub fn resizable_columns( mut self, - resizable: [ResizeBehavior; COLS], - column_widths: &Entity>, + resizable: [TableResizeBehavior; COLS], + column_widths: &Entity>, cx: &mut App, ) -> Self { if let Some(table_widths) = self.col_widths.as_mut() { @@ -616,7 +622,7 @@ fn base_cell_style_text(width: Option, cx: &App) -> Div { base_cell_style(width).text_ui(cx) } -pub fn render_row( +pub fn render_table_row( row_index: usize, items: [impl IntoElement; COLS], table_context: TableRenderContext, @@ -663,12 +669,12 @@ pub fn render_row( div().size_full().child(row).into_any_element() } -pub fn render_header( +pub fn render_table_header( headers: [impl IntoElement; COLS], table_context: TableRenderContext, columns_widths: Option<( - WeakEntity>, - [ResizeBehavior; COLS], + WeakEntity>, + [TableResizeBehavior; COLS], [DefiniteLength; COLS], )>, entity_id: Option, @@ -771,7 +777,7 @@ impl RenderOnce for Table { .h_full() .v_flex() .when_some(self.headers.take(), |this, headers| { - this.child(render_header( + this.child(render_table_header( headers, table_context.clone(), current_widths_with_initial_sizes, @@ -822,7 +828,13 @@ impl RenderOnce for Table { .map(|parent| match self.rows { TableContents::Vec(items) => { parent.children(items.into_iter().enumerate().map(|(index, row)| { - render_row(index, row, table_context.clone(), window, cx) + div().child(render_table_row( + index, + row, + table_context.clone(), + window, + cx, + )) })) } TableContents::UniformList(uniform_list_data) => parent.child( @@ -837,7 +849,7 @@ impl RenderOnce for Table { .into_iter() .zip(range) .map(|(row, row_index)| { - render_row( + render_table_row( row_index, row, table_context.clone(), @@ -888,10 +900,14 @@ impl RenderOnce for Table { ); if let Some(state) = interaction_state.as_ref() { + let scrollbars = state + .read(cx) + .custom_scrollbar + .clone() + .unwrap_or_else(|| Scrollbars::new(super::ScrollAxes::Both)); content .custom_scrollbars( - Scrollbars::for_settings::() - .tracked_scroll_handle(state.read(cx).scroll_handle.clone()), + scrollbars.tracked_scroll_handle(state.read(cx).scroll_handle.clone()), window, cx, ) @@ -1055,14 +1071,15 @@ mod test { fn parse_resize_behavior( input: &str, total_size: f32, - ) -> [ResizeBehavior; COLS] { - let mut resize_behavior = [ResizeBehavior::None; COLS]; + ) -> [TableResizeBehavior; COLS] { + let mut resize_behavior = [TableResizeBehavior::None; COLS]; let mut max_index = 0; for (index, col) in input.split('|').enumerate() { if col.starts_with('X') || col.is_empty() { - resize_behavior[index] = ResizeBehavior::None; + resize_behavior[index] = TableResizeBehavior::None; } else if col.starts_with('*') { - resize_behavior[index] = ResizeBehavior::MinSize(col.len() as f32 / total_size); + resize_behavior[index] = + TableResizeBehavior::MinSize(col.len() as f32 / total_size); } else { panic!("invalid test input: unrecognized resize behavior: {}", col); } @@ -1123,7 +1140,7 @@ mod test { "invalid test input: total width not the same" ); let resize_behavior = parse_resize_behavior::(resize_behavior, total_1); - let result = ColumnWidths::reset_to_initial_size( + let result = TableColumnWidths::reset_to_initial_size( column_index, widths, initial_sizes, @@ -1301,7 +1318,7 @@ mod test { let distance = distance as f32 / total_1; - let result = ColumnWidths::drag_column_handle( + let result = TableColumnWidths::drag_column_handle( distance, column_index, &mut widths, diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index e4c2937be775eed4d9ae673da763e1eda5a65419..f3cc37d2f55356f05af3f1644dc4292a6add2660 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -1,4 +1,4 @@ -use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Not, time::Duration}; +use std::{any::Any, fmt::Debug, ops::Not, time::Duration}; use gpui::{ Along, App, AppContext as _, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Context, @@ -68,22 +68,10 @@ pub mod scrollbars { } } - impl GlobalSetting for ShowScrollbar { - fn get_value(_cx: &App) -> &Self { - &ShowScrollbar::Always - } - } - pub trait ScrollbarVisibility: GlobalSetting + 'static { fn visibility(&self, cx: &App) -> ShowScrollbar; } - impl ScrollbarVisibility for ShowScrollbar { - fn visibility(&self, cx: &App) -> ShowScrollbar { - *ShowScrollbar::get_value(cx) - } - } - #[derive(Default)] pub struct ScrollbarAutoHide(pub bool); @@ -96,14 +84,13 @@ pub mod scrollbars { impl Global for ScrollbarAutoHide {} } -fn get_scrollbar_state( - mut config: Scrollbars, +fn get_scrollbar_state( + mut config: Scrollbars, caller_location: &'static std::panic::Location, window: &mut Window, cx: &mut App, -) -> Entity> +) -> Entity> where - S: ScrollbarVisibility, T: ScrollableHandle, { let element_id = config.id.take().unwrap_or_else(|| caller_location.into()); @@ -119,14 +106,13 @@ where pub trait WithScrollbar: Sized { type Output; - fn custom_scrollbars( + fn custom_scrollbars( self, - config: Scrollbars, + config: Scrollbars, window: &mut Window, cx: &mut App, ) -> Self::Output where - S: ScrollbarVisibility, T: ScrollableHandle; #[track_caller] @@ -168,14 +154,13 @@ impl WithScrollbar for Stateful
{ type Output = Self; #[track_caller] - fn custom_scrollbars( + fn custom_scrollbars( self, - config: Scrollbars, + config: Scrollbars, window: &mut Window, cx: &mut App, ) -> Self::Output where - S: ScrollbarVisibility, T: ScrollableHandle, { render_scrollbar( @@ -190,14 +175,13 @@ impl WithScrollbar for Div { type Output = Stateful
; #[track_caller] - fn custom_scrollbars( + fn custom_scrollbars( self, - config: Scrollbars, + config: Scrollbars, window: &mut Window, cx: &mut App, ) -> Self::Output where - S: ScrollbarVisibility, T: ScrollableHandle, { let scrollbar = get_scrollbar_state(config, std::panic::Location::caller(), window, cx); @@ -213,13 +197,12 @@ impl WithScrollbar for Div { } } -fn render_scrollbar( - scrollbar: Entity>, +fn render_scrollbar( + scrollbar: Entity>, div: Stateful
, cx: &App, ) -> Stateful
where - S: ScrollbarVisibility, T: ScrollableHandle, { let state = &scrollbar.read(cx).0; @@ -247,9 +230,7 @@ where .child(state.clone()) } -impl UniformListDecoration - for ScrollbarStateWrapper -{ +impl UniformListDecoration for ScrollbarStateWrapper { fn compute( &self, _visible_range: Range, @@ -334,7 +315,7 @@ impl ReservedSpace { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, Copy)] enum ScrollbarWidth { #[default] Normal, @@ -352,11 +333,12 @@ impl ScrollbarWidth { } } -pub struct Scrollbars { +#[derive(Clone)] +pub struct Scrollbars { id: Option, - tracked_setting: PhantomData, + get_visibility: fn(&App) -> ShowScrollbar, tracked_entity: Option>, - scrollable_handle: Box T>, + scrollable_handle: T, handle_was_added: bool, visibility: Point, scrollbar_width: ScrollbarWidth, @@ -364,21 +346,21 @@ pub struct Scrollbars Self { - Self::new_with_setting(show_along) + Self::new_with_setting(show_along, |_| ShowScrollbar::Always) } - pub fn for_settings() -> Scrollbars { - Scrollbars::::new_with_setting(ScrollAxes::Both) + pub fn for_settings() -> Scrollbars { + Scrollbars::new_with_setting(ScrollAxes::Both, |cx| S::get_value(cx).visibility(cx)) } } -impl Scrollbars { - fn new_with_setting(show_along: ScrollAxes) -> Self { +impl Scrollbars { + fn new_with_setting(show_along: ScrollAxes, get_visibility: fn(&App) -> ShowScrollbar) -> Self { Self { id: None, - tracked_setting: PhantomData, + get_visibility, handle_was_added: false, - scrollable_handle: Box::new(ScrollHandle::new), + scrollable_handle: ScrollHandle::new(), tracked_entity: None, visibility: show_along.apply_to(Default::default(), ReservedSpace::Thumb), scrollbar_width: ScrollbarWidth::Normal, @@ -386,9 +368,7 @@ impl Scrollbars { } } -impl - Scrollbars -{ +impl Scrollbars { pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self @@ -416,24 +396,24 @@ impl pub fn tracked_scroll_handle( self, tracked_scroll_handle: TrackedHandle, - ) -> Scrollbars { + ) -> Scrollbars { let Self { id, - tracked_setting, tracked_entity: tracked_entity_id, scrollbar_width, visibility, + get_visibility, .. } = self; Scrollbars { handle_was_added: true, - scrollable_handle: Box::new(|| tracked_scroll_handle), + scrollable_handle: tracked_scroll_handle, id, - tracked_setting, tracked_entity: tracked_entity_id, visibility, scrollbar_width, + get_visibility, } } @@ -491,19 +471,17 @@ enum ParentHovered { /// This is used to ensure notifies within the state do not notify the parent /// unintentionally. -struct ScrollbarStateWrapper( - Entity>, -); +struct ScrollbarStateWrapper(Entity>); /// A scrollbar state that should be persisted across frames. -struct ScrollbarState { +struct ScrollbarState { thumb_state: ThumbState, notify_id: Option, manually_added: bool, scroll_handle: T, width: ScrollbarWidth, - tracked_setting: PhantomData, show_setting: ShowScrollbar, + get_visibility: fn(&App) -> ShowScrollbar, visibility: Point, show_state: VisibilityState, mouse_in_parent: bool, @@ -511,9 +489,9 @@ struct ScrollbarState>, } -impl ScrollbarState { +impl ScrollbarState { fn new_from_config( - config: Scrollbars, + config: Scrollbars, parent_id: EntityId, window: &mut Window, cx: &mut Context, @@ -521,16 +499,16 @@ impl ScrollbarState { cx.observe_global_in::(window, Self::settings_changed) .detach(); - let show_setting = S::get_value(cx).visibility(cx); + let show_setting = (config.get_visibility)(cx); ScrollbarState { thumb_state: Default::default(), notify_id: config.tracked_entity.map(|id| id.unwrap_or(parent_id)), manually_added: config.handle_was_added, - scroll_handle: (config.scrollable_handle)(), + scroll_handle: config.scrollable_handle, width: config.scrollbar_width, visibility: config.visibility, - tracked_setting: PhantomData, show_setting, + get_visibility: config.get_visibility, show_state: VisibilityState::from_show_setting(show_setting), mouse_in_parent: true, last_prepaint_state: None, @@ -539,7 +517,7 @@ impl ScrollbarState { } fn settings_changed(&mut self, window: &mut Window, cx: &mut Context) { - self.set_show_scrollbar(S::get_value(cx).visibility(cx), window, cx); + self.set_show_scrollbar((self.get_visibility)(cx), window, cx); } /// Schedules a scrollbar auto hide if no auto hide is currently in progress yet. @@ -754,7 +732,7 @@ impl ScrollbarState { } } -impl Render for ScrollbarState { +impl Render for ScrollbarState { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { ScrollbarElement { state: cx.entity(), @@ -763,9 +741,9 @@ impl Render for ScrollbarState { +struct ScrollbarElement { origin: Point, - state: Entity>, + state: Entity>, } #[derive(Default, Debug, PartialEq, Eq)] @@ -945,7 +923,7 @@ impl PartialEq for ScrollbarPrepaintState { } } -impl Element for ScrollbarElement { +impl Element for ScrollbarElement { type RequestLayoutState = (); type PrepaintState = Option; @@ -1266,7 +1244,7 @@ impl Element for ScrollbarElement IntoElement for ScrollbarElement { +impl IntoElement for ScrollbarElement { type Element = Self; fn into_element(self) -> Self::Element { From 98edf1bf0b6e1b6ec62986d370763920dde8b890 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sun, 14 Sep 2025 21:36:24 -0600 Subject: [PATCH 019/721] Reload API keys when URLs configured for LLM providers change (#38163) Three motivations for this: * Changing provider URL could cause credentials for the prior URL to be sent to the new URL. * The UI is in a misleading state after URL change - it shows a configured API key, but on restart it will show no API key. * #34110 will add support for both URL and key configuration for Ollama. This is the first provider to have UI for setting the URL, and this makes these issues show up more directly as odd UI interactions. #37610 implemented something similar for the OpenAI and OpenAI compatible providers. This extracts out some shared code, uses it in all relevant providers, and adds more safety around key use. I haven't tested all providers, but the per-provider changes were pretty mechanical, so hopefully work properly. Release Notes: - Fixed handling of changes to LLM provider URL in settings to also load the associated API key. --- Cargo.lock | 2 + crates/agent_servers/src/gemini.rs | 8 +- crates/language_models/Cargo.toml | 1 + crates/language_models/src/api_key.rs | 295 ++++++++++++++++++ crates/language_models/src/language_models.rs | 1 + .../language_models/src/provider/anthropic.rs | 186 ++++------- .../language_models/src/provider/deepseek.rs | 183 +++++------ crates/language_models/src/provider/google.rs | 219 ++++++------- .../language_models/src/provider/mistral.rs | 181 +++++------ .../language_models/src/provider/open_ai.rs | 225 +++++-------- .../src/provider/open_ai_compatible.rs | 183 ++++------- .../src/provider/open_router.rs | 192 +++++------- crates/language_models/src/provider/vercel.rs | 201 +++++------- crates/language_models/src/provider/x_ai.rs | 219 +++++-------- crates/zed_env_vars/Cargo.toml | 1 + crates/zed_env_vars/src/zed_env_vars.rs | 42 ++- 16 files changed, 1030 insertions(+), 1109 deletions(-) create mode 100644 crates/language_models/src/api_key.rs diff --git a/Cargo.lock b/Cargo.lock index db61ba7b121773c6e6941dd940201ec0a9e82f73..22589ee11a4ffb657238091ab85a3e76d9b6bf32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9212,6 +9212,7 @@ dependencies = [ "vercel", "workspace-hack", "x_ai", + "zed_env_vars", ] [[package]] @@ -20677,6 +20678,7 @@ dependencies = [ name = "zed_env_vars" version = "0.1.0" dependencies = [ + "gpui", "workspace-hack", ] diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index 01f15557899e1c7826e91d1555320996eccd0f45..6a668785943239f28b4bc9aafce9d37fdfa386d2 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -44,8 +44,12 @@ impl AgentServer for Gemini { cx.spawn(async move |cx| { let mut extra_env = HashMap::default(); - if let Some(api_key) = cx.update(GoogleLanguageModelProvider::api_key)?.await.ok() { - extra_env.insert("GEMINI_API_KEY".into(), api_key.key); + if let Some(api_key) = cx + .update(GoogleLanguageModelProvider::api_key_for_gemini_cli)? + .await + .ok() + { + extra_env.insert("GEMINI_API_KEY".into(), api_key); } let (mut command, root_dir, login) = store .update(cx, |store, cx| { diff --git a/crates/language_models/Cargo.toml b/crates/language_models/Cargo.toml index b5bfb870f643452bd5be248c9910d99f16a8101e..7dc0988d23579c4d1ab1ac2dde1f1413c5e751b8 100644 --- a/crates/language_models/Cargo.toml +++ b/crates/language_models/Cargo.toml @@ -61,6 +61,7 @@ util.workspace = true vercel = { workspace = true, features = ["schemars"] } workspace-hack.workspace = true x_ai = { workspace = true, features = ["schemars"] } +zed_env_vars.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/language_models/src/api_key.rs b/crates/language_models/src/api_key.rs new file mode 100644 index 0000000000000000000000000000000000000000..122234b6ced6d0bf1b7a0d684683c841824ccd2d --- /dev/null +++ b/crates/language_models/src/api_key.rs @@ -0,0 +1,295 @@ +use anyhow::{Result, anyhow}; +use credentials_provider::CredentialsProvider; +use futures::{FutureExt, future}; +use gpui::{AsyncApp, Context, SharedString, Task}; +use language_model::AuthenticateError; +use std::{ + fmt::{Display, Formatter}, + sync::Arc, +}; +use util::ResultExt as _; +use zed_env_vars::EnvVar; + +/// Manages a single API key for a language model provider. API keys either come from environment +/// variables or the system keychain. +/// +/// Keys from the system keychain are associated with a provider URL, and this ensures that they are +/// only used with that URL. +pub struct ApiKeyState { + url: SharedString, + load_status: LoadStatus, + load_task: Option>>, +} + +#[derive(Debug, Clone)] +pub enum LoadStatus { + NotPresent, + Error(String), + Loaded(ApiKey), +} + +#[derive(Debug, Clone)] +pub struct ApiKey { + source: ApiKeySource, + key: Arc, +} + +impl ApiKeyState { + pub fn new(url: SharedString) -> Self { + Self { + url, + load_status: LoadStatus::NotPresent, + load_task: None, + } + } + + pub fn has_key(&self) -> bool { + matches!(self.load_status, LoadStatus::Loaded { .. }) + } + + pub fn is_from_env_var(&self) -> bool { + match &self.load_status { + LoadStatus::Loaded(ApiKey { + source: ApiKeySource::EnvVar { .. }, + .. + }) => true, + _ => false, + } + } + + /// Get the stored API key, verifying that it is associated with the URL. Returns `None` if + /// there is no key or for URL mismatches, and the mismatch case is logged. + /// + /// To avoid URL mismatches, expects that `load_if_needed` or `handle_url_change` has been + /// called with this URL. + pub fn key(&self, url: &str) -> Option> { + let api_key = match &self.load_status { + LoadStatus::Loaded(api_key) => api_key, + _ => return None, + }; + if url == self.url.as_str() { + Some(api_key.key.clone()) + } else if let ApiKeySource::EnvVar(var_name) = &api_key.source { + log::warn!( + "{} is now being used with URL {}, when initially it was used with URL {}", + var_name, + url, + self.url + ); + Some(api_key.key.clone()) + } else { + // bug case because load_if_needed should be called whenever the url may have changed + log::error!( + "bug: Attempted to use API key associated with URL {} instead with URL {}", + self.url, + url + ); + None + } + } + + /// Set or delete the API key in the system keychain. + pub fn store( + &mut self, + url: SharedString, + key: Option, + get_this: impl Fn(&mut Ent) -> &mut Self + 'static, + cx: &Context, + ) -> Task> { + if self.is_from_env_var() { + return Task::ready(Err(anyhow!( + "bug: attempted to store API key in system keychain when API key is from env var", + ))); + } + let credentials_provider = ::global(cx); + cx.spawn(async move |ent, cx| { + if let Some(key) = &key { + credentials_provider + .write_credentials(&url, "Bearer", key.as_bytes(), cx) + .await + .log_err(); + } else { + credentials_provider + .delete_credentials(&url, cx) + .await + .log_err(); + } + ent.update(cx, |ent, cx| { + let this = get_this(ent); + this.url = url; + this.load_status = match &key { + Some(key) => LoadStatus::Loaded(ApiKey { + source: ApiKeySource::SystemKeychain, + key: key.as_str().into(), + }), + None => LoadStatus::NotPresent, + }; + cx.notify(); + }) + }) + } + + /// Reloads the API key if the current API key is associated with a different URL. + /// + /// Note that it is not efficient to use this or `load_if_needed` with multiple URLs + /// interchangeably - URL change should correspond to some user initiated change. + pub fn handle_url_change( + &mut self, + url: SharedString, + env_var: &EnvVar, + get_this: impl Fn(&mut Ent) -> &mut Self + Clone + 'static, + cx: &mut Context, + ) { + if url != self.url { + if !self.is_from_env_var() { + // loading will continue even though this result task is dropped + let _task = self.load_if_needed(url, env_var, get_this, cx); + } + } + } + + /// If needed, loads the API key associated with the given URL from the system keychain. When a + /// non-empty environment variable is provided, it will be used instead. If called when an API + /// key was already loaded for a different URL, that key will be cleared before loading. + /// + /// Dropping the returned Task does not cancel key loading. + pub fn load_if_needed( + &mut self, + url: SharedString, + env_var: &EnvVar, + get_this: impl Fn(&mut Ent) -> &mut Self + Clone + 'static, + cx: &mut Context, + ) -> Task> { + if let LoadStatus::Loaded { .. } = &self.load_status + && self.url == url + { + return Task::ready(Ok(())); + } + + if let Some(key) = &env_var.value + && !key.is_empty() + { + let api_key = ApiKey::from_env(env_var.name.clone(), key); + self.url = url; + self.load_status = LoadStatus::Loaded(api_key); + self.load_task = None; + cx.notify(); + return Task::ready(Ok(())); + } + + let task = if let Some(load_task) = &self.load_task { + load_task.clone() + } else { + let load_task = Self::load(url.clone(), get_this.clone(), cx).shared(); + self.url = url; + self.load_status = LoadStatus::NotPresent; + self.load_task = Some(load_task.clone()); + cx.notify(); + load_task + }; + + cx.spawn(async move |ent, cx| { + task.await; + ent.update(cx, |ent, _cx| { + get_this(ent).load_status.clone().into_authenticate_result() + }) + .ok(); + Ok(()) + }) + } + + fn load( + url: SharedString, + get_this: impl Fn(&mut Ent) -> &mut Self + 'static, + cx: &Context, + ) -> Task<()> { + let credentials_provider = ::global(cx); + cx.spawn({ + async move |ent, cx| { + let load_status = + ApiKey::load_from_system_keychain_impl(&url, credentials_provider.as_ref(), cx) + .await; + ent.update(cx, |ent, cx| { + let this = get_this(ent); + this.url = url; + this.load_status = load_status; + this.load_task = None; + cx.notify(); + }) + .ok(); + } + }) + } +} + +impl ApiKey { + pub fn key(&self) -> &str { + &self.key + } + + pub fn from_env(env_var_name: SharedString, key: &str) -> Self { + Self { + source: ApiKeySource::EnvVar(env_var_name), + key: key.into(), + } + } + + pub async fn load_from_system_keychain( + url: &str, + credentials_provider: &dyn CredentialsProvider, + cx: &AsyncApp, + ) -> Result { + Self::load_from_system_keychain_impl(url, credentials_provider, cx) + .await + .into_authenticate_result() + } + + async fn load_from_system_keychain_impl( + url: &str, + credentials_provider: &dyn CredentialsProvider, + cx: &AsyncApp, + ) -> LoadStatus { + if url.is_empty() { + return LoadStatus::NotPresent; + } + let read_result = credentials_provider.read_credentials(&url, cx).await; + let api_key = match read_result { + Ok(Some((_, api_key))) => api_key, + Ok(None) => return LoadStatus::NotPresent, + Err(err) => return LoadStatus::Error(err.to_string()), + }; + let key = match str::from_utf8(&api_key) { + Ok(key) => key, + Err(_) => return LoadStatus::Error(format!("API key for URL {url} is not utf8")), + }; + LoadStatus::Loaded(Self { + source: ApiKeySource::SystemKeychain, + key: key.into(), + }) + } +} + +impl LoadStatus { + fn into_authenticate_result(self) -> Result { + match self { + LoadStatus::Loaded(api_key) => Ok(api_key), + LoadStatus::NotPresent => Err(AuthenticateError::CredentialsNotFound), + LoadStatus::Error(err) => Err(AuthenticateError::Other(anyhow!(err))), + } + } +} + +#[derive(Debug, Clone)] +enum ApiKeySource { + EnvVar(SharedString), + SystemKeychain, +} + +impl Display for ApiKeySource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ApiKeySource::EnvVar(var) => write!(f, "environment variable {}", var), + ApiKeySource::SystemKeychain => write!(f, "system keychain"), + } + } +} diff --git a/crates/language_models/src/language_models.rs b/crates/language_models/src/language_models.rs index 738b72b0c9a6dbb7c9606cc72707b27e66abf09c..61e1a794695310421397469515a43a4d5bf5deb8 100644 --- a/crates/language_models/src/language_models.rs +++ b/crates/language_models/src/language_models.rs @@ -7,6 +7,7 @@ use gpui::{App, Context, Entity}; use language_model::{LanguageModelProviderId, LanguageModelRegistry}; use provider::deepseek::DeepSeekLanguageModelProvider; +mod api_key; pub mod provider; mod settings; pub mod ui; diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index ca7763e2c5cda3c07c5cb51389cb3173a55865e2..f9c664182426dfd9523254867ba2f95b8f3dc7c4 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -1,18 +1,15 @@ -use crate::AllLanguageModelSettings; use crate::ui::InstructionListItem; +use crate::{AllLanguageModelSettings, api_key::ApiKeyState}; use anthropic::{ AnthropicError, AnthropicModelMode, ContentDelta, Event, ResponseContent, ToolResultContent, ToolResultPart, Usage, }; -use anyhow::{Context as _, Result, anyhow}; +use anyhow::Result; use collections::{BTreeMap, HashMap}; -use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; use futures::Stream; use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream}; -use gpui::{ - AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, -}; +use gpui::{AnyView, App, AsyncApp, Context, Entity, FontStyle, Task, TextStyle, WhiteSpace}; use http_client::HttpClient; use language_model::{ AuthenticateError, ConfigurationViewTargetAgent, LanguageModel, @@ -27,11 +24,12 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; use util::ResultExt; +use zed_env_vars::{EnvVar, env_var}; const PROVIDER_ID: LanguageModelProviderId = language_model::ANTHROPIC_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::ANTHROPIC_PROVIDER_NAME; @@ -97,91 +95,52 @@ pub struct AnthropicLanguageModelProvider { state: gpui::Entity, } -const ANTHROPIC_API_KEY_VAR: &str = "ANTHROPIC_API_KEY"; +const API_KEY_ENV_VAR_NAME: &str = "ANTHROPIC_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); pub struct State { - api_key: Option, - api_key_from_env: bool, - _subscription: Subscription, + api_key_state: ApiKeyState, } impl State { - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .anthropic - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .ok(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) - } - - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .anthropic - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await - .ok(); - - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) - } - fn is_authenticated(&self) -> bool { - self.api_key.is_some() + self.api_key_state.has_key() } - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let key = AnthropicLanguageModelProvider::api_key(cx); - - cx.spawn(async move |this, cx| { - let key = key.await?; - - this.update(cx, |this, cx| { - this.api_key = Some(key.key); - this.api_key_from_env = key.from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = AnthropicLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } -} -pub struct ApiKey { - pub key: String, - pub from_env: bool, + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = AnthropicLanguageModelProvider::api_url(cx); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) + } } impl AnthropicLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|_, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = Self::api_url(cx); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + } }); Self { http_client, state } @@ -197,30 +156,16 @@ impl AnthropicLanguageModelProvider { }) } - pub fn api_key(cx: &mut App) -> Task> { - let credentials_provider = ::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, - })) + fn settings(cx: &App) -> &AnthropicSettings { + &AllLanguageModelSettings::get_global(cx).anthropic + } + + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + anthropic::ANTHROPIC_API_URL.into() } 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, - }) - }) + SharedString::new(api_url.as_str()) } } } @@ -327,7 +272,8 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -417,11 +363,16 @@ impl AnthropicModel { > { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).anthropic; - (state.api_key.clone(), settings.api_url.clone()) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped").into())).boxed(); + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = AnthropicLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err.into())).boxed(); + } }; let beta_headers = self.model.beta_headers(); @@ -483,7 +434,10 @@ impl LanguageModel for AnthropicModel { } fn api_key(&self, cx: &App) -> Option { - self.state.read(cx).api_key.clone() + self.state.read_with(cx, |state, cx| { + let api_url = AnthropicLanguageModelProvider::api_url(cx); + state.api_key_state.key(&api_url).map(|key| key.to_string()) + }) } fn max_token_count(&self) -> u64 { @@ -987,12 +941,10 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -1001,11 +953,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn render_api_key_editor(&self, cx: &mut Context) -> impl IntoElement { @@ -1040,7 +992,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); if self.load_credentials_task.is_some() { div().child(Label::new("Loading credentials...")).into_any() @@ -1079,7 +1031,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - format!("You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed."), + format!("You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."), ) .size(LabelSize::Small) .color(Color::Muted), @@ -1099,7 +1051,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {ANTHROPIC_API_KEY_VAR} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -1112,7 +1064,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .disabled(env_var_set) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, unset the {ANTHROPIC_API_KEY_VAR} environment variable."))) + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index 82bf067cd475fe031630767da9e4302afa4d78ec..e00d8bbf4b34560bdc55c982114bf96675e22d99 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -1,12 +1,11 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; -use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; use futures::Stream; use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream}; use gpui::{ - AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle, - WhiteSpace, + AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, + Window, }; use http_client::HttpClient; use language_model::{ @@ -21,16 +20,19 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use theme::ThemeSettings; use ui::{Icon, IconName, List, prelude::*}; use util::ResultExt; +use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, ui::InstructionListItem}; +use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("deepseek"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("DeepSeek"); -const DEEPSEEK_API_KEY_VAR: &str = "DEEPSEEK_API_KEY"; + +const API_KEY_ENV_VAR_NAME: &str = "DEEPSEEK_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); #[derive(Default)] struct RawToolCall { @@ -59,95 +61,48 @@ pub struct DeepSeekLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, - _subscription: Subscription, + api_key_state: ApiKeyState, } impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() - } - - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .deepseek - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) + self.api_key_state.has_key() } - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .deepseek - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await?; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx)); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .deepseek - .api_url - .clone(); - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_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, - ) - }; - - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx)); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) } } impl DeepSeekLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|_this: &mut State, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = SharedString::new(Self::api_url(cx)); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(SharedString::new(Self::api_url(cx))), + } }); Self { http_client, state } @@ -160,7 +115,15 @@ impl DeepSeekLanguageModelProvider { state: self.state.clone(), http_client: self.http_client.clone(), request_limiter: RateLimiter::new(4), - }) as Arc + }) + } + + fn settings(cx: &App) -> &DeepSeekSettings { + &AllLanguageModelSettings::get_global(cx).deepseek + } + + fn api_url(cx: &App) -> &str { + &Self::settings(cx).api_url } } @@ -199,11 +162,7 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider { models.insert("deepseek-chat", deepseek::Model::Chat); models.insert("deepseek-reasoner", deepseek::Model::Reasoner); - for available_model in AllLanguageModelSettings::get_global(cx) - .deepseek - .available_models - .iter() - { + for available_model in &Self::settings(cx).available_models { models.insert( &available_model.name, deepseek::Model::Custom { @@ -240,7 +199,8 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -259,15 +219,25 @@ impl DeepSeekLanguageModel { cx: &AsyncApp, ) -> BoxFuture<'static, Result>>> { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).deepseek; - (state.api_key.clone(), settings.api_url.clone()) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = DeepSeekLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(api_url); + (api_key, api_url.to_string()) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; let future = self.request_limiter.stream(async move { - let api_key = api_key.context("Missing DeepSeek API Key")?; + let Some(api_key) = api_key else { + return Err(LanguageModelCompletionError::NoApiKey { + provider: PROVIDER_NAME, + }); + }; let request = deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request); let response = request.await?; @@ -610,7 +580,7 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context) { - let api_key = self.api_key_editor.read(cx).text(cx); + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); if api_key.is_empty() { return; } @@ -618,12 +588,10 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn(async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -631,10 +599,12 @@ impl ConfigurationView { .update(cx, |editor, cx| editor.set_text("", window, cx)); let state = self.state.clone(); - cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await) - .detach_and_log_err(cx); - - cx.notify(); + cx.spawn(async move |_, cx| { + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await + }) + .detach_and_log_err(cx); } fn render_api_key_editor(&self, cx: &mut Context) -> impl IntoElement { @@ -672,7 +642,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); if self.load_credentials_task.is_some() { div().child(Label::new("Loading credentials...")).into_any() @@ -706,8 +676,7 @@ impl Render for ConfigurationView { ) .child( Label::new(format!( - "Or set the {} environment variable.", - DEEPSEEK_API_KEY_VAR + "Or set the {API_KEY_ENV_VAR_NAME} environment variable." )) .size(LabelSize::Small) .color(Color::Muted), @@ -727,7 +696,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {}", DEEPSEEK_API_KEY_VAR) + format!("API key set in {API_KEY_ENV_VAR_NAME}") } else { "API key configured".to_string() })), diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index 939cf0ca60d92d713b90a5d62e8ec7f6dac7ec46..677d5775b3854245ad441acf37b0e1ed02f6bab5 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -1,4 +1,4 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Context as _, Result}; use collections::BTreeMap; use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; @@ -8,7 +8,8 @@ use google_ai::{ ThinkingConfig, UsageMetadata, }; use gpui::{ - AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, + AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, + Window, }; use http_client::HttpClient; use language_model::{ @@ -26,18 +27,18 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::sync::{ - Arc, + Arc, LazyLock, atomic::{self, AtomicU64}, }; use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; use util::ResultExt; +use zed_env_vars::EnvVar; -use crate::AllLanguageModelSettings; +use crate::api_key::ApiKey; use crate::ui::InstructionListItem; - -use super::anthropic::ApiKey; +use crate::{AllLanguageModelSettings, api_key::ApiKeyState}; const PROVIDER_ID: LanguageModelProviderId = language_model::GOOGLE_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::GOOGLE_PROVIDER_NAME; @@ -91,101 +92,56 @@ pub struct GoogleLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, - _subscription: Subscription, + api_key_state: ApiKeyState, } -const GEMINI_API_KEY_VAR: &str = "GEMINI_API_KEY"; -const GOOGLE_AI_API_KEY_VAR: &str = "GOOGLE_AI_API_KEY"; +const GEMINI_API_KEY_VAR_NAME: &str = "GEMINI_API_KEY"; +const GOOGLE_AI_API_KEY_VAR_NAME: &str = "GOOGLE_AI_API_KEY"; + +static API_KEY_ENV_VAR: LazyLock = LazyLock::new(|| { + // Try GEMINI_API_KEY first as primary, fallback to GOOGLE_AI_API_KEY + EnvVar::new(GEMINI_API_KEY_VAR_NAME.into()).or(EnvVar::new(GOOGLE_AI_API_KEY_VAR_NAME.into())) +}); impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() + self.api_key_state.has_key() } - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .google - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = GoogleLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .google - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await?; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) - } - - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .google - .api_url - .clone(); - - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(GOOGLE_AI_API_KEY_VAR) { - (api_key, true) - } else if let Ok(api_key) = std::env::var(GEMINI_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, - ) - }; - - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = GoogleLanguageModelProvider::api_url(cx); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) } } impl GoogleLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|_, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = Self::api_url(cx); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + } }); Self { http_client, state } @@ -201,30 +157,28 @@ impl GoogleLanguageModelProvider { }) } - pub fn api_key(cx: &mut App) -> Task> { + pub fn api_key_for_gemini_cli(cx: &mut App) -> Task> { + if let Some(key) = API_KEY_ENV_VAR.value.clone() { + return Task::ready(Ok(key)); + } let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .google - .api_url - .clone(); - - if let Ok(key) = std::env::var(GEMINI_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) + let api_url = Self::api_url(cx).to_string(); + cx.spawn(async move |cx| { + Ok( + ApiKey::load_from_system_keychain(&api_url, credentials_provider.as_ref(), cx) .await? - .ok_or(AuthenticateError::CredentialsNotFound)?; + .key() + .to_string(), + ) + }) + } - Ok(ApiKey { - key: String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, - from_env: false, - }) - }) + fn api_url(cx: &App) -> SharedString { + let api_url = &AllLanguageModelSettings::get_global(cx).google.api_url; + if api_url.is_empty() { + google_ai::API_URL.into() + } else { + SharedString::new(api_url.as_str()) } } } @@ -317,7 +271,8 @@ impl LanguageModelProvider for GoogleLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -340,11 +295,16 @@ impl GoogleLanguageModel { > { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).google; - (state.api_key.clone(), settings.api_url.clone()) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = GoogleLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; async move { @@ -418,13 +378,16 @@ impl LanguageModel for GoogleLanguageModel { let model_id = self.model.request_id().to_string(); let request = into_google(request, model_id, self.model.mode()); let http_client = self.http_client.clone(); - let api_key = self.state.read(cx).api_key.clone(); - - let settings = &AllLanguageModelSettings::get_global(cx).google; - let api_url = settings.api_url.clone(); + let api_url = GoogleLanguageModelProvider::api_url(cx); + let api_key = self.state.read(cx).api_key_state.key(&api_url); async move { - let api_key = api_key.context("Missing Google API key")?; + let Some(api_key) = api_key else { + return Err(LanguageModelCompletionError::NoApiKey { + provider: PROVIDER_NAME, + } + .into()); + }; let response = google_ai::count_tokens( http_client.as_ref(), &api_url, @@ -852,7 +815,7 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self.api_key_editor.read(cx).text(cx); + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); if api_key.is_empty() { return; } @@ -860,12 +823,10 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -874,11 +835,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn render_api_key_editor(&self, cx: &mut Context) -> impl IntoElement { @@ -913,7 +874,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); if self.load_credentials_task.is_some() { div().child(Label::new("Loading credentials...")).into_any() @@ -950,7 +911,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - format!("You can also assign the {GEMINI_API_KEY_VAR} environment variable and restart Zed."), + format!("You can also assign the {GEMINI_API_KEY_VAR_NAME} environment variable and restart Zed."), ) .size(LabelSize::Small).color(Color::Muted), ) @@ -969,7 +930,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {GEMINI_API_KEY_VAR} environment variable.") + format!("API key set in {GEMINI_API_KEY_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -982,7 +943,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .disabled(env_var_set) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, make sure {GEMINI_API_KEY_VAR} and {GOOGLE_AI_API_KEY_VAR} environment variables are unset."))) + this.tooltip(Tooltip::text(format!("To reset your API key, make sure {GEMINI_API_KEY_VAR_NAME} and {GOOGLE_AI_API_KEY_VAR_NAME} environment variables are unset."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index c9824bf89ea7a919f4517f492a5091a2cda7b43b..d2de056489d5fe10bab5eaa41780c0a5f312a181 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -1,10 +1,10 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Result, anyhow}; use collections::BTreeMap; -use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream}; use gpui::{ - AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, + AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, + Window, }; use http_client::HttpClient; use language_model::{ @@ -21,17 +21,21 @@ use settings::{Settings, SettingsStore}; use std::collections::HashMap; use std::pin::Pin; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; use util::ResultExt; +use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, ui::InstructionListItem}; +use crate::{api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("mistral"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("Mistral"); +const API_KEY_ENV_VAR_NAME: &str = "MISTRAL_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); + #[derive(Default, Clone, Debug, PartialEq)] pub struct MistralSettings { pub api_url: String, @@ -56,96 +60,48 @@ pub struct MistralLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, - _subscription: Subscription, + api_key_state: ApiKeyState, } -const MISTRAL_API_KEY_VAR: &str = "MISTRAL_API_KEY"; - impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() - } - - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .mistral - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) + self.api_key_state.has_key() } - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .mistral - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await?; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = MistralLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .mistral - .api_url - .clone(); - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(MISTRAL_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, - ) - }; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = MistralLanguageModelProvider::api_url(cx); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) } } impl MistralLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|_this: &mut State, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = Self::api_url(cx); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + } }); Self { http_client, state } @@ -160,6 +116,19 @@ impl MistralLanguageModelProvider { request_limiter: RateLimiter::new(4), }) } + + fn settings(cx: &App) -> &MistralSettings { + &crate::AllLanguageModelSettings::get_global(cx).mistral + } + + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + mistral::MISTRAL_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } + } } impl LanguageModelProviderState for MistralLanguageModelProvider { @@ -202,10 +171,7 @@ impl LanguageModelProvider for MistralLanguageModelProvider { } // Override with available models from settings - for model in &AllLanguageModelSettings::get_global(cx) - .mistral - .available_models - { + for model in &Self::settings(cx).available_models { models.insert( model.name.clone(), mistral::Model::Custom { @@ -254,7 +220,8 @@ impl LanguageModelProvider for MistralLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -276,15 +243,25 @@ impl MistralLanguageModel { Result>>, > { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).mistral; - (state.api_key.clone(), settings.api_url.clone()) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = MistralLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; let future = self.request_limiter.stream(async move { - let api_key = api_key.context("Missing Mistral API Key")?; + let Some(api_key) = api_key else { + return Err(LanguageModelCompletionError::NoApiKey { + provider: PROVIDER_NAME, + }); + }; let request = mistral::stream_completion(http_client.as_ref(), &api_url, &api_key, request); let response = request.await?; @@ -780,7 +757,7 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self.api_key_editor.read(cx).text(cx); + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); if api_key.is_empty() { return; } @@ -788,12 +765,10 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -802,11 +777,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn render_api_key_editor(&self, cx: &mut Context) -> impl IntoElement { @@ -841,7 +816,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); if self.load_credentials_task.is_some() { div().child(Label::new("Loading credentials...")).into_any() @@ -878,7 +853,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - format!("You can also assign the {MISTRAL_API_KEY_VAR} environment variable and restart Zed."), + format!("You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."), ) .size(LabelSize::Small).color(Color::Muted), ) @@ -897,7 +872,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {MISTRAL_API_KEY_VAR} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -910,7 +885,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .disabled(env_var_set) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, unset the {MISTRAL_API_KEY_VAR} environment variable."))) + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index fca1cf977cb5e3b32dc6f2335fb0d9188979bc9f..a7e8f64d6614d95e4e892aba569d441320108ca4 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -1,10 +1,8 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; -use credentials_provider::CredentialsProvider; - use futures::Stream; use futures::{FutureExt, StreamExt, future::BoxFuture}; -use gpui::{AnyView, App, AsyncApp, Context, Entity, Subscription, Task, Window}; +use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window}; use http_client::HttpClient; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, @@ -20,18 +18,21 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr as _; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; - use ui::{ElevationIndex, List, Tooltip, prelude::*}; use ui_input::SingleLineInput; use util::ResultExt; +use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, ui::InstructionListItem}; +use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = language_model::OPEN_AI_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::OPEN_AI_PROVIDER_NAME; +const API_KEY_ENV_VAR_NAME: &str = "OPENAI_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); + #[derive(Default, Clone, Debug, PartialEq)] pub struct OpenAiSettings { pub api_url: String, @@ -54,132 +55,48 @@ pub struct OpenAiLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, - last_api_url: String, - _subscription: Subscription, + api_key_state: ApiKeyState, } -const OPENAI_API_KEY_VAR: &str = "OPENAI_API_KEY"; - impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() - } - - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .openai - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) - } - - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .openai - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) + self.api_key_state.has_key() } - fn get_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .openai - .api_url - .clone(); - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(OPENAI_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, - ) - }; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = OpenAiLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - self.get_api_key(cx) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = OpenAiLanguageModelProvider::api_url(cx); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) } } impl OpenAiLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let initial_api_url = AllLanguageModelSettings::get_global(cx) - .openai - .api_url - .clone(); - - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - last_api_url: initial_api_url.clone(), - _subscription: cx.observe_global::(|this: &mut State, cx| { - let current_api_url = AllLanguageModelSettings::get_global(cx) - .openai - .api_url - .clone(); - - if this.last_api_url != current_api_url { - this.last_api_url = current_api_url; - if !this.api_key_from_env { - this.api_key = None; - let spawn_task = cx.spawn(async move |handle, cx| { - if let Ok(task) = handle.update(cx, |this, cx| this.get_api_key(cx)) { - if let Err(_) = task.await { - handle - .update(cx, |this, _| { - this.api_key = None; - this.api_key_from_env = false; - }) - .ok(); - } - } - }); - spawn_task.detach(); - } - } + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = Self::api_url(cx); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + } }); Self { http_client, state } @@ -194,6 +111,19 @@ impl OpenAiLanguageModelProvider { request_limiter: RateLimiter::new(4), }) } + + fn settings(cx: &App) -> &OpenAiSettings { + &AllLanguageModelSettings::get_global(cx).openai + } + + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + open_ai::OPEN_AI_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } + } } impl LanguageModelProviderState for OpenAiLanguageModelProvider { @@ -278,7 +208,8 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -298,11 +229,17 @@ impl OpenAiLanguageModel { ) -> BoxFuture<'static, Result>>> { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).openai; - (state.api_key.clone(), settings.api_url.clone()) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = OpenAiLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; let future = self.request_limiter.stream(async move { @@ -802,29 +739,18 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self - .api_key_editor - .read(cx) - .editor() - .read(cx) - .text(cx) - .trim() - .to_string(); - - // Don't proceed if no API key is provided and we're not authenticated - if api_key.is_empty() && !self.state.read(cx).is_authenticated() { + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); + if api_key.is_empty() { return; } let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -836,11 +762,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn should_render_editor(&self, cx: &mut Context) -> bool { @@ -850,7 +776,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); let api_key_section = if self.should_render_editor(cx) { v_flex() @@ -872,10 +798,11 @@ impl Render for ConfigurationView { ) .child(self.api_key_editor.clone()) .child( - Label::new( - format!("You can also assign the {OPENAI_API_KEY_VAR} environment variable and restart Zed."), - ) - .size(LabelSize::Small).color(Color::Muted), + Label::new(format!( + "You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed." + )) + .size(LabelSize::Small) + .color(Color::Muted), ) .child( Label::new( @@ -898,7 +825,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {OPENAI_API_KEY_VAR} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -911,7 +838,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .layer(ElevationIndex::ModalSurface) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, unset the {OPENAI_API_KEY_VAR} environment variable."))) + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs index 4ebb11a07b66ec7054ca65437ec887a415fa3f5c..2b5e372a1d6ccd9b3bb13feb777f1091f0336b58 100644 --- a/crates/language_models/src/provider/open_ai_compatible.rs +++ b/crates/language_models/src/provider/open_ai_compatible.rs @@ -1,9 +1,7 @@ -use anyhow::{Context as _, Result, anyhow}; -use credentials_provider::CredentialsProvider; - +use anyhow::Result; use convert_case::{Case, Casing}; use futures::{FutureExt, StreamExt, future::BoxFuture}; -use gpui::{AnyView, App, AsyncApp, Context, Entity, Subscription, Task, Window}; +use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window}; use http_client::HttpClient; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, @@ -17,13 +15,13 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; - use ui::{ElevationIndex, Tooltip, prelude::*}; use ui_input::SingleLineInput; use util::ResultExt; +use zed_env_vars::EnvVar; -use crate::AllLanguageModelSettings; use crate::provider::open_ai::{OpenAiEventMapper, into_open_ai}; +use crate::{AllLanguageModelSettings, api_key::ApiKeyState}; #[derive(Default, Clone, Debug, PartialEq)] pub struct OpenAiCompatibleSettings { @@ -70,82 +68,30 @@ pub struct OpenAiCompatibleLanguageModelProvider { pub struct State { id: Arc, - env_var_name: Arc, - api_key: Option, - api_key_from_env: bool, + api_key_env_var: EnvVar, + api_key_state: ApiKeyState, settings: OpenAiCompatibleSettings, - _subscription: Subscription, } impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() - } - - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = self.settings.api_url.clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) - } - - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - 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) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) + self.api_key_state.has_key() } - fn get_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let env_var_name = self.env_var_name.clone(); - let api_url = self.settings.api_url.clone(); - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(env_var_name.as_ref()) { - (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, - ) - }; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = SharedString::new(self.settings.api_url.as_str()); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - self.get_api_key(cx) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = SharedString::new(self.settings.api_url.clone()); + self.api_key_state.load_if_needed( + api_url, + &self.api_key_env_var, + |this| &mut this.api_key_state, + cx, + ) } } @@ -157,37 +103,32 @@ impl OpenAiCompatibleLanguageModelProvider { .get(id) } - let state = cx.new(|cx| State { - id: id.clone(), - env_var_name: format!("{}_API_KEY", id).to_case(Case::Constant).into(), - settings: resolve_settings(&id, cx).cloned().unwrap_or_default(), - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|this: &mut State, cx| { + let api_key_env_var_name = format!("{}_API_KEY", id).to_case(Case::UpperSnake).into(); + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { let Some(settings) = resolve_settings(&this.id, cx).cloned() else { return; }; if &this.settings != &settings { - if settings.api_url != this.settings.api_url && !this.api_key_from_env { - let spawn_task = cx.spawn(async move |handle, cx| { - if let Ok(task) = handle.update(cx, |this, cx| this.get_api_key(cx)) { - if let Err(_) = task.await { - handle - .update(cx, |this, _| { - this.api_key = None; - this.api_key_from_env = false; - }) - .ok(); - } - } - }); - spawn_task.detach(); - } - + let api_url = SharedString::new(settings.api_url.as_str()); + this.api_key_state.handle_url_change( + api_url, + &this.api_key_env_var, + |this| &mut this.api_key_state, + cx, + ); this.settings = settings; cx.notify(); } - }), + }) + .detach(); + let settings = resolve_settings(&id, cx).cloned().unwrap_or_default(); + State { + id: id.clone(), + api_key_env_var: EnvVar::new(api_key_env_var_name), + api_key_state: ApiKeyState::new(SharedString::new(settings.api_url.as_str())), + settings, + } }); Self { @@ -274,7 +215,8 @@ impl LanguageModelProvider for OpenAiCompatibleLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -296,10 +238,17 @@ impl OpenAiCompatibleLanguageModel { ) -> BoxFuture<'static, Result>>> { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, _| { - (state.api_key.clone(), state.settings.api_url.clone()) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + + let api_key_and_url = self.state.read_with(cx, |state, _cx| { + let api_url = &state.settings.api_url; + let api_key = state.api_key_state.key(api_url); + (api_key, state.settings.api_url.clone()) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; let provider = self.provider_name.clone(); @@ -469,29 +418,18 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self - .api_key_editor - .read(cx) - .editor() - .read(cx) - .text(cx) - .trim() - .to_string(); - - // Don't proceed if no API key is provided and we're not authenticated - if api_key.is_empty() && !self.state.read(cx).is_authenticated() { + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); + if api_key.is_empty() { return; } let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -503,22 +441,23 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } - fn should_render_editor(&self, cx: &mut Context) -> bool { + fn should_render_editor(&self, cx: &Context) -> bool { !self.state.read(cx).is_authenticated() } } impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; - let env_var_name = self.state.read(cx).env_var_name.clone(); + let state = self.state.read(cx); + let env_var_set = state.api_key_state.is_from_env_var(); + let env_var_name = &state.api_key_env_var.name; let api_key_section = if self.should_render_editor(cx) { v_flex() diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index 0ebf379b393791558cba6ff36ab31d278162386e..efa6a1eabe33fe8ecb52da3b5c61122cfc26e036 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -1,10 +1,9 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Result, anyhow}; use collections::HashMap; -use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; use futures::{FutureExt, Stream, StreamExt, future::BoxFuture}; use gpui::{ - AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, + AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, }; use http_client::HttpClient; use language_model::{ @@ -16,23 +15,26 @@ use language_model::{ }; use open_router::{ Model, ModelMode as OpenRouterModelMode, Provider, ResponseStreamEvent, list_models, - stream_completion, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr as _; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; use util::ResultExt; +use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, ui::InstructionListItem}; +use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("openrouter"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("OpenRouter"); +const API_KEY_ENV_VAR_NAME: &str = "OPENROUTER_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); + #[derive(Default, Clone, Debug, PartialEq)] pub struct OpenRouterSettings { pub api_url: String, @@ -90,93 +92,38 @@ pub struct OpenRouterLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, + api_key_state: ApiKeyState, http_client: Arc, available_models: Vec, fetch_models_task: Option>>, settings: OpenRouterSettings, - _subscription: Subscription, } -const OPENROUTER_API_KEY_VAR: &str = "OPENROUTER_API_KEY"; - impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() - } - - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .open_router - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) + self.api_key_state.has_key() } - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .open_router - .api_url - .clone(); - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.restart_fetch_models_task(cx); - cx.notify(); - }) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = OpenRouterLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let credentials_provider = ::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .open_router - .api_url - .clone(); + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = OpenRouterLanguageModelProvider::api_url(cx); + let task = self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(OPENROUTER_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, - ) - }; - - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - this.restart_fetch_models_task(cx); - cx.notify(); - })?; - - Ok(()) + let result = task.await; + this.update(cx, |this, cx| this.restart_fetch_models_task(cx)) + .ok(); + result }) } @@ -184,10 +131,9 @@ impl State { &mut self, cx: &mut Context, ) -> Task> { - let settings = &AllLanguageModelSettings::get_global(cx).open_router; let http_client = self.http_client.clone(); - let api_url = settings.api_url.clone(); - let Some(api_key) = self.api_key.clone() else { + let api_url = OpenRouterLanguageModelProvider::api_url(cx); + let Some(api_key) = self.api_key_state.key(&api_url) else { return Task::ready(Err(LanguageModelCompletionError::NoApiKey { provider: PROVIDER_NAME, })); @@ -216,33 +162,45 @@ impl State { if self.is_authenticated() { let task = self.fetch_models(cx); self.fetch_models_task.replace(task); + } else { + self.available_models = Vec::new(); } } } impl OpenRouterLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - http_client: http_client.clone(), - available_models: Vec::new(), - fetch_models_task: None, - settings: OpenRouterSettings::default(), - _subscription: cx.observe_global::(|this: &mut State, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { let current_settings = &AllLanguageModelSettings::get_global(cx).open_router; let settings_changed = current_settings != &this.settings; if settings_changed { this.settings = current_settings.clone(); - this.restart_fetch_models_task(cx); + this.authenticate(cx).detach(); } cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + http_client: http_client.clone(), + available_models: Vec::new(), + fetch_models_task: None, + settings: OpenRouterSettings::default(), + } }); Self { http_client, state } } + fn settings(cx: &App) -> &OpenRouterSettings { + &AllLanguageModelSettings::get_global(cx).open_router + } + + fn api_url(cx: &App) -> SharedString { + SharedString::new(Self::settings(cx).api_url.as_str()) + } + fn create_language_model(&self, model: open_router::Model) -> Arc { Arc::new(OpenRouterLanguageModel { id: LanguageModelId::from(model.id().to_string()), @@ -287,10 +245,7 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider { let mut models_from_api = self.state.read(cx).available_models.clone(); let mut settings_models = Vec::new(); - for model in &AllLanguageModelSettings::get_global(cx) - .open_router - .available_models - { + for model in &Self::settings(cx).available_models { settings_models.push(open_router::Model { name: model.name.clone(), display_name: model.display_name.clone(), @@ -338,7 +293,8 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -366,14 +322,17 @@ impl OpenRouterLanguageModel { >, > { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).open_router; - (state.api_key.clone(), settings.api_url.clone()) - }) else { - return futures::future::ready(Err(LanguageModelCompletionError::Other(anyhow!( - "App state dropped" - )))) - .boxed(); + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = OpenRouterLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(LanguageModelCompletionError::Other(err))) + .boxed(); + } }; async move { @@ -382,7 +341,8 @@ impl OpenRouterLanguageModel { provider: PROVIDER_NAME, }); }; - let request = stream_completion(http_client.as_ref(), &api_url, &api_key, request); + let request = + open_router::stream_completion(http_client.as_ref(), &api_url, &api_key, request); request.await.map_err(Into::into) } .boxed() @@ -830,7 +790,7 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self.api_key_editor.read(cx).text(cx); + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); if api_key.is_empty() { return; } @@ -838,12 +798,10 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -852,11 +810,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn render_api_key_editor(&self, cx: &mut Context) -> impl IntoElement { @@ -891,7 +849,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); if self.load_credentials_task.is_some() { div().child(Label::new("Loading credentials...")).into_any() @@ -928,7 +886,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - format!("You can also assign the {OPENROUTER_API_KEY_VAR} environment variable and restart Zed."), + format!("You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."), ) .size(LabelSize::Small).color(Color::Muted), ) @@ -947,7 +905,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {OPENROUTER_API_KEY_VAR} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -960,7 +918,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .disabled(env_var_set) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, unset the {OPENROUTER_API_KEY_VAR} environment variable."))) + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/language_models/src/provider/vercel.rs b/crates/language_models/src/provider/vercel.rs index 84f3175d1e5493fd55cafd2ea9c4a0604d2a97b4..ad28946e3bc78078d9d6510fafacbba34c1f98ca 100644 --- a/crates/language_models/src/provider/vercel.rs +++ b/crates/language_models/src/provider/vercel.rs @@ -1,8 +1,7 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::Result; use collections::BTreeMap; -use credentials_provider::CredentialsProvider; use futures::{FutureExt, StreamExt, future::BoxFuture}; -use gpui::{AnyView, App, AsyncApp, Context, Entity, Subscription, Task, Window}; +use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window}; use http_client::HttpClient; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, @@ -10,24 +9,26 @@ use language_model::{ LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice, RateLimiter, Role, }; -use menu; use open_ai::ResponseStreamEvent; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; -use vercel::Model; - use ui::{ElevationIndex, List, Tooltip, prelude::*}; use ui_input::SingleLineInput; use util::ResultExt; +use vercel::Model; +use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, ui::InstructionListItem}; +use crate::{api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("vercel"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("Vercel"); +const API_KEY_ENV_VAR_NAME: &str = "VERCEL_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); + #[derive(Default, Clone, Debug, PartialEq)] pub struct VercelSettings { pub api_url: String, @@ -49,103 +50,48 @@ pub struct VercelLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, - _subscription: Subscription, + api_key_state: ApiKeyState, } -const VERCEL_API_KEY_VAR: &str = "VERCEL_API_KEY"; - impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() + self.api_key_state.has_key() } - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let settings = &AllLanguageModelSettings::get_global(cx).vercel; - let api_url = if settings.api_url.is_empty() { - vercel::VERCEL_API_URL.to_string() - } else { - settings.api_url.clone() - }; - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = VercelLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let settings = &AllLanguageModelSettings::get_global(cx).vercel; - let api_url = if settings.api_url.is_empty() { - vercel::VERCEL_API_URL.to_string() - } else { - settings.api_url.clone() - }; - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) - } - - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let credentials_provider = ::global(cx); - let settings = &AllLanguageModelSettings::get_global(cx).vercel; - let api_url = if settings.api_url.is_empty() { - vercel::VERCEL_API_URL.to_string() - } else { - settings.api_url.clone() - }; - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(VERCEL_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, - ) - }; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = VercelLanguageModelProvider::api_url(cx); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) } } impl VercelLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|_this: &mut State, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = Self::api_url(cx); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + } }); Self { http_client, state } @@ -160,6 +106,19 @@ impl VercelLanguageModelProvider { request_limiter: RateLimiter::new(4), }) } + + fn settings(cx: &App) -> &VercelSettings { + &crate::AllLanguageModelSettings::get_global(cx).vercel + } + + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + vercel::VERCEL_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } + } } impl LanguageModelProviderState for VercelLanguageModelProvider { @@ -200,10 +159,7 @@ impl LanguageModelProvider for VercelLanguageModelProvider { } } - for model in &AllLanguageModelSettings::get_global(cx) - .vercel - .available_models - { + for model in &Self::settings(cx).available_models { models.insert( model.name.clone(), vercel::Model::Custom { @@ -241,7 +197,8 @@ impl LanguageModelProvider for VercelLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -261,16 +218,17 @@ impl VercelLanguageModel { ) -> BoxFuture<'static, Result>>> { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).vercel; - let api_url = if settings.api_url.is_empty() { - vercel::VERCEL_API_URL.to_string() - } else { - settings.api_url.clone() - }; - (state.api_key.clone(), api_url) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = VercelLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; let future = self.request_limiter.stream(async move { @@ -466,29 +424,18 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self - .api_key_editor - .read(cx) - .editor() - .read(cx) - .text(cx) - .trim() - .to_string(); - - // Don't proceed if no API key is provided and we're not authenticated - if api_key.is_empty() && !self.state.read(cx).is_authenticated() { + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); + if api_key.is_empty() { return; } let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -500,11 +447,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn should_render_editor(&self, cx: &mut Context) -> bool { @@ -514,7 +461,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); let api_key_section = if self.should_render_editor(cx) { v_flex() @@ -534,7 +481,7 @@ impl Render for ConfigurationView { .child(self.api_key_editor.clone()) .child( Label::new(format!( - "You can also assign the {VERCEL_API_KEY_VAR} environment variable and restart Zed." + "You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed." )) .size(LabelSize::Small) .color(Color::Muted), @@ -559,7 +506,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {VERCEL_API_KEY_VAR} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -572,7 +519,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .layer(ElevationIndex::ModalSurface) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, unset the {VERCEL_API_KEY_VAR} environment variable."))) + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/language_models/src/provider/x_ai.rs b/crates/language_models/src/provider/x_ai.rs index bb17f22c7f3fdbb0296b1e0bb290fbce9a979ddf..d5c5848293da5170b609bfd1526c26429865184c 100644 --- a/crates/language_models/src/provider/x_ai.rs +++ b/crates/language_models/src/provider/x_ai.rs @@ -1,8 +1,7 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::Result; use collections::BTreeMap; -use credentials_provider::CredentialsProvider; use futures::{FutureExt, StreamExt, future::BoxFuture}; -use gpui::{AnyView, App, AsyncApp, Context, Entity, Subscription, Task, Window}; +use gpui::{AnyView, App, AsyncApp, Context, Entity, Task, Window}; use http_client::HttpClient; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, @@ -10,23 +9,25 @@ use language_model::{ LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolSchemaFormat, RateLimiter, Role, }; -use menu; use open_ai::ResponseStreamEvent; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; -use x_ai::Model; - use ui::{ElevationIndex, List, Tooltip, prelude::*}; use ui_input::SingleLineInput; use util::ResultExt; +use x_ai::{Model, XAI_API_URL}; +use zed_env_vars::{EnvVar, env_var}; + +use crate::{api_key::ApiKeyState, ui::InstructionListItem}; -use crate::{AllLanguageModelSettings, ui::InstructionListItem}; +const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("x_ai"); +const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("xAI"); -const PROVIDER_ID: &str = "x_ai"; -const PROVIDER_NAME: &str = "xAI"; +const API_KEY_ENV_VAR_NAME: &str = "XAI_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); #[derive(Default, Clone, Debug, PartialEq)] pub struct XAiSettings { @@ -49,103 +50,48 @@ pub struct XAiLanguageModelProvider { } pub struct State { - api_key: Option, - api_key_from_env: bool, - _subscription: Subscription, + api_key_state: ApiKeyState, } -const XAI_API_KEY_VAR: &str = "XAI_API_KEY"; - impl State { fn is_authenticated(&self) -> bool { - self.api_key.is_some() + self.api_key_state.has_key() } - fn reset_api_key(&self, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let settings = &AllLanguageModelSettings::get_global(cx).x_ai; - let api_url = if settings.api_url.is_empty() { - x_ai::XAI_API_URL.to_string() - } else { - settings.api_url.clone() - }; - cx.spawn(async move |this, cx| { - credentials_provider - .delete_credentials(&api_url, cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = None; - this.api_key_from_env = false; - cx.notify(); - }) - }) + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = XAiLanguageModelProvider::api_url(cx); + self.api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx) } - fn set_api_key(&mut self, api_key: String, cx: &mut Context) -> Task> { - let credentials_provider = ::global(cx); - let settings = &AllLanguageModelSettings::get_global(cx).x_ai; - let api_url = if settings.api_url.is_empty() { - x_ai::XAI_API_URL.to_string() - } else { - settings.api_url.clone() - }; - cx.spawn(async move |this, cx| { - credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) - .await - .log_err(); - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - cx.notify(); - }) - }) - } - - fn authenticate(&self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let credentials_provider = ::global(cx); - let settings = &AllLanguageModelSettings::get_global(cx).x_ai; - let api_url = if settings.api_url.is_empty() { - x_ai::XAI_API_URL.to_string() - } else { - settings.api_url.clone() - }; - cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(XAI_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, - ) - }; - this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - })?; - - Ok(()) - }) + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = XAiLanguageModelProvider::api_url(cx); + self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ) } } impl XAiLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { - let state = cx.new(|cx| State { - api_key: None, - api_key_from_env: false, - _subscription: cx.observe_global::(|_this: &mut State, cx| { + let state = cx.new(|cx| { + cx.observe_global::(|this: &mut State, cx| { + let api_url = Self::api_url(cx); + this.api_key_state.handle_url_change( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); cx.notify(); - }), + }) + .detach(); + State { + api_key_state: ApiKeyState::new(Self::api_url(cx)), + } }); Self { http_client, state } @@ -160,6 +106,19 @@ impl XAiLanguageModelProvider { request_limiter: RateLimiter::new(4), }) } + + fn settings(cx: &App) -> &XAiSettings { + &crate::AllLanguageModelSettings::get_global(cx).x_ai + } + + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + XAI_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } + } } impl LanguageModelProviderState for XAiLanguageModelProvider { @@ -172,11 +131,11 @@ impl LanguageModelProviderState for XAiLanguageModelProvider { impl LanguageModelProvider for XAiLanguageModelProvider { fn id(&self) -> LanguageModelProviderId { - LanguageModelProviderId(PROVIDER_ID.into()) + PROVIDER_ID } fn name(&self) -> LanguageModelProviderName { - LanguageModelProviderName(PROVIDER_NAME.into()) + PROVIDER_NAME } fn icon(&self) -> IconName { @@ -200,10 +159,7 @@ impl LanguageModelProvider for XAiLanguageModelProvider { } } - for model in &AllLanguageModelSettings::get_global(cx) - .x_ai - .available_models - { + for model in &Self::settings(cx).available_models { models.insert( model.name.clone(), x_ai::Model::Custom { @@ -241,7 +197,8 @@ impl LanguageModelProvider for XAiLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.reset_api_key(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -261,20 +218,25 @@ impl XAiLanguageModel { ) -> BoxFuture<'static, Result>>> { let http_client = self.http_client.clone(); - let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { - let settings = &AllLanguageModelSettings::get_global(cx).x_ai; - let api_url = if settings.api_url.is_empty() { - x_ai::XAI_API_URL.to_string() - } else { - settings.api_url.clone() - }; - (state.api_key.clone(), api_url) - }) else { - return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); + + let api_key_and_url = self.state.read_with(cx, |state, cx| { + let api_url = XAiLanguageModelProvider::api_url(cx); + let api_key = state.api_key_state.key(&api_url); + (api_key, api_url) + }); + let (api_key, api_url) = match api_key_and_url { + Ok(api_key_and_url) => api_key_and_url, + Err(err) => { + return futures::future::ready(Err(err)).boxed(); + } }; let future = self.request_limiter.stream(async move { - let api_key = api_key.context("Missing xAI API Key")?; + let Some(api_key) = api_key else { + return Err(LanguageModelCompletionError::NoApiKey { + provider: PROVIDER_NAME, + }); + }; let request = open_ai::stream_completion(http_client.as_ref(), &api_url, &api_key, request); let response = request.await?; @@ -295,11 +257,11 @@ impl LanguageModel for XAiLanguageModel { } fn provider_id(&self) -> LanguageModelProviderId { - LanguageModelProviderId(PROVIDER_ID.into()) + PROVIDER_ID } fn provider_name(&self) -> LanguageModelProviderName { - LanguageModelProviderName(PROVIDER_NAME.into()) + PROVIDER_NAME } fn supports_tools(&self) -> bool { @@ -456,29 +418,18 @@ impl ConfigurationView { } fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { - let api_key = self - .api_key_editor - .read(cx) - .editor() - .read(cx) - .text(cx) - .trim() - .to_string(); - - // Don't proceed if no API key is provided and we're not authenticated - if api_key.is_empty() && !self.state.read(cx).is_authenticated() { + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); + if api_key.is_empty() { return; } let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state - .update(cx, |state, cx| state.set_api_key(api_key, cx))? + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? .await }) .detach_and_log_err(cx); - - cx.notify(); } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { @@ -490,11 +441,11 @@ impl ConfigurationView { let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { - state.update(cx, |state, cx| state.reset_api_key(cx))?.await + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await }) .detach_and_log_err(cx); - - cx.notify(); } fn should_render_editor(&self, cx: &mut Context) -> bool { @@ -504,7 +455,7 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let env_var_set = self.state.read(cx).api_key_from_env; + let env_var_set = self.state.read(cx).api_key_state.is_from_env_var(); let api_key_section = if self.should_render_editor(cx) { v_flex() @@ -524,7 +475,7 @@ impl Render for ConfigurationView { .child(self.api_key_editor.clone()) .child( Label::new(format!( - "You can also assign the {XAI_API_KEY_VAR} environment variable and restart Zed." + "You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed." )) .size(LabelSize::Small) .color(Color::Muted), @@ -549,7 +500,7 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {XAI_API_KEY_VAR} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") } else { "API key configured.".to_string() })), @@ -562,7 +513,7 @@ impl Render for ConfigurationView { .icon_position(IconPosition::Start) .layer(ElevationIndex::ModalSurface) .when(env_var_set, |this| { - this.tooltip(Tooltip::text(format!("To reset your API key, unset the {XAI_API_KEY_VAR} environment variable."))) + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) }) .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), ) diff --git a/crates/zed_env_vars/Cargo.toml b/crates/zed_env_vars/Cargo.toml index 9abfc410e7e74774c4e9e7608e8c1c3824ebc3c1..f56e3dd529cc7a8001d0021e96902f55034f88e2 100644 --- a/crates/zed_env_vars/Cargo.toml +++ b/crates/zed_env_vars/Cargo.toml @@ -16,3 +16,4 @@ default = [] [dependencies] workspace-hack.workspace = true +gpui.workspace = true diff --git a/crates/zed_env_vars/src/zed_env_vars.rs b/crates/zed_env_vars/src/zed_env_vars.rs index d1679a0518f2bae857364b0035b6184350ffca55..53b9c22bb207e81831d1d9ae6087d1a297331d3f 100644 --- a/crates/zed_env_vars/src/zed_env_vars.rs +++ b/crates/zed_env_vars/src/zed_env_vars.rs @@ -1,6 +1,44 @@ +use gpui::SharedString; use std::sync::LazyLock; /// Whether Zed is running in stateless mode. /// When true, Zed will use in-memory databases instead of persistent storage. -pub static ZED_STATELESS: LazyLock = - LazyLock::new(|| std::env::var("ZED_STATELESS").is_ok_and(|v| !v.is_empty())); +pub static ZED_STATELESS: LazyLock = bool_env_var!("ZED_STATELESS"); + +pub struct EnvVar { + pub name: SharedString, + /// Value of the environment variable. Also `None` when set to an empty string. + pub value: Option, +} + +impl EnvVar { + pub fn new(name: SharedString) -> Self { + let value = std::env::var(name.as_str()).ok(); + if value.as_ref().is_some_and(|v| v.is_empty()) { + Self { name, value: None } + } else { + Self { name, value } + } + } + + pub fn or(self, other: EnvVar) -> EnvVar { + if self.value.is_some() { self } else { other } + } +} + +/// Creates a `LazyLock` expression for use in a `static` declaration. +#[macro_export] +macro_rules! env_var { + ($name:expr) => { + LazyLock::new(|| $crate::EnvVar::new(($name).into())) + }; +} + +/// Generates a `LazyLock` expression for use in a `static` declaration. Checks if the +/// environment variable exists and is non-empty. +#[macro_export] +macro_rules! bool_env_var { + ($name:expr) => { + LazyLock::new(|| $crate::EnvVar::new(($name).into()).value.is_some()) + }; +} From 634ae72cadae10ce83127c090f4903ddf87681a0 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sun, 14 Sep 2025 23:08:38 -0600 Subject: [PATCH 020/721] Misc cleanup + clear language model provider API key editors when API keys are submitted (#38165) Followup to #38163 along with some other misc cleanups Release Notes: - N/A --- .../language_models/src/provider/anthropic.rs | 32 ++++++-------- .../language_models/src/provider/deepseek.rs | 19 +++----- crates/language_models/src/provider/google.rs | 34 +++++++-------- .../language_models/src/provider/mistral.rs | 19 ++++---- .../language_models/src/provider/open_ai.rs | 35 ++++++--------- .../src/provider/open_ai_compatible.rs | 35 ++++++++------- .../src/provider/open_router.rs | 43 +++++++++---------- crates/language_models/src/provider/vercel.rs | 28 ++++++------ crates/language_models/src/provider/x_ai.rs | 28 ++++++------ crates/ui_input/src/ui_input.rs | 6 +++ 10 files changed, 128 insertions(+), 151 deletions(-) diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index f9c664182426dfd9523254867ba2f95b8f3dc7c4..e35787b501d74016ce214ac6823ca147e22f6c14 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -1,14 +1,13 @@ +use crate::api_key::ApiKeyState; use crate::ui::InstructionListItem; -use crate::{AllLanguageModelSettings, api_key::ApiKeyState}; use anthropic::{ AnthropicError, AnthropicModelMode, ContentDelta, Event, ResponseContent, ToolResultContent, ToolResultPart, Usage, }; -use anyhow::Result; +use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; use editor::{Editor, EditorElement, EditorStyle}; -use futures::Stream; -use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream}; +use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture, stream::BoxStream}; use gpui::{AnyView, App, AsyncApp, Context, Entity, FontStyle, Task, TextStyle, WhiteSpace}; use http_client::HttpClient; use language_model::{ @@ -157,7 +156,7 @@ impl AnthropicLanguageModelProvider { } fn settings(cx: &App) -> &AnthropicSettings { - &AllLanguageModelSettings::get_global(cx).anthropic + &crate::AllLanguageModelSettings::get_global(cx).anthropic } fn api_url(cx: &App) -> SharedString { @@ -220,11 +219,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider { } // Override with available models from settings - for model in AllLanguageModelSettings::get_global(cx) - .anthropic - .available_models - .iter() - { + for model in &AnthropicLanguageModelProvider::settings(cx).available_models { models.insert( model.name.clone(), anthropic::Model::Custom { @@ -363,16 +358,11 @@ impl AnthropicModel { > { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = AnthropicLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err.into())).boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped").into())).boxed(); }; let beta_headers = self.model.beta_headers(); @@ -938,6 +928,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index e00d8bbf4b34560bdc55c982114bf96675e22d99..8bd8f4d2ed811e3a481d35b27f32bffc77539c31 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -2,7 +2,7 @@ use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; use editor::{Editor, EditorElement, EditorStyle}; use futures::Stream; -use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream}; +use futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream}; use gpui::{ AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, Window, @@ -26,7 +26,7 @@ use ui::{Icon, IconName, List, prelude::*}; use util::ResultExt; use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem}; +use crate::{api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("deepseek"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("DeepSeek"); @@ -119,7 +119,7 @@ impl DeepSeekLanguageModelProvider { } fn settings(cx: &App) -> &DeepSeekSettings { - &AllLanguageModelSettings::get_global(cx).deepseek + &crate::AllLanguageModelSettings::get_global(cx).deepseek } fn api_url(cx: &App) -> &str { @@ -220,16 +220,11 @@ impl DeepSeekLanguageModel { ) -> BoxFuture<'static, Result>>> { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = DeepSeekLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(api_url); - (api_key, api_url.to_string()) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + (state.api_key_state.key(api_url), api_url.to_string()) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; let future = self.request_limiter.stream(async move { diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index 677d5775b3854245ad441acf37b0e1ed02f6bab5..70b028bfecaeef38468ced2814f40dfe8cb4103a 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -1,8 +1,8 @@ -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, Result, anyhow}; use collections::BTreeMap; use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; -use futures::{FutureExt, Stream, StreamExt, future::BoxFuture}; +use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture}; use google_ai::{ FunctionDeclaration, GenerateContentResponse, GoogleModelMode, Part, SystemInstruction, ThinkingConfig, UsageMetadata, @@ -37,8 +37,8 @@ use util::ResultExt; use zed_env_vars::EnvVar; use crate::api_key::ApiKey; +use crate::api_key::ApiKeyState; use crate::ui::InstructionListItem; -use crate::{AllLanguageModelSettings, api_key::ApiKeyState}; const PROVIDER_ID: LanguageModelProviderId = language_model::GOOGLE_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::GOOGLE_PROVIDER_NAME; @@ -173,8 +173,12 @@ impl GoogleLanguageModelProvider { }) } + fn settings(cx: &App) -> &GoogleSettings { + &crate::AllLanguageModelSettings::get_global(cx).google + } + fn api_url(cx: &App) -> SharedString { - let api_url = &AllLanguageModelSettings::get_global(cx).google.api_url; + let api_url = &Self::settings(cx).api_url; if api_url.is_empty() { google_ai::API_URL.into() } else { @@ -223,10 +227,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider { } // Override with available models from settings - for model in &AllLanguageModelSettings::get_global(cx) - .google - .available_models - { + for model in &GoogleLanguageModelProvider::settings(cx).available_models { models.insert( model.name.clone(), google_ai::Model::Custom { @@ -295,16 +296,11 @@ impl GoogleLanguageModel { > { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = GoogleLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; async move { @@ -820,6 +816,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index d2de056489d5fe10bab5eaa41780c0a5f312a181..260e438efacb1d9db165f8a67107dc412394c8de 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -1,7 +1,7 @@ use anyhow::{Result, anyhow}; use collections::BTreeMap; use editor::{Editor, EditorElement, EditorStyle}; -use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream}; +use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture, stream::BoxStream}; use gpui::{ AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, Window, @@ -244,16 +244,11 @@ impl MistralLanguageModel { > { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = MistralLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; let future = self.request_limiter.stream(async move { @@ -762,6 +757,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index a7e8f64d6614d95e4e892aba569d441320108ca4..050ddd09ab9da5f93cedec8ad204b0b8d6d726c3 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -1,7 +1,7 @@ use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; use futures::Stream; -use futures::{FutureExt, StreamExt, future::BoxFuture}; +use futures::{FutureExt, StreamExt, future, future::BoxFuture}; use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window}; use http_client::HttpClient; use language_model::{ @@ -25,7 +25,7 @@ use ui_input::SingleLineInput; use util::ResultExt; use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem}; +use crate::{api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = language_model::OPEN_AI_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::OPEN_AI_PROVIDER_NAME; @@ -113,7 +113,7 @@ impl OpenAiLanguageModelProvider { } fn settings(cx: &App) -> &OpenAiSettings { - &AllLanguageModelSettings::get_global(cx).openai + &crate::AllLanguageModelSettings::get_global(cx).openai } fn api_url(cx: &App) -> SharedString { @@ -166,10 +166,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider { } // Override with available models from settings - for model in &AllLanguageModelSettings::get_global(cx) - .openai - .available_models - { + for model in &OpenAiLanguageModelProvider::settings(cx).available_models { models.insert( model.name.clone(), open_ai::Model::Custom { @@ -230,16 +227,11 @@ impl OpenAiLanguageModel { { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = OpenAiLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; let future = self.request_limiter.stream(async move { @@ -744,6 +736,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state @@ -754,11 +750,8 @@ impl ConfigurationView { } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { - self.api_key_editor.update(cx, |input, cx| { - input.editor.update(cx, |editor, cx| { - editor.set_text("", window, cx); - }); - }); + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs index 2b5e372a1d6ccd9b3bb13feb777f1091f0336b58..83cbaddb7076c6665dc82300cc71b8b8d037f1fb 100644 --- a/crates/language_models/src/provider/open_ai_compatible.rs +++ b/crates/language_models/src/provider/open_ai_compatible.rs @@ -1,6 +1,6 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use convert_case::{Case, Casing}; -use futures::{FutureExt, StreamExt, future::BoxFuture}; +use futures::{FutureExt, StreamExt, future, future::BoxFuture}; use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window}; use http_client::HttpClient; use language_model::{ @@ -20,8 +20,8 @@ use ui_input::SingleLineInput; use util::ResultExt; use zed_env_vars::EnvVar; +use crate::api_key::ApiKeyState; use crate::provider::open_ai::{OpenAiEventMapper, into_open_ai}; -use crate::{AllLanguageModelSettings, api_key::ApiKeyState}; #[derive(Default, Clone, Debug, PartialEq)] pub struct OpenAiCompatibleSettings { @@ -98,7 +98,7 @@ impl State { impl OpenAiCompatibleLanguageModelProvider { pub fn new(id: Arc, http_client: Arc, cx: &mut App) -> Self { fn resolve_settings<'a>(id: &'a str, cx: &'a App) -> Option<&'a OpenAiCompatibleSettings> { - AllLanguageModelSettings::get_global(cx) + crate::AllLanguageModelSettings::get_global(cx) .openai_compatible .get(id) } @@ -239,16 +239,14 @@ impl OpenAiCompatibleLanguageModel { { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, _cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, _cx| { let api_url = &state.settings.api_url; - let api_key = state.api_key_state.key(api_url); - (api_key, state.settings.api_url.clone()) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + ( + state.api_key_state.key(api_url), + state.settings.api_url.clone(), + ) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; let provider = self.provider_name.clone(); @@ -423,6 +421,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state @@ -433,11 +435,8 @@ impl ConfigurationView { } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { - self.api_key_editor.update(cx, |input, cx| { - input.editor.update(cx, |editor, cx| { - editor.set_text("", window, cx); - }); - }); + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index efa6a1eabe33fe8ecb52da3b5c61122cfc26e036..116f40e3ad4490f63e27d82ea08abec2407f407a 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -1,7 +1,7 @@ use anyhow::{Result, anyhow}; use collections::HashMap; use editor::{Editor, EditorElement, EditorStyle}; -use futures::{FutureExt, Stream, StreamExt, future::BoxFuture}; +use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture}; use gpui::{ AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace, }; @@ -27,7 +27,7 @@ use ui::{Icon, IconName, List, Tooltip, prelude::*}; use util::ResultExt; use zed_env_vars::{EnvVar, env_var}; -use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem}; +use crate::{api_key::ApiKeyState, ui::InstructionListItem}; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("openrouter"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("OpenRouter"); @@ -96,7 +96,6 @@ pub struct State { http_client: Arc, available_models: Vec, fetch_models_task: Option>>, - settings: OpenRouterSettings, } impl State { @@ -171,14 +170,17 @@ impl State { impl OpenRouterLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { let state = cx.new(|cx| { - cx.observe_global::(|this: &mut State, cx| { - let current_settings = &AllLanguageModelSettings::get_global(cx).open_router; - let settings_changed = current_settings != &this.settings; - if settings_changed { - this.settings = current_settings.clone(); - this.authenticate(cx).detach(); + cx.observe_global::({ + let mut last_settings = OpenRouterLanguageModelProvider::settings(cx).clone(); + move |this: &mut State, cx| { + let current_settings = OpenRouterLanguageModelProvider::settings(cx); + let settings_changed = current_settings != &last_settings; + if settings_changed { + last_settings = current_settings.clone(); + this.authenticate(cx).detach(); + cx.notify(); + } } - cx.notify(); }) .detach(); State { @@ -186,7 +188,6 @@ impl OpenRouterLanguageModelProvider { http_client: http_client.clone(), available_models: Vec::new(), fetch_models_task: None, - settings: OpenRouterSettings::default(), } }); @@ -194,7 +195,7 @@ impl OpenRouterLanguageModelProvider { } fn settings(cx: &App) -> &OpenRouterSettings { - &AllLanguageModelSettings::get_global(cx).open_router + &crate::AllLanguageModelSettings::get_global(cx).open_router } fn api_url(cx: &App) -> SharedString { @@ -322,17 +323,11 @@ impl OpenRouterLanguageModel { >, > { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = OpenRouterLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(LanguageModelCompletionError::Other(err))) - .boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped").into())).boxed(); }; async move { @@ -795,6 +790,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state diff --git a/crates/language_models/src/provider/vercel.rs b/crates/language_models/src/provider/vercel.rs index ad28946e3bc78078d9d6510fafacbba34c1f98ca..abf63bd8e27b4ba08df8b7e2549fffc0578c138d 100644 --- a/crates/language_models/src/provider/vercel.rs +++ b/crates/language_models/src/provider/vercel.rs @@ -1,6 +1,6 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use collections::BTreeMap; -use futures::{FutureExt, StreamExt, future::BoxFuture}; +use futures::{FutureExt, StreamExt, future, future::BoxFuture}; use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window}; use http_client::HttpClient; use language_model::{ @@ -219,16 +219,11 @@ impl VercelLanguageModel { { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = VercelLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; let future = self.request_limiter.stream(async move { @@ -429,6 +424,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state @@ -439,11 +438,8 @@ impl ConfigurationView { } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { - self.api_key_editor.update(cx, |input, cx| { - input.editor.update(cx, |editor, cx| { - editor.set_text("", window, cx); - }); - }); + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { diff --git a/crates/language_models/src/provider/x_ai.rs b/crates/language_models/src/provider/x_ai.rs index d5c5848293da5170b609bfd1526c26429865184c..eba95d1c146045b3e4482ffe2db34c8a166530ef 100644 --- a/crates/language_models/src/provider/x_ai.rs +++ b/crates/language_models/src/provider/x_ai.rs @@ -1,6 +1,6 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use collections::BTreeMap; -use futures::{FutureExt, StreamExt, future::BoxFuture}; +use futures::{FutureExt, StreamExt, future, future::BoxFuture}; use gpui::{AnyView, App, AsyncApp, Context, Entity, Task, Window}; use http_client::HttpClient; use language_model::{ @@ -219,16 +219,11 @@ impl XAiLanguageModel { { let http_client = self.http_client.clone(); - let api_key_and_url = self.state.read_with(cx, |state, cx| { + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = XAiLanguageModelProvider::api_url(cx); - let api_key = state.api_key_state.key(&api_url); - (api_key, api_url) - }); - let (api_key, api_url) = match api_key_and_url { - Ok(api_key_and_url) => api_key_and_url, - Err(err) => { - return futures::future::ready(Err(err)).boxed(); - } + (state.api_key_state.key(&api_url), api_url) + }) else { + return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; let future = self.request_limiter.stream(async move { @@ -423,6 +418,10 @@ impl ConfigurationView { return; } + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |editor, cx| editor.set_text("", window, cx)); + let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { state @@ -433,11 +432,8 @@ impl ConfigurationView { } fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { - self.api_key_editor.update(cx, |input, cx| { - input.editor.update(cx, |editor, cx| { - editor.set_text("", window, cx); - }); - }); + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); let state = self.state.clone(); cx.spawn_in(window, async move |_, cx| { diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index 86a569b53200cc5ef3ed144841e76cecd94ef94e..45c0deba4adfe71ea99d83c1bd081af1fc272671 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/crates/ui_input/src/ui_input.rs @@ -9,6 +9,7 @@ use component::{example_group, single_example}; use editor::{Editor, EditorElement, EditorStyle}; use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle}; use settings::Settings; +use std::sync::Arc; use theme::ThemeSettings; use ui::prelude::*; @@ -101,6 +102,11 @@ impl SingleLineInput { pub fn text(&self, cx: &App) -> String { self.editor().read(cx).text(cx) } + + pub fn set_text(&self, text: impl Into>, window: &mut Window, cx: &mut App) { + self.editor() + .update(cx, |editor, cx| editor.set_text(text, window, cx)) + } } impl Render for SingleLineInput { From a598fbaa735da17489ff9883162c21028942a7b1 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sun, 14 Sep 2025 23:49:25 -0600 Subject: [PATCH 021/721] ai: Show "API key configured for {URL}" for non-default urls (#38170) Followup to #38163, also makes some changes intended to be included in that PR. Release Notes: - N/A --- .../language_models/src/provider/anthropic.rs | 17 ++++++---- .../language_models/src/provider/deepseek.rs | 34 +++++++++++++------ crates/language_models/src/provider/google.rs | 11 ++++-- .../language_models/src/provider/mistral.rs | 13 ++++--- .../language_models/src/provider/open_ai.rs | 15 +++++--- .../src/provider/open_ai_compatible.rs | 6 ++-- .../src/provider/open_router.rs | 21 +++++++++--- crates/language_models/src/provider/vercel.rs | 15 +++++--- crates/language_models/src/provider/x_ai.rs | 11 ++++-- 9 files changed, 100 insertions(+), 43 deletions(-) diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index e35787b501d74016ce214ac6823ca147e22f6c14..f928dcbcb5c598b6cd3f5ba9def9da29311ebdf6 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -1,8 +1,8 @@ use crate::api_key::ApiKeyState; use crate::ui::InstructionListItem; use anthropic::{ - AnthropicError, AnthropicModelMode, ContentDelta, Event, ResponseContent, ToolResultContent, - ToolResultPart, Usage, + ANTHROPIC_API_URL, AnthropicError, AnthropicModelMode, ContentDelta, Event, ResponseContent, + ToolResultContent, ToolResultPart, Usage, }; use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; @@ -27,7 +27,7 @@ use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::{EnvVar, env_var}; const PROVIDER_ID: LanguageModelProviderId = language_model::ANTHROPIC_PROVIDER_ID; @@ -162,7 +162,7 @@ impl AnthropicLanguageModelProvider { fn api_url(cx: &App) -> SharedString { let api_url = &Self::settings(cx).api_url; if api_url.is_empty() { - anthropic::ANTHROPIC_API_URL.into() + ANTHROPIC_API_URL.into() } else { SharedString::new(api_url.as_str()) } @@ -1045,9 +1045,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured.".to_string() + let api_url = AnthropicLanguageModelProvider::api_url(cx); + if api_url == ANTHROPIC_API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index 8bd8f4d2ed811e3a481d35b27f32bffc77539c31..0775f26b0bcaf0cd88bb80934baf8710c5c996ef 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -1,5 +1,6 @@ use anyhow::{Result, anyhow}; use collections::{BTreeMap, HashMap}; +use deepseek::DEEPSEEK_API_URL; use editor::{Editor, EditorElement, EditorStyle}; use futures::Stream; use futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream}; @@ -23,7 +24,7 @@ use std::str::FromStr; use std::sync::{Arc, LazyLock}; use theme::ThemeSettings; use ui::{Icon, IconName, List, prelude::*}; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::{EnvVar, env_var}; use crate::{api_key::ApiKeyState, ui::InstructionListItem}; @@ -70,13 +71,13 @@ impl State { } fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { - let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx)); + let api_url = DeepSeekLanguageModelProvider::api_url(cx); self.api_key_state .store(api_url, api_key, |this| &mut this.api_key_state, cx) } fn authenticate(&mut self, cx: &mut Context) -> Task> { - let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx)); + let api_url = DeepSeekLanguageModelProvider::api_url(cx); self.api_key_state.load_if_needed( api_url, &API_KEY_ENV_VAR, @@ -90,7 +91,7 @@ impl DeepSeekLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut App) -> Self { let state = cx.new(|cx| { cx.observe_global::(|this: &mut State, cx| { - let api_url = SharedString::new(Self::api_url(cx)); + let api_url = Self::api_url(cx); this.api_key_state.handle_url_change( api_url, &API_KEY_ENV_VAR, @@ -101,7 +102,7 @@ impl DeepSeekLanguageModelProvider { }) .detach(); State { - api_key_state: ApiKeyState::new(SharedString::new(Self::api_url(cx))), + api_key_state: ApiKeyState::new(Self::api_url(cx)), } }); @@ -122,8 +123,13 @@ impl DeepSeekLanguageModelProvider { &crate::AllLanguageModelSettings::get_global(cx).deepseek } - fn api_url(cx: &App) -> &str { - &Self::settings(cx).api_url + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + DEEPSEEK_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } } } @@ -222,7 +228,7 @@ impl DeepSeekLanguageModel { let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { let api_url = DeepSeekLanguageModelProvider::api_url(cx); - (state.api_key_state.key(api_url), api_url.to_string()) + (state.api_key_state.key(&api_url), api_url) }) else { return future::ready(Err(anyhow!("App state dropped"))).boxed(); }; @@ -691,9 +697,17 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME}") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured".to_string() + let api_url = DeepSeekLanguageModelProvider::api_url(cx); + if api_url == DEEPSEEK_API_URL { + "API key configured".to_string() + } else { + format!( + "API key configured for {}", + truncate_and_trailoff(&api_url, 32) + ) + } })), ) .child( diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index 70b028bfecaeef38468ced2814f40dfe8cb4103a..140968b917db497a04c4185857d6a44da0003a65 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -33,7 +33,7 @@ use std::sync::{ use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::EnvVar; use crate::api_key::ApiKey; @@ -930,9 +930,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {GEMINI_API_KEY_VAR_NAME} environment variable.") + format!("API key set in {} environment variable", API_KEY_ENV_VAR.name) } else { - "API key configured.".to_string() + let api_url = GoogleLanguageModelProvider::api_url(cx); + if api_url == google_ai::API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index 260e438efacb1d9db165f8a67107dc412394c8de..682d3549b8ae720ca28629293beb720798d9b583 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -14,7 +14,7 @@ use language_model::{ LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, }; -use mistral::StreamResponse; +use mistral::{MISTRAL_API_URL, StreamResponse}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; @@ -25,7 +25,7 @@ use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::{EnvVar, env_var}; use crate::{api_key::ApiKeyState, ui::InstructionListItem}; @@ -871,9 +871,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured.".to_string() + let api_url = MistralLanguageModelProvider::api_url(cx); + if api_url == MISTRAL_API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index 050ddd09ab9da5f93cedec8ad204b0b8d6d726c3..ceb8d1144cdb73d4dde3e1cffdfdb780e6ff888e 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -12,7 +12,9 @@ use language_model::{ RateLimiter, Role, StopReason, TokenUsage, }; use menu; -use open_ai::{ImageUrl, Model, ReasoningEffort, ResponseStreamEvent, stream_completion}; +use open_ai::{ + ImageUrl, Model, OPEN_AI_API_URL, ReasoningEffort, ResponseStreamEvent, stream_completion, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; @@ -22,7 +24,7 @@ use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use ui::{ElevationIndex, List, Tooltip, prelude::*}; use ui_input::SingleLineInput; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::{EnvVar, env_var}; use crate::{api_key::ApiKeyState, ui::InstructionListItem}; @@ -818,9 +820,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured.".to_string() + let api_url = OpenAiLanguageModelProvider::api_url(cx); + if api_url == OPEN_AI_API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs index 83cbaddb7076c6665dc82300cc71b8b8d037f1fb..9a64a6d340b500d651dc88d3138e75f7c5d98528 100644 --- a/crates/language_models/src/provider/open_ai_compatible.rs +++ b/crates/language_models/src/provider/open_ai_compatible.rs @@ -17,7 +17,7 @@ use settings::{Settings, SettingsStore}; use std::sync::Arc; use ui::{ElevationIndex, Tooltip, prelude::*}; use ui_input::SingleLineInput; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::EnvVar; use crate::api_key::ApiKeyState; @@ -488,9 +488,9 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {env_var_name} environment variable.") + format!("API key set in {env_var_name} environment variable") } else { - "API key configured.".to_string() + format!("API key configured for {}", truncate_and_trailoff(&state.settings.api_url, 32)) })), ) .child( diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index 116f40e3ad4490f63e27d82ea08abec2407f407a..170beab5dae84423501127835a7f560d142fe00a 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -14,7 +14,8 @@ use language_model::{ LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, }; use open_router::{ - Model, ModelMode as OpenRouterModelMode, Provider, ResponseStreamEvent, list_models, + Model, ModelMode as OpenRouterModelMode, OPEN_ROUTER_API_URL, Provider, ResponseStreamEvent, + list_models, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -24,7 +25,7 @@ use std::str::FromStr as _; use std::sync::{Arc, LazyLock}; use theme::ThemeSettings; use ui::{Icon, IconName, List, Tooltip, prelude::*}; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::{EnvVar, env_var}; use crate::{api_key::ApiKeyState, ui::InstructionListItem}; @@ -199,7 +200,12 @@ impl OpenRouterLanguageModelProvider { } fn api_url(cx: &App) -> SharedString { - SharedString::new(Self::settings(cx).api_url.as_str()) + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + OPEN_ROUTER_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } } fn create_language_model(&self, model: open_router::Model) -> Arc { @@ -904,9 +910,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured.".to_string() + let api_url = OpenRouterLanguageModelProvider::api_url(cx); + if api_url == OPEN_ROUTER_API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( diff --git a/crates/language_models/src/provider/vercel.rs b/crates/language_models/src/provider/vercel.rs index abf63bd8e27b4ba08df8b7e2549fffc0578c138d..6fce79fd85462ed8a8e45d342b43ee83270adc25 100644 --- a/crates/language_models/src/provider/vercel.rs +++ b/crates/language_models/src/provider/vercel.rs @@ -17,8 +17,8 @@ use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use ui::{ElevationIndex, List, Tooltip, prelude::*}; use ui_input::SingleLineInput; -use util::ResultExt; -use vercel::Model; +use util::{ResultExt, truncate_and_trailoff}; +use vercel::{Model, VERCEL_API_URL}; use zed_env_vars::{EnvVar, env_var}; use crate::{api_key::ApiKeyState, ui::InstructionListItem}; @@ -114,7 +114,7 @@ impl VercelLanguageModelProvider { fn api_url(cx: &App) -> SharedString { let api_url = &Self::settings(cx).api_url; if api_url.is_empty() { - vercel::VERCEL_API_URL.into() + VERCEL_API_URL.into() } else { SharedString::new(api_url.as_str()) } @@ -502,9 +502,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured.".to_string() + let api_url = VercelLanguageModelProvider::api_url(cx); + if api_url == VERCEL_API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( diff --git a/crates/language_models/src/provider/x_ai.rs b/crates/language_models/src/provider/x_ai.rs index eba95d1c146045b3e4482ffe2db34c8a166530ef..51b59a04b3eea21e45926a3383893cdde09a7b8f 100644 --- a/crates/language_models/src/provider/x_ai.rs +++ b/crates/language_models/src/provider/x_ai.rs @@ -17,7 +17,7 @@ use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; use ui::{ElevationIndex, List, Tooltip, prelude::*}; use ui_input::SingleLineInput; -use util::ResultExt; +use util::{ResultExt, truncate_and_trailoff}; use x_ai::{Model, XAI_API_URL}; use zed_env_vars::{EnvVar, env_var}; @@ -496,9 +496,14 @@ impl Render for ConfigurationView { .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) .child(Label::new(if env_var_set { - format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable") } else { - "API key configured.".to_string() + let api_url = XAiLanguageModelProvider::api_url(cx); + if api_url == XAI_API_URL { + "API key configured".to_string() + } else { + format!("API key configured for {}", truncate_and_trailoff(&api_url, 32)) + } })), ) .child( From 526196917b40287462013c70eb1b61c1332effa8 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:04:26 +0530 Subject: [PATCH 022/721] language_models: Add support for API key to Ollama provider (#34110) Closes https://github.com/zed-industries/zed/issues/19491 Release Notes: - Ollama: Added configuration of URL and API key for remote Ollama provider. --------- Signed-off-by: Umesh Yadav Co-authored-by: Peter Tripp Co-authored-by: Oliver Azevedo Barnes Co-authored-by: Michael Sloan --- Cargo.lock | 1 + crates/language_models/Cargo.toml | 1 + crates/language_models/src/provider/ollama.rs | 516 +++++++++++++----- crates/ollama/src/ollama.rs | 36 +- docs/src/ai/llm-providers.md | 14 + 5 files changed, 414 insertions(+), 154 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22589ee11a4ffb657238091ab85a3e76d9b6bf32..14ec0e21db3a1893002c6277c43b9d1b996ab086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9179,6 +9179,7 @@ dependencies = [ "credentials_provider", "deepseek", "editor", + "fs", "futures 0.3.31", "google_ai", "gpui", diff --git a/crates/language_models/Cargo.toml b/crates/language_models/Cargo.toml index 7dc0988d23579c4d1ab1ac2dde1f1413c5e751b8..8a2a681c26ede21ce948b6667b4aaea589724dcf 100644 --- a/crates/language_models/Cargo.toml +++ b/crates/language_models/Cargo.toml @@ -29,6 +29,7 @@ copilot.workspace = true credentials_provider.workspace = true deepseek = { workspace = true, features = ["schemars"] } editor.workspace = true +fs.workspace = true futures.workspace = true google_ai = { workspace = true, features = ["schemars"] } gpui.workspace = true diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index a80cacfc4a02521af74b32c34cc3360e9665a7d9..4932d9507027296003dd8ae095318718e9019334 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -1,7 +1,8 @@ use anyhow::{Result, anyhow}; +use fs::Fs; use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream}; use futures::{Stream, TryFutureExt, stream}; -use gpui::{AnyView, App, AsyncApp, Context, Subscription, Task}; +use gpui::{AnyView, App, AsyncApp, Context, Task}; use http_client::HttpClient; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, @@ -10,20 +11,25 @@ use language_model::{ LanguageModelRequestTool, LanguageModelToolChoice, LanguageModelToolUse, LanguageModelToolUseId, MessageContent, RateLimiter, Role, StopReason, TokenUsage, }; +use menu; use ollama::{ - ChatMessage, ChatOptions, ChatRequest, ChatResponseDelta, KeepAlive, OllamaFunctionCall, - OllamaFunctionTool, OllamaToolCall, get_models, show_model, stream_chat_completion, + ChatMessage, ChatOptions, ChatRequest, ChatResponseDelta, KeepAlive, OLLAMA_API_URL, + OllamaFunctionCall, OllamaFunctionTool, OllamaToolCall, get_models, show_model, + stream_chat_completion, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::{Settings, SettingsStore, update_settings_file}; use std::pin::Pin; +use std::sync::LazyLock; use std::sync::atomic::{AtomicU64, Ordering}; use std::{collections::HashMap, sync::Arc}; -use ui::{ButtonLike, Indicator, List, prelude::*}; -use util::ResultExt; +use ui::{ButtonLike, ElevationIndex, List, Tooltip, prelude::*}; +use ui_input::SingleLineInput; +use zed_env_vars::{EnvVar, env_var}; use crate::AllLanguageModelSettings; +use crate::api_key::ApiKeyState; use crate::ui::InstructionListItem; const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download"; @@ -33,6 +39,9 @@ const OLLAMA_SITE: &str = "https://ollama.com/"; const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("ollama"); const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("Ollama"); +const API_KEY_ENV_VAR_NAME: &str = "OLLAMA_API_KEY"; +static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); + #[derive(Default, Debug, Clone, PartialEq)] pub struct OllamaSettings { pub api_url: String, @@ -63,25 +72,61 @@ pub struct OllamaLanguageModelProvider { } pub struct State { + api_key_state: ApiKeyState, http_client: Arc, - available_models: Vec, + fetched_models: Vec, fetch_model_task: Option>>, - _subscription: Subscription, } impl State { fn is_authenticated(&self) -> bool { - !self.available_models.is_empty() + !self.fetched_models.is_empty() + } + + fn set_api_key(&mut self, api_key: Option, cx: &mut Context) -> Task> { + let api_url = OllamaLanguageModelProvider::api_url(cx); + let task = self + .api_key_state + .store(api_url, api_key, |this| &mut this.api_key_state, cx); + + self.fetched_models.clear(); + cx.spawn(async move |this, cx| { + let result = task.await; + this.update(cx, |this, cx| this.restart_fetch_models_task(cx)) + .ok(); + result + }) + } + + fn authenticate(&mut self, cx: &mut Context) -> Task> { + let api_url = OllamaLanguageModelProvider::api_url(cx); + let task = self.api_key_state.load_if_needed( + api_url, + &API_KEY_ENV_VAR, + |this| &mut this.api_key_state, + cx, + ); + + // Always try to fetch models - if no API key is needed (local Ollama), it will work + // If API key is needed and provided, it will work + // If API key is needed and not provided, it will fail gracefully + cx.spawn(async move |this, cx| { + let result = task.await; + this.update(cx, |this, cx| this.restart_fetch_models_task(cx)) + .ok(); + result + }) } fn fetch_models(&mut self, cx: &mut Context) -> Task> { - let settings = &AllLanguageModelSettings::get_global(cx).ollama; let http_client = Arc::clone(&self.http_client); - let api_url = settings.api_url.clone(); + let api_url = OllamaLanguageModelProvider::api_url(cx); + let api_key = self.api_key_state.key(&api_url); // As a proxy for the server being "authenticated", we'll check if its up by fetching the models cx.spawn(async move |this, cx| { - let models = get_models(http_client.as_ref(), &api_url, None).await?; + let models = + get_models(http_client.as_ref(), &api_url, api_key.as_deref(), None).await?; let tasks = models .into_iter() @@ -92,9 +137,12 @@ impl State { .map(|model| { let http_client = Arc::clone(&http_client); let api_url = api_url.clone(); + let api_key = api_key.clone(); async move { let name = model.name.as_str(); - let capabilities = show_model(http_client.as_ref(), &api_url, name).await?; + let capabilities = + show_model(http_client.as_ref(), &api_url, api_key.as_deref(), name) + .await?; let ollama_model = ollama::Model::new( name, None, @@ -119,7 +167,7 @@ impl State { ollama_models.sort_by(|a, b| a.name.cmp(&b.name)); this.update(cx, |this, cx| { - this.available_models = ollama_models; + this.fetched_models = ollama_models; cx.notify(); }) }) @@ -129,15 +177,6 @@ impl State { let task = self.fetch_models(cx); self.fetch_model_task.replace(task); } - - fn authenticate(&mut self, cx: &mut Context) -> Task> { - if self.is_authenticated() { - return Task::ready(Ok(())); - } - - let fetch_models_task = self.fetch_models(cx); - cx.spawn(async move |_this, _cx| Ok(fetch_models_task.await?)) - } } impl OllamaLanguageModelProvider { @@ -145,30 +184,47 @@ impl OllamaLanguageModelProvider { let this = Self { http_client: http_client.clone(), state: cx.new(|cx| { - let subscription = cx.observe_global::({ - let mut settings = AllLanguageModelSettings::get_global(cx).ollama.clone(); + cx.observe_global::({ + let mut last_settings = OllamaLanguageModelProvider::settings(cx).clone(); move |this: &mut State, cx| { - let new_settings = &AllLanguageModelSettings::get_global(cx).ollama; - if &settings != new_settings { - settings = new_settings.clone(); - this.restart_fetch_models_task(cx); + let current_settings = OllamaLanguageModelProvider::settings(cx); + let settings_changed = current_settings != &last_settings; + if settings_changed { + let url_changed = last_settings.api_url != current_settings.api_url; + last_settings = current_settings.clone(); + if url_changed { + this.fetched_models.clear(); + this.authenticate(cx).detach(); + } cx.notify(); } } - }); + }) + .detach(); State { http_client, - available_models: Default::default(), + fetched_models: Default::default(), fetch_model_task: None, - _subscription: subscription, + api_key_state: ApiKeyState::new(Self::api_url(cx)), } }), }; - this.state - .update(cx, |state, cx| state.restart_fetch_models_task(cx)); this } + + fn settings(cx: &App) -> &OllamaSettings { + &AllLanguageModelSettings::get_global(cx).ollama + } + + fn api_url(cx: &App) -> SharedString { + let api_url = &Self::settings(cx).api_url; + if api_url.is_empty() { + OLLAMA_API_URL.into() + } else { + SharedString::new(api_url.as_str()) + } + } } impl LanguageModelProviderState for OllamaLanguageModelProvider { @@ -208,16 +264,12 @@ impl LanguageModelProvider for OllamaLanguageModelProvider { let mut models: HashMap = HashMap::new(); // Add models from the Ollama API - for model in self.state.read(cx).available_models.iter() { + for model in self.state.read(cx).fetched_models.iter() { models.insert(model.name.clone(), model.clone()); } // Override with available models from settings - for model in AllLanguageModelSettings::get_global(cx) - .ollama - .available_models - .iter() - { + for model in &OllamaLanguageModelProvider::settings(cx).available_models { models.insert( model.name.clone(), ollama::Model { @@ -240,6 +292,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider { model, http_client: self.http_client.clone(), request_limiter: RateLimiter::new(4), + state: self.state.clone(), }) as Arc }) .collect::>(); @@ -267,7 +320,8 @@ impl LanguageModelProvider for OllamaLanguageModelProvider { } fn reset_credentials(&self, cx: &mut App) -> Task> { - self.state.update(cx, |state, cx| state.fetch_models(cx)) + self.state + .update(cx, |state, cx| state.set_api_key(None, cx)) } } @@ -276,6 +330,7 @@ pub struct OllamaLanguageModel { model: ollama::Model, http_client: Arc, request_limiter: RateLimiter, + state: gpui::Entity, } impl OllamaLanguageModel { @@ -454,15 +509,17 @@ impl LanguageModel for OllamaLanguageModel { let request = self.to_ollama_request(request); let http_client = self.http_client.clone(); - let Ok(api_url) = cx.update(|cx| { - let settings = &AllLanguageModelSettings::get_global(cx).ollama; - settings.api_url.clone() + let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| { + let api_url = OllamaLanguageModelProvider::api_url(cx); + (state.api_key_state.key(&api_url), api_url) }) else { return futures::future::ready(Err(anyhow!("App state dropped").into())).boxed(); }; let future = self.request_limiter.stream(async move { - let stream = stream_chat_completion(http_client.as_ref(), &api_url, request).await?; + let stream = + stream_chat_completion(http_client.as_ref(), &api_url, api_key.as_deref(), request) + .await?; let stream = map_to_language_model_completion_events(stream); Ok(stream) }); @@ -574,138 +631,305 @@ fn map_to_language_model_completion_events( } struct ConfigurationView { + api_key_editor: gpui::Entity, + api_url_editor: gpui::Entity, state: gpui::Entity, - loading_models_task: Option>, } impl ConfigurationView { pub fn new(state: gpui::Entity, window: &mut Window, cx: &mut Context) -> Self { - let loading_models_task = Some(cx.spawn_in(window, { - let state = state.clone(); - async move |this, cx| { - if let Some(task) = state - .update(cx, |state, cx| state.authenticate(cx)) - .log_err() - { - task.await.log_err(); - } - this.update(cx, |this, cx| { - this.loading_models_task = None; - cx.notify(); - }) - .log_err(); - } - })); + let api_key_editor = + cx.new(|cx| SingleLineInput::new(window, cx, "63e02e...").label("API key")); + + let api_url_editor = cx.new(|cx| { + let input = SingleLineInput::new(window, cx, OLLAMA_API_URL).label("API URL"); + input.set_text(OllamaLanguageModelProvider::api_url(cx), window, cx); + input + }); + + cx.observe(&state, |_, _, cx| { + cx.notify(); + }) + .detach(); Self { + api_key_editor, + api_url_editor, state, - loading_models_task, } } fn retry_connection(&self, cx: &mut App) { self.state - .update(cx, |state, cx| state.fetch_models(cx)) - .detach_and_log_err(cx); + .update(cx, |state, cx| state.restart_fetch_models_task(cx)); } -} -impl Render for ConfigurationView { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let is_authenticated = self.state.read(cx).is_authenticated(); + fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { + let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string(); + if api_key.is_empty() { + return; + } - let ollama_intro = - "Get up & running with Llama 3.3, Mistral, Gemma 2, and other LLMs with Ollama."; + // url changes can cause the editor to be displayed again + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); - if self.loading_models_task.is_some() { - div().child(Label::new("Loading models...")).into_any() - } else { + let state = self.state.clone(); + cx.spawn_in(window, async move |_, cx| { + state + .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))? + .await + }) + .detach_and_log_err(cx); + } + + fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context) { + self.api_key_editor + .update(cx, |input, cx| input.set_text("", window, cx)); + + let state = self.state.clone(); + cx.spawn_in(window, async move |_, cx| { + state + .update(cx, |state, cx| state.set_api_key(None, cx))? + .await + }) + .detach_and_log_err(cx); + + cx.notify(); + } + + fn save_api_url(&mut self, cx: &mut Context) { + let api_url = self.api_url_editor.read(cx).text(cx).trim().to_string(); + let current_url = OllamaLanguageModelProvider::api_url(cx); + if !api_url.is_empty() && &api_url != ¤t_url { + let fs = ::global(cx); + update_settings_file::(fs, cx, move |settings, _| { + if let Some(settings) = settings.ollama.as_mut() { + settings.api_url = Some(api_url); + } else { + settings.ollama = Some(crate::settings::OllamaSettingsContent { + api_url: Some(api_url), + available_models: None, + }); + } + }); + } + } + + fn reset_api_url(&mut self, window: &mut Window, cx: &mut Context) { + self.api_url_editor + .update(cx, |input, cx| input.set_text("", window, cx)); + let fs = ::global(cx); + update_settings_file::(fs, cx, |settings, _cx| { + if let Some(settings) = settings.ollama.as_mut() { + settings.api_url = Some(OLLAMA_API_URL.into()); + } + }); + cx.notify(); + } + + fn render_instructions() -> Div { + v_flex() + .gap_2() + .child(Label::new( + "Run LLMs locally on your machine with Ollama, or connect to an Ollama server. \ + Can provide access to Llama, Mistral, Gemma, and hundreds of other models.", + )) + .child(Label::new("To use local Ollama:")) + .child( + List::new() + .child(InstructionListItem::new( + "Download and install Ollama from", + Some("ollama.com"), + Some("https://ollama.com/download"), + )) + .child(InstructionListItem::text_only( + "Start Ollama and download a model: `ollama run gpt-oss:20b`", + )) + .child(InstructionListItem::text_only( + "Click 'Connect' below to start using Ollama in Zed", + )), + ) + .child(Label::new( + "Alternatively, you can connect to an Ollama server by specifying its \ + URL and API key (may not be required):", + )) + } + + fn render_api_key_editor(&self, cx: &Context) -> Div { + let state = self.state.read(cx); + let env_var_set = state.api_key_state.is_from_env_var(); + + if !state.api_key_state.has_key() { v_flex() - .gap_2() + .on_action(cx.listener(Self::save_api_key)) + .child(self.api_key_editor.clone()) + .child( + Label::new( + format!("You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.") + ) + .size(LabelSize::Small) + .color(Color::Muted), + ) + } else { + h_flex() + .p_3() + .justify_between() + .rounded_md() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().elevated_surface_background) .child( - v_flex().gap_1().child(Label::new(ollama_intro)).child( - List::new() - .child(InstructionListItem::text_only("Ollama must be running with at least one model installed to use it in the assistant.")) - .child(InstructionListItem::text_only( - "Once installed, try `ollama run llama3.2`", - )), - ), + h_flex() + .gap_2() + .child(Icon::new(IconName::Check).color(Color::Success)) + .child( + Label::new( + if env_var_set { + format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.") + } else { + "API key configured".to_string() + } + ) + ) ) + .child( + Button::new("reset-api-key", "Reset API Key") + .label_size(LabelSize::Small) + .icon(IconName::Undo) + .icon_size(IconSize::Small) + .icon_position(IconPosition::Start) + .layer(ElevationIndex::ModalSurface) + .when(env_var_set, |this| { + this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))) + }) + .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))), + ) + } + } + + fn render_api_url_editor(&self, cx: &Context) -> Div { + let api_url = OllamaLanguageModelProvider::api_url(cx); + let custom_api_url_set = api_url != OLLAMA_API_URL; + + if custom_api_url_set { + h_flex() + .p_3() + .justify_between() + .rounded_md() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().elevated_surface_background) .child( h_flex() - .w_full() - .justify_between() .gap_2() - .child( - h_flex() - .w_full() - .gap_2() - .map(|this| { - if is_authenticated { - this.child( - Button::new("ollama-site", "Ollama") - .style(ButtonStyle::Subtle) - .icon(IconName::ArrowUpRight) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .on_click(move |_, _, cx| cx.open_url(OLLAMA_SITE)) - .into_any_element(), - ) - } else { - this.child( - Button::new( - "download_ollama_button", - "Download Ollama", - ) + .child(Icon::new(IconName::Check).color(Color::Success)) + .child(v_flex().gap_1().child(Label::new(api_url))), + ) + .child( + Button::new("reset-api-url", "Reset API URL") + .label_size(LabelSize::Small) + .icon(IconName::Undo) + .icon_size(IconSize::Small) + .icon_position(IconPosition::Start) + .layer(ElevationIndex::ModalSurface) + .on_click( + cx.listener(|this, _, window, cx| this.reset_api_url(window, cx)), + ), + ) + } else { + v_flex() + .on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| { + this.save_api_url(cx); + cx.notify(); + })) + .gap_2() + .child(self.api_url_editor.clone()) + } + } +} + +impl Render for ConfigurationView { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let is_authenticated = self.state.read(cx).is_authenticated(); + + v_flex() + .gap_2() + .child(Self::render_instructions()) + .child(self.render_api_url_editor(cx)) + .child(self.render_api_key_editor(cx)) + .child( + h_flex() + .w_full() + .justify_between() + .gap_2() + .child( + h_flex() + .w_full() + .gap_2() + .map(|this| { + if is_authenticated { + this.child( + Button::new("ollama-site", "Ollama") + .style(ButtonStyle::Subtle) + .icon(IconName::ArrowUpRight) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .on_click(move |_, _, cx| cx.open_url(OLLAMA_SITE)) + .into_any_element(), + ) + } else { + this.child( + Button::new("download_ollama_button", "Download Ollama") .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) - .icon_size(IconSize::Small) + .icon_size(IconSize::XSmall) .icon_color(Color::Muted) .on_click(move |_, _, cx| { cx.open_url(OLLAMA_DOWNLOAD_URL) }) .into_any_element(), - ) - } - }) - .child( - Button::new("view-models", "View All Models") - .style(ButtonStyle::Subtle) - .icon(IconName::ArrowUpRight) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .on_click(move |_, _, cx| cx.open_url(OLLAMA_LIBRARY_URL)), - ), - ) - .map(|this| { - if is_authenticated { - this.child( - ButtonLike::new("connected") - .disabled(true) - .cursor_style(gpui::CursorStyle::Arrow) - .child( - h_flex() - .gap_2() - .child(Indicator::dot().color(Color::Success)) - .child(Label::new("Connected")) - .into_any_element(), - ), - ) - } else { - this.child( - Button::new("retry_ollama_models", "Connect") - .icon_position(IconPosition::Start) - .icon_size(IconSize::XSmall) - .icon(IconName::PlayFilled) - .on_click(cx.listener(move |this, _, _, cx| { + ) + } + }) + .child( + Button::new("view-models", "View All Models") + .style(ButtonStyle::Subtle) + .icon(IconName::ArrowUpRight) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .on_click(move |_, _, cx| cx.open_url(OLLAMA_LIBRARY_URL)), + ), + ) + .map(|this| { + if is_authenticated { + this.child( + ButtonLike::new("connected") + .disabled(true) + .cursor_style(gpui::CursorStyle::Arrow) + .child( + h_flex() + .gap_2() + .child(Icon::new(IconName::Check).color(Color::Success)) + .child(Label::new("Connected")) + .into_any_element(), + ), + ) + } else { + this.child( + Button::new("retry_ollama_models", "Connect") + .icon_position(IconPosition::Start) + .icon_size(IconSize::XSmall) + .icon(IconName::PlayOutlined) + .on_click( + cx.listener(move |this, _, _, cx| { this.retry_connection(cx) - })), - ) - } - }) - ) - .into_any() - } + }), + ), + ) + } + }), + ) } } diff --git a/crates/ollama/src/ollama.rs b/crates/ollama/src/ollama.rs index c61108d8bd59375256b7eb8b511527e8a0a119c2..dfafac02d8b25176f1aff5191487a12be914aab1 100644 --- a/crates/ollama/src/ollama.rs +++ b/crates/ollama/src/ollama.rs @@ -279,14 +279,19 @@ pub async fn complete( pub async fn stream_chat_completion( client: &dyn HttpClient, api_url: &str, + api_key: Option<&str>, request: ChatRequest, ) -> Result>> { let uri = format!("{api_url}/api/chat"); - let request_builder = http::Request::builder() + let mut request_builder = http::Request::builder() .method(Method::POST) .uri(uri) .header("Content-Type", "application/json"); + if let Some(api_key) = api_key { + request_builder = request_builder.header("Authorization", format!("Bearer {api_key}")) + } + let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; if response.status().is_success() { @@ -313,14 +318,19 @@ pub async fn stream_chat_completion( pub async fn get_models( client: &dyn HttpClient, api_url: &str, + api_key: Option<&str>, _: Option, ) -> Result> { let uri = format!("{api_url}/api/tags"); - let request_builder = HttpRequest::builder() + let mut request_builder = HttpRequest::builder() .method(Method::GET) .uri(uri) .header("Accept", "application/json"); + if let Some(api_key) = api_key { + request_builder = request_builder.header("Authorization", format!("Bearer {api_key}")); + } + let request = request_builder.body(AsyncBody::default())?; let mut response = client.send(request).await?; @@ -340,15 +350,25 @@ pub async fn get_models( } /// Fetch details of a model, used to determine model capabilities -pub async fn show_model(client: &dyn HttpClient, api_url: &str, model: &str) -> Result { +pub async fn show_model( + client: &dyn HttpClient, + api_url: &str, + api_key: Option<&str>, + model: &str, +) -> Result { let uri = format!("{api_url}/api/show"); - let request = HttpRequest::builder() + let mut request_builder = HttpRequest::builder() .method(Method::POST) .uri(uri) - .header("Content-Type", "application/json") - .body(AsyncBody::from( - serde_json::json!({ "model": model }).to_string(), - ))?; + .header("Content-Type", "application/json"); + + if let Some(api_key) = api_key { + request_builder = request_builder.header("Authorization", format!("Bearer {api_key}")) + } + + let request = request_builder.body(AsyncBody::from( + serde_json::json!({ "model": model }).to_string(), + ))?; let mut response = client.send(request).await?; let mut body = String::new(); diff --git a/docs/src/ai/llm-providers.md b/docs/src/ai/llm-providers.md index 98aaeef2126d559efa7696143faca13d39e11e62..09f67cc9c123a968705a834f9d1c5a2e855a782f 100644 --- a/docs/src/ai/llm-providers.md +++ b/docs/src/ai/llm-providers.md @@ -376,6 +376,20 @@ If the model is tagged with `thinking` in the Ollama catalog, set this option an The `supports_images` option enables the model's vision capabilities, allowing it to process images included in the conversation context. If the model is tagged with `vision` in the Ollama catalog, set this option and you can use it in Zed. +#### Ollama Authentication + +In addition to running Ollama on your own hardware, which generally does not require authentication, Zed also supports connecting to remote Ollama instances. API keys are required for authentication. + +One such service is [Ollama Turbo])(https://ollama.com/turbo). To configure Zed to use Ollama turbo: + +1. Sign in to your Ollama account and subscribe to Ollama Turbo +2. Visit [ollama.com/settings/keys](https://ollama.com/settings/keys) and create an API key +3. Open the settings view (`agent: open settings`) and go to the Ollama section +4. Paste your API key and press enter. +5. For the API URL enter `https://ollama.com` + +Zed will also use the `OLLAMA_API_KEY` environment variables if defined. + ### OpenAI {#openai} 1. Visit the OpenAI platform and [create an API key](https://platform.openai.com/account/api-keys) From 0fef17baa23130557813bd688ab0d3064bf4681e Mon Sep 17 00:00:00 2001 From: Vladimir Varankin Date: Mon, 15 Sep 2025 09:43:04 +0200 Subject: [PATCH 023/721] Hide BasedPyright banner in toolbar when dismissed (#38135) This PR fixes the `BasedPyrightBanner`, making sure the banner is completely hidden in the toolbar, when it was dismissed, or it's not installed. Without the fix, the banner still occupies some space in the toolbar, making the UI looks inconsistent when editing a Python file. The bug is **especially prominent** when the toolbar is hidden in the user's settings (see below). _Banner is shown_ Screenshot 2025-09-14 at 11 36 37 _Banner dismissed_ Screenshot 2025-09-14 at 11 36 44 _Banner dismissed (and the toolbar is hidden)_ Screenshot 2025-09-14 at 12 07 25 Closes n/a Release Notes: - Fixed the basedpyright onboarding banner --- crates/language_onboarding/src/python.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/language_onboarding/src/python.rs b/crates/language_onboarding/src/python.rs index 6b83b841e0488d67014cc090b6c741035e544e04..e715cb7c806f417980a93a62210c72ca8529fcb5 100644 --- a/crates/language_onboarding/src/python.rs +++ b/crates/language_onboarding/src/python.rs @@ -30,6 +30,10 @@ impl BasedPyrightBanner { _subscriptions: [subscription], } } + + fn onboarding_banner_enabled(&self) -> bool { + !self.dismissed && self.have_basedpyright + } } impl EventEmitter for BasedPyrightBanner {} @@ -38,7 +42,7 @@ impl Render for BasedPyrightBanner { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { div() .id("basedpyright-banner") - .when(!self.dismissed && self.have_basedpyright, |el| { + .when(self.onboarding_banner_enabled(), |el| { el.child( Banner::new() .child( @@ -81,6 +85,9 @@ impl ToolbarItemView for BasedPyrightBanner { _window: &mut ui::Window, cx: &mut Context, ) -> ToolbarItemLocation { + if !self.onboarding_banner_enabled() { + return ToolbarItemLocation::Hidden; + } if let Some(item) = active_pane_item && let Some(editor) = item.act_as::(cx) && let Some(path) = editor.update(cx, |editor, cx| editor.target_file_abs_path(cx)) From 5b1c87b6a6006b69779f8ea048d0f691bc19e085 Mon Sep 17 00:00:00 2001 From: Tim Vermeulen Date: Mon, 15 Sep 2025 09:52:56 +0200 Subject: [PATCH 024/721] Fix incorrect ANSI color contrast adjustment on some background colors (#38155) The `Hsla` -> `Rgba` conversion sometimes results in negative (but very close to 0) color components due to floating point imprecision, causing the `.powf(constants.main_trc)` computations in the `srgb_to_y` function to evaluate to `NaN`. This propagates to `apca_contrast` which then makes `ensure_minimum_contrast` unconditionally return `black` for certain background colors. This PR addresses this by clamping the rgba components in `impl From for Rgba` to 0-1. Before/after: before after Release Notes: - Fixed an issue where ANSI colors were incorrectly adjusted to improve contrast on some background colors --- crates/gpui/src/color.rs | 6 +++--- crates/ui/src/utils/apca_contrast.rs | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index c6c4cdc77b133105a7c233417efae06d5c7aa220..b84f8699e38f015232313e49dd748a2b049c146d 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -151,9 +151,9 @@ impl From for Rgba { }; Rgba { - r, - g, - b, + r: r.clamp(0., 1.), + g: g.clamp(0., 1.), + b: b.clamp(0., 1.), a: color.a, } } diff --git a/crates/ui/src/utils/apca_contrast.rs b/crates/ui/src/utils/apca_contrast.rs index 522dca3e91341adf9056b3d03e3b5536cfcdc695..341b44670d867072ef7f7ac10fba098128cdc8af 100644 --- a/crates/ui/src/utils/apca_contrast.rs +++ b/crates/ui/src/utils/apca_contrast.rs @@ -393,6 +393,13 @@ mod tests { ); } + #[test] + fn test_srgb_to_y_nan_issue() { + let dark_red = hsla_from_hex(0x5f0000); + let y_dark_red = srgb_to_y(dark_red, &APCAConstants::default()); + assert!(!y_dark_red.is_nan()); + } + #[test] fn test_ensure_minimum_contrast() { let white_bg = hsla(0.0, 0.0, 1.0, 1.0); From 4d54ccf4947ef75727888890c224f02e9585f0ca Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 15 Sep 2025 10:30:46 +0200 Subject: [PATCH 025/721] agent_servers: Let Gemini CLI know it is running in Zed (#38058) By passing through Zed as the surface, Gemini can know which editor it is running in. Release Notes: - N/A --- crates/agent_servers/src/gemini.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index 6a668785943239f28b4bc9aafce9d37fdfa386d2..e897bbd747b2ba7cf3947ed5cd3e594408bc1c2a 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -44,6 +44,7 @@ impl AgentServer for Gemini { cx.spawn(async move |cx| { let mut extra_env = HashMap::default(); + extra_env.insert("SURFACE".to_owned(), "zed".to_owned()); if let Some(api_key) = cx .update(GoogleLanguageModelProvider::api_key_for_gemini_cli)? .await From 14f4e867aad291131719f2f13684d17690cb5a6d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 15 Sep 2025 11:43:05 +0200 Subject: [PATCH 026/721] terminal: Do not auto close shell terminals if they error out (#38180) cc https://github.com/zed-industries/zed/issues/38134 Release Notes: - N/A --- crates/terminal/src/terminal.rs | 12 +++++++++--- crates/terminal_view/src/terminal_panel.rs | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d926faaf484b84dc7cf17b1ef0b816f5773ff02c..a07aef5f7b4da90373bcbf7c406dd8277cb09387 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -25,7 +25,7 @@ use alacritty_terminal::{ ClearMode, CursorStyle as AlacCursorStyle, Handler, NamedPrivateMode, PrivateMode, }, }; -use anyhow::{Result, bail}; +use anyhow::{Context as _, Result, bail}; use futures::{ FutureExt, @@ -486,7 +486,8 @@ impl TerminalBuilder { pty, pty_options.drain_on_exit, false, - )?; + ) + .context("failed to create event loop")?; //Kick things off let pty_tx = event_loop.channel(); @@ -528,6 +529,7 @@ impl TerminalBuilder { max_scroll_history_lines, window_id, }, + child_exited: None, }; if !activation_script.is_empty() && no_task { @@ -726,6 +728,7 @@ pub struct Terminal { shell_program: Option, template: CopyTemplate, activation_script: Vec, + child_exited: Option, } struct CopyTemplate { @@ -1921,10 +1924,13 @@ impl Terminal { if let Some(tx) = &self.completion_tx { tx.try_send(e).ok(); } + if let Some(e) = e { + self.child_exited = Some(e); + } let task = match &mut self.task { Some(task) => task, None => { - if error_code.is_none() { + if self.child_exited.is_none_or(|e| e.code() == Some(0)) { cx.emit(Event::CloseTerminal); } return; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5308b230f78b334c9a39cadbcbb3117ac4e3e11f..6c93644f992dcd5d3c0126a28c9aa8b8bab020d3 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -471,7 +471,7 @@ impl TerminalPanel { }) .ok()? .await - .ok()?; + .log_err()?; panel .update_in(cx, move |terminal_panel, window, cx| { @@ -769,7 +769,7 @@ impl TerminalPanel { }) } - pub fn add_terminal_shell( + fn add_terminal_shell( &mut self, cwd: Option, reveal_strategy: RevealStrategy, @@ -779,7 +779,7 @@ impl TerminalPanel { let workspace = self.workspace.clone(); cx.spawn_in(window, async move |terminal_panel, cx| { if workspace.update(cx, |workspace, cx| !is_enabled_in_workspace(workspace, cx))? { - anyhow::bail!("terminal not yet supported for remote projects"); + anyhow::bail!("terminal not yet supported for collaborative projects"); } let pane = terminal_panel.update(cx, |terminal_panel, _| { terminal_panel.pending_terminals_to_add += 1; From cfb2925169b00f94f2554dfc4d7b717eebf898df Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 15 Sep 2025 02:47:27 -0700 Subject: [PATCH 027/721] macOS: Disable NSAutoFillHeuristicController on macOS 26 (#38179) Closes #33182 From https://github.com/zed-industries/zed/issues/33182#issuecomment-3289846957, thanks @mitchellh. Release Notes: - Fixed an issue where scrolling could sometimes feel choppy on macOS 26. --- crates/gpui/src/platform/mac/platform.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index dea04d89a06acac526a8b033681829fdc1e148fd..9bb1993f13b0444d7bec2721f160b81509629106 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -82,6 +82,10 @@ unsafe fn build_classes() { APP_DELEGATE_CLASS = unsafe { let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); + decl.add_method( + sel!(applicationWillFinishLaunching:), + will_finish_launching as extern "C" fn(&mut Object, Sel, id), + ); decl.add_method( sel!(applicationDidFinishLaunching:), did_finish_launching as extern "C" fn(&mut Object, Sel, id), @@ -1356,6 +1360,25 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { } } +extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) { + unsafe { + let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults]; + let defaults_dict: id = msg_send![class!(NSMutableDictionary), dictionary]; + + // The autofill heuristic controller causes slowdown and high CPU usage. + // We don't know exactly why. This disables the full heuristic controller. + // + // Adapted from: https://github.com/ghostty-org/ghostty/pull/8625 + let false_value: id = msg_send![class!(NSNumber), numberWithBool:false]; + let _: () = msg_send![defaults_dict, + setObject: false_value + forKey: ns_string("NSAutoFillHeuristicControllerEnabled") + ]; + + let _: () = msg_send![user_defaults, registerDefaults:defaults_dict]; + } +} + extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; From 4a582504d4aab388c6fa49ab74056e1a7eccfe94 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Mon, 15 Sep 2025 11:53:33 +0200 Subject: [PATCH 028/721] ui: Follow-up improvements to the scrollbar component (#38178) This PR lands some more improvements to the reworked scrollbars. Namely, we will now explicitly paint a background in cases where a track is requested for the specific scrollbar, which prevents a flicker, and also reserve space only if space actually needs to be reserved. The latter was a regression introduced by the recent changes. Release Notes: - N/A --- crates/git_ui/src/git_panel.rs | 5 +- crates/outline_panel/src/outline_panel.rs | 5 +- crates/project_panel/src/project_panel.rs | 5 +- crates/terminal_view/src/terminal_view.rs | 5 +- crates/ui/src/components/scrollbar.rs | 84 +++++++++++++++++------ 5 files changed, 80 insertions(+), 24 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 3063206723021d00aa21e0f8163b4fe3b941fcb7..e8306efc0fe6f6c554f16d3b873a0fd3c659c0b7 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3748,7 +3748,10 @@ impl GitPanel { .custom_scrollbars( Scrollbars::for_settings::() .tracked_scroll_handle(self.scroll_handle.clone()) - .with_track_along(ScrollAxes::Horizontal), + .with_track_along( + ScrollAxes::Horizontal, + cx.theme().colors().panel_background, + ), window, cx, ), diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 8f501b2f970019c24c36e65fa94099b80454dfe2..5b1c5b313d29751e53e08bd479eeb72d15527373 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -4692,7 +4692,10 @@ impl OutlinePanel { .custom_scrollbars( Scrollbars::for_settings::() .tracked_scroll_handle(self.scroll_handle.clone()) - .with_track_along(ScrollAxes::Horizontal) + .with_track_along( + ScrollAxes::Horizontal, + cx.theme().colors().panel_background, + ) .tracked_entity(cx.entity_id()), window, cx, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b53dfe6fdf8bdf81761299c075eee302a06a8956..aed3b44515554299b3d50fbcf5e2b58123495908 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5601,7 +5601,10 @@ impl Render for ProjectPanel { .custom_scrollbars( Scrollbars::for_settings::() .tracked_scroll_handle(self.scroll_handle.clone()) - .with_track_along(ScrollAxes::Horizontal) + .with_track_along( + ScrollAxes::Horizontal, + cx.theme().colors().panel_background, + ) .notify_content(), window, cx, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 5fdefc6a66224587bc746d21ecddb1a66a2bbe4f..133bde6175c02e6f8845d83c765f7820b77e37f7 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1119,7 +1119,10 @@ impl Render for TerminalView { div.custom_scrollbars( Scrollbars::for_settings::() .show_along(ScrollAxes::Vertical) - .with_track_along(ScrollAxes::Vertical) + .with_track_along( + ScrollAxes::Vertical, + cx.theme().colors().editor_background, + ) .tracked_scroll_handle(self.scroll_handle.clone()), window, cx, diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index f3cc37d2f55356f05af3f1644dc4292a6add2660..6885a11c51647aafbf1e3136060d52105e3664b7 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -30,7 +30,7 @@ pub mod scrollbars { /// When to show the scrollbar in the editor. /// /// Default: auto - #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { /// Show the scrollbar if there's important information or @@ -39,6 +39,7 @@ pub mod scrollbars { /// Match the system's configured behavior. System, /// Always show the scrollbar. + #[default] Always, /// Never show the scrollbar. Never, @@ -302,7 +303,7 @@ enum ReservedSpace { #[default] None, Thumb, - Track, + Track(Hsla), } impl ReservedSpace { @@ -311,7 +312,14 @@ impl ReservedSpace { } fn needs_scroll_track(&self) -> bool { - *self == ReservedSpace::Track + matches!(self, ReservedSpace::Track(_)) + } + + fn track_color(&self) -> Option { + match self { + ReservedSpace::Track(color) => Some(*color), + _ => None, + } } } @@ -333,20 +341,25 @@ impl ScrollbarWidth { } } +#[derive(Clone)] +enum Handle { + Tracked(T), + Untracked(fn() -> T), +} + #[derive(Clone)] pub struct Scrollbars { id: Option, get_visibility: fn(&App) -> ShowScrollbar, tracked_entity: Option>, - scrollable_handle: T, - handle_was_added: bool, + scrollable_handle: Handle, visibility: Point, scrollbar_width: ScrollbarWidth, } impl Scrollbars { pub fn new(show_along: ScrollAxes) -> Self { - Self::new_with_setting(show_along, |_| ShowScrollbar::Always) + Self::new_with_setting(show_along, |_| ShowScrollbar::default()) } pub fn for_settings() -> Scrollbars { @@ -359,8 +372,7 @@ impl Scrollbars { Self { id: None, get_visibility, - handle_was_added: false, - scrollable_handle: ScrollHandle::new(), + scrollable_handle: Handle::Untracked(ScrollHandle::new), tracked_entity: None, visibility: show_along.apply_to(Default::default(), ReservedSpace::Thumb), scrollbar_width: ScrollbarWidth::Normal, @@ -407,8 +419,7 @@ impl Scrollbars { } = self; Scrollbars { - handle_was_added: true, - scrollable_handle: tracked_scroll_handle, + scrollable_handle: Handle::Tracked(tracked_scroll_handle), id, tracked_entity: tracked_entity_id, visibility, @@ -422,8 +433,8 @@ impl Scrollbars { self } - pub fn with_track_along(mut self, along: ScrollAxes) -> Self { - self.visibility = along.apply_to(self.visibility, ReservedSpace::Track); + pub fn with_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self { + self.visibility = along.apply_to(self.visibility, ReservedSpace::Track(background_color)); self } @@ -499,12 +510,17 @@ impl ScrollbarState { cx.observe_global_in::(window, Self::settings_changed) .detach(); + let (manually_added, scroll_handle) = match config.scrollable_handle { + Handle::Tracked(handle) => (true, handle), + Handle::Untracked(func) => (false, func()), + }; + let show_setting = (config.get_visibility)(cx); ScrollbarState { thumb_state: Default::default(), notify_id: config.tracked_entity.map(|id| id.unwrap_or(parent_id)), - manually_added: config.handle_was_added, - scroll_handle: config.scrollable_handle, + manually_added, + scroll_handle, width: config.scrollbar_width, visibility: config.visibility, show_setting, @@ -531,8 +547,10 @@ impl ScrollbarState { .await; scrollbar_state .update(cx, |state, cx| { - state.set_visibility(VisibilityState::Hidden, cx); - state._auto_hide_task.take() + if state.thumb_state == ThumbState::Inactive { + state.set_visibility(VisibilityState::Hidden, cx); + } + state._auto_hide_task.take(); }) .log_err(); }) @@ -578,8 +596,15 @@ impl ScrollbarState { } fn space_to_reserve_for(&self, axis: ScrollbarAxis) -> Option { - (self.show_state.is_disabled().not() && self.visibility.along(axis).needs_scroll_track()) - .then(|| self.space_to_reserve()) + (self.show_state.is_disabled().not() + && self.visibility.along(axis).needs_scroll_track() + && self + .scroll_handle() + .max_offset() + .along(axis) + .is_zero() + .not()) + .then(|| self.space_to_reserve()) } fn space_to_reserve(&self) -> Pixels { @@ -643,7 +668,8 @@ impl ScrollbarState { if state == ThumbState::Inactive { self.schedule_auto_hide(window, cx); } else { - self.show_scrollbars(window, cx); + self.set_visibility(VisibilityState::Visible, cx); + self._auto_hide_task.take(); } self.thumb_state = state; cx.notify(); @@ -848,6 +874,7 @@ struct ScrollbarLayout { track_bounds: Bounds, cursor_hitbox: Hitbox, reserved_space: ReservedSpace, + track_background: Option<(Bounds, Hsla)>, axis: ScrollbarAxis, } @@ -1035,6 +1062,9 @@ impl Element for ScrollbarElement { }, HitboxBehavior::BlockMouseExceptScroll, ), + track_background: reserved_space + .track_color() + .map(|color| (padded_bounds.dilate(SCROLLBAR_PADDING), color)), reserved_space, } }) @@ -1076,6 +1106,7 @@ impl Element for ScrollbarElement { cursor_hitbox, axis, reserved_space, + track_background, .. } in &prepaint_state.thumbs { @@ -1092,7 +1123,9 @@ impl Element for ScrollbarElement { }; let blending_color = if hovered || reserved_space.needs_scroll_track() { - colors.surface_background + track_background + .map(|(_, background)| background) + .unwrap_or(colors.surface_background) } else { let blend_color = colors.surface_background; blend_color.min(blend_color.alpha(MAXIMUM_OPACITY)) @@ -1100,6 +1133,17 @@ impl Element for ScrollbarElement { let thumb_background = blending_color.blend(thumb_base_color); + if let Some((track_bounds, color)) = track_background { + window.paint_quad(quad( + *track_bounds, + Corners::default(), + *color, + Edges::default(), + Hsla::transparent_black(), + BorderStyle::default(), + )); + } + window.paint_quad(quad( *thumb_bounds, Corners::all(Pixels::MAX).clamp_radii_for_quad_size(thumb_bounds.size), From 393d6787a3d45125e60fbbe708c130dd54982f4e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 15 Sep 2025 12:09:25 +0200 Subject: [PATCH 029/721] terminal: Do not auto close shell terminals if they error out (#38182) Closes https://github.com/zed-industries/zed/issues/38134 This also reduces an annoying level of shell nesting Release Notes: - N/A --- crates/remote/src/remote_client.rs | 26 ++-- crates/remote/src/transport/ssh.rs | 212 +++++++++++++++++++++-------- 2 files changed, 165 insertions(+), 73 deletions(-) diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index 501c6a8dd639630b1930cb32e804f8cca658a9ca..0ccea81ec69425554ad8faf7d63e528728403594 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -769,13 +769,11 @@ impl RemoteClient { } pub fn shell(&self) -> Option { - Some(self.state.as_ref()?.remote_connection()?.shell()) + Some(self.remote_connection()?.shell()) } pub fn shares_network_interface(&self) -> bool { - self.state - .as_ref() - .and_then(|state| state.remote_connection()) + self.remote_connection() .map_or(false, |connection| connection.shares_network_interface()) } @@ -787,12 +785,8 @@ impl RemoteClient { working_dir: Option, port_forward: Option<(u16, String, u16)>, ) -> Result { - let Some(connection) = self - .state - .as_ref() - .and_then(|state| state.remote_connection()) - else { - return Err(anyhow!("no connection")); + let Some(connection) = self.remote_connection() else { + return Err(anyhow!("no ssh connection")); }; connection.build_command(program, args, env, working_dir, port_forward) } @@ -803,11 +797,7 @@ impl RemoteClient { dest_path: RemotePathBuf, cx: &App, ) -> Task> { - let Some(connection) = self - .state - .as_ref() - .and_then(|state| state.remote_connection()) - else { + let Some(connection) = self.remote_connection() else { return Task::ready(Err(anyhow!("no ssh connection"))); }; connection.upload_directory(src_path, dest_path, cx) @@ -916,6 +906,12 @@ impl RemoteClient { .unwrap() .unwrap() } + + fn remote_connection(&self) -> Option> { + self.state + .as_ref() + .and_then(|state| state.remote_connection()) + } } enum ConnectionPoolEntry { diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 42c6da04b5c39b0b1133b2d13549585fa9433ef7..932cb7145e4404c4529c483562e626205831d145 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -113,64 +113,24 @@ impl RemoteConnection for SshRemoteConnection { working_dir: Option, port_forward: Option<(u16, String, u16)>, ) -> Result { - use std::fmt::Write as _; - - let mut script = String::new(); - if let Some(working_dir) = working_dir { - let working_dir = - RemotePathBuf::new(working_dir.into(), self.ssh_path_style).to_string(); - - // shlex will wrap the command in single quotes (''), disabling ~ expansion, - // replace ith with something that works - const TILDE_PREFIX: &'static str = "~/"; - let working_dir = if working_dir.starts_with(TILDE_PREFIX) { - let working_dir = working_dir.trim_start_matches("~").trim_start_matches("/"); - format!("$HOME/{working_dir}") - } else { - working_dir - }; - write!(&mut script, "cd \"{working_dir}\"; ",).unwrap(); - } else { - write!(&mut script, "cd; ").unwrap(); - }; - - for (k, v) in input_env.iter() { - if let Some((k, v)) = shlex::try_quote(k).ok().zip(shlex::try_quote(v).ok()) { - write!(&mut script, "{}={} ", k, v).unwrap(); - } - } - - let shell = &self.ssh_shell; - - if let Some(input_program) = input_program { - let command = shlex::try_quote(&input_program)?; - script.push_str(&command); - for arg in input_args { - let arg = shlex::try_quote(&arg)?; - script.push_str(" "); - script.push_str(&arg); - } - } else { - write!(&mut script, "exec {shell} -l").unwrap(); - }; - - let shell_invocation = format!("{shell} -c {}", shlex::try_quote(&script).unwrap()); - - let mut args = Vec::new(); - args.extend(self.socket.ssh_args()); - - if let Some((local_port, host, remote_port)) = port_forward { - args.push("-L".into()); - args.push(format!("{local_port}:{host}:{remote_port}")); - } - - args.push("-t".into()); - args.push(shell_invocation); - Ok(CommandTemplate { - program: "ssh".into(), - args, - env: self.socket.envs.clone(), - }) + let Self { + ssh_path_style, + socket, + ssh_shell, + .. + } = self; + let env = socket.envs.clone(); + build_command( + input_program, + input_args, + input_env, + working_dir, + port_forward, + env, + *ssh_path_style, + ssh_shell, + socket.ssh_args(), + ) } fn upload_directory( @@ -1037,3 +997,139 @@ impl SshConnectionOptions { } } } + +fn build_command( + input_program: Option, + input_args: &[String], + input_env: &HashMap, + working_dir: Option, + port_forward: Option<(u16, String, u16)>, + ssh_env: HashMap, + ssh_path_style: PathStyle, + ssh_shell: &str, + ssh_args: Vec, +) -> Result { + use std::fmt::Write as _; + + let mut exec = String::from("exec env -C "); + if let Some(working_dir) = working_dir { + let working_dir = RemotePathBuf::new(working_dir.into(), ssh_path_style).to_string(); + + // shlex will wrap the command in single quotes (''), disabling ~ expansion, + // replace with with something that works + const TILDE_PREFIX: &'static str = "~/"; + if working_dir.starts_with(TILDE_PREFIX) { + let working_dir = working_dir.trim_start_matches("~").trim_start_matches("/"); + write!(exec, "\"$HOME/{working_dir}\" ",).unwrap(); + } else { + write!(exec, "\"{working_dir}\" ",).unwrap(); + } + } else { + write!(exec, "\"$HOME\" ").unwrap(); + }; + + for (k, v) in input_env.iter() { + if let Some((k, v)) = shlex::try_quote(k).ok().zip(shlex::try_quote(v).ok()) { + write!(exec, "{}={} ", k, v).unwrap(); + } + } + + write!(exec, "{ssh_shell} ").unwrap(); + if let Some(input_program) = input_program { + let mut script = shlex::try_quote(&input_program)?.into_owned(); + for arg in input_args { + let arg = shlex::try_quote(&arg)?; + script.push_str(" "); + script.push_str(&arg); + } + write!(exec, "-c {}", shlex::try_quote(&script).unwrap()).unwrap(); + } else { + write!(exec, "-l").unwrap(); + }; + + let mut args = Vec::new(); + args.extend(ssh_args); + + if let Some((local_port, host, remote_port)) = port_forward { + args.push("-L".into()); + args.push(format!("{local_port}:{host}:{remote_port}")); + } + + args.push("-t".into()); + args.push(exec); + Ok(CommandTemplate { + program: "ssh".into(), + args, + env: ssh_env, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_command() -> Result<()> { + let mut input_env = HashMap::default(); + input_env.insert("INPUT_VA".to_string(), "val".to_string()); + let mut env = HashMap::default(); + env.insert("SSH_VAR".to_string(), "ssh-val".to_string()); + + let command = build_command( + Some("remote_program".to_string()), + &["arg1".to_string(), "arg2".to_string()], + &input_env, + Some("~/work".to_string()), + None, + env.clone(), + PathStyle::Posix, + "/bin/fish", + vec!["-p".to_string(), "2222".to_string()], + )?; + + assert_eq!(command.program, "ssh"); + assert_eq!( + command.args.iter().map(String::as_str).collect::>(), + [ + "-p", + "2222", + "-t", + "exec env -C \"$HOME/work\" INPUT_VA=val /bin/fish -c 'remote_program arg1 arg2'" + ] + ); + assert_eq!(command.env, env); + + let mut input_env = HashMap::default(); + input_env.insert("INPUT_VA".to_string(), "val".to_string()); + let mut env = HashMap::default(); + env.insert("SSH_VAR".to_string(), "ssh-val".to_string()); + + let command = build_command( + None, + &["arg1".to_string(), "arg2".to_string()], + &input_env, + None, + Some((1, "foo".to_owned(), 2)), + env.clone(), + PathStyle::Posix, + "/bin/fish", + vec!["-p".to_string(), "2222".to_string()], + )?; + + assert_eq!(command.program, "ssh"); + assert_eq!( + command.args.iter().map(String::as_str).collect::>(), + [ + "-p", + "2222", + "-L", + "1:foo:2", + "-t", + "exec env -C \"$HOME\" INPUT_VA=val /bin/fish -l" + ] + ); + assert_eq!(command.env, env); + + Ok(()) + } +} From 989adde57b4024e2c3c4e255498a6df93f69ce99 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Mon, 15 Sep 2025 12:17:27 +0200 Subject: [PATCH 030/721] Add scrollbars to markdown preview and syntax tree view (#38183) Closes https://github.com/zed-industries/zed/issues/38141 This PR adds default scrollbars to the markdown preview and syntax tree view. Release Notes: - Added scrollbars to the markdown preview and syntax tree view. --- crates/language_tools/src/syntax_tree_view.rs | 8 ++++++-- crates/markdown_preview/src/markdown_preview_view.rs | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 5700d8d487e990937597295fb5bab761a46f2ba3..5a110019ddb229c6b4111663b46b64895befb8ff 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -12,7 +12,8 @@ use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; use ui::{ ButtonCommon, ButtonLike, Clickable, Color, ContextMenu, FluentBuilder as _, IconButton, - IconName, Label, LabelCommon, LabelSize, PopoverMenu, StyledExt, Tooltip, h_flex, v_flex, + IconName, Label, LabelCommon, LabelSize, PopoverMenu, StyledExt, Tooltip, WithScrollbar, + h_flex, v_flex, }; use workspace::{ Event as WorkspaceEvent, SplitDirection, ToolbarItemEvent, ToolbarItemLocation, @@ -487,7 +488,7 @@ impl SyntaxTreeView { } impl Render for SyntaxTreeView { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { div() .flex_1() .bg(cx.theme().colors().editor_background) @@ -512,6 +513,8 @@ impl Render for SyntaxTreeView { .text_bg(cx.theme().colors().background) .into_any_element(), ) + .vertical_scrollbar_for(self.list_scroll_handle.clone(), window, cx) + .into_any_element() } else { let inner_content = v_flex() .items_center() @@ -540,6 +543,7 @@ impl Render for SyntaxTreeView { .size_full() .justify_center() .child(inner_content) + .into_any_element() } }) } diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 1121d64655f6c7e02f0b0d621605c9ba1aae7cde..d20ed40b7928186e2caf564be5ff66b0bd04f0d1 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -13,7 +13,7 @@ use gpui::{ use language::LanguageRegistry; use settings::Settings; use theme::ThemeSettings; -use ui::prelude::*; +use ui::{WithScrollbar, prelude::*}; use workspace::item::{Item, ItemHandle}; use workspace::{Pane, Workspace}; @@ -481,7 +481,7 @@ impl Item for MarkdownPreviewView { } impl Render for MarkdownPreviewView { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let buffer_size = ThemeSettings::get_global(cx).buffer_font_size(cx); let buffer_line_height = ThemeSettings::get_global(cx).buffer_line_height; @@ -598,5 +598,6 @@ impl Render for MarkdownPreviewView { .size_full(), ) })) + .vertical_scrollbar_for(self.list_state.clone(), window, cx) } } From e9b4f59e0f2b732424d830b3c49119fd3c233b59 Mon Sep 17 00:00:00 2001 From: Hakan Ensari Date: Mon, 15 Sep 2025 12:20:27 +0200 Subject: [PATCH 031/721] Fix external agent authentication with spaces in paths (#38175) This fixes terminal-based authentication for external ACP agents (Claude Code, Gemini CLI) when file paths contain spaces, like "Application Support" on macOS and "Program Files" on Windows. When users click authentication buttons or type `/login`, they get errors like `Cannot find module '/Users/username/Library/Application'` because the path gets split at the space. The fix removes redundant `shlex::try_quote` calls from `spawn_external_agent_login`. These were causing double-quoting since the terminal spawning code already handles proper shell escaping. Added a test to verify paths with spaces aren't pre-quoted. Release Notes: - Fixed external agent authentication failures when file paths contain spaces --------- Co-authored-by: Hakan Ensari Co-authored-by: Claude --- Cargo.lock | 1 - crates/agent_ui/Cargo.toml | 1 - crates/agent_ui/src/acp/thread_view.rs | 32 +++++++++++++++----------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14ec0e21db3a1893002c6277c43b9d1b996ab086..2b01b45f637dd2e51802b91cec62f05b2f3f55fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,7 +416,6 @@ dependencies = [ "serde_json", "serde_json_lenient", "settings", - "shlex", "smol", "streaming_diff", "task", diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml index 028db95c10a8c7a319bb05927dcabd0564a14683..47d9f6d6a27a2ad5102e831094912208e66a9b43 100644 --- a/crates/agent_ui/Cargo.toml +++ b/crates/agent_ui/Cargo.toml @@ -80,7 +80,6 @@ serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true settings.workspace = true -shlex.workspace = true smol.workspace = true streaming_diff.workspace = true task.workspace = true diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index d3986155d741393a01bce829a886aba4ee497948..ceed3f8f98fbf95f5b4278d8354e30e90299b18d 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -9,7 +9,7 @@ use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, AgentServerDelegate}; use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting}; use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer}; -use anyhow::{Context as _, Result, anyhow, bail}; +use anyhow::{Result, anyhow, bail}; use arrayvec::ArrayVec; use audio::{Audio, Sound}; use buffer_diff::BufferDiff; @@ -1584,19 +1584,6 @@ impl AcpThreadView { window.spawn(cx, async move |cx| { let mut task = login.clone(); - task.command = task - .command - .map(|command| anyhow::Ok(shlex::try_quote(&command)?.to_string())) - .transpose()?; - task.args = task - .args - .iter() - .map(|arg| { - Ok(shlex::try_quote(arg) - .context("Failed to quote argument")? - .to_string()) - }) - .collect::>>()?; task.full_label = task.label.clone(); task.id = task::TaskId(format!("external-agent-{}-login", task.label)); task.command_label = task.label.clone(); @@ -5680,6 +5667,23 @@ pub(crate) mod tests { }); } + #[gpui::test] + async fn test_spawn_external_agent_login_handles_spaces(cx: &mut TestAppContext) { + init_test(cx); + + // Verify paths with spaces aren't pre-quoted + let path_with_spaces = "/Users/test/Library/Application Support/Zed/cli.js"; + let login_task = task::SpawnInTerminal { + command: Some("node".to_string()), + args: vec![path_with_spaces.to_string(), "/login".to_string()], + ..Default::default() + }; + + // Args should be passed as-is, not pre-quoted + assert!(!login_task.args[0].starts_with('"')); + assert!(!login_task.args[0].starts_with('\'')); + } + #[gpui::test] async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) { init_test(cx); From 92b946e8e5d340fc44e4ac4ac82fadd71868fab7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 15 Sep 2025 14:43:41 +0200 Subject: [PATCH 032/721] acp_thread: Properly use `project` terminal API (#38186) Closes https://github.com/zed-industries/zed/issues/35603 Release Notes: - Fixed shell selection for terminal tool --- Cargo.lock | 2 - crates/acp_thread/Cargo.toml | 1 - crates/acp_thread/src/acp_thread.rs | 57 +++++-------- crates/acp_thread/src/terminal.rs | 4 +- crates/assistant_tools/Cargo.toml | 1 - crates/assistant_tools/src/assistant_tools.rs | 2 +- crates/assistant_tools/src/terminal_tool.rs | 81 +++++++------------ crates/languages/src/python.rs | 6 +- crates/project/src/terminals.rs | 66 ++++++++------- crates/remote/src/remote_client.rs | 9 +++ crates/remote/src/transport/ssh.rs | 7 ++ crates/remote/src/transport/wsl.rs | 12 ++- crates/task/src/shell_builder.rs | 37 +++++++-- 13 files changed, 148 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b01b45f637dd2e51802b91cec62f05b2f3f55fd..caeb7e714da6c7f2c689d51e82500cc69d68f35d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,7 +39,6 @@ dependencies = [ "util", "uuid", "watch", - "which 6.0.3", "workspace-hack", ] @@ -1023,7 +1022,6 @@ dependencies = [ "util", "watch", "web_search", - "which 6.0.3", "workspace", "workspace-hack", "zlog", diff --git a/crates/acp_thread/Cargo.toml b/crates/acp_thread/Cargo.toml index a0bbda848f9ec761aebdf66b644a8b2926685122..ac24a6ed0f41c75d5c4dcd9b9b4122336022ddf3 100644 --- a/crates/acp_thread/Cargo.toml +++ b/crates/acp_thread/Cargo.toml @@ -45,7 +45,6 @@ url.workspace = true util.workspace = true uuid.workspace = true watch.workspace = true -which.workspace = true workspace-hack.workspace = true [dev-dependencies] diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index afbb4781f61d5ccf1ea753df1fd0379e533e8e46..c89b742742536308a6064fe9ddabff3f8e73c341 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -7,12 +7,12 @@ use agent_settings::AgentSettings; use collections::HashSet; pub use connection::*; pub use diff::*; -use futures::future::Shared; use language::language_settings::FormatOnSave; pub use mention::*; use project::lsp_store::{FormatTrigger, LspFormatTarget}; use serde::{Deserialize, Serialize}; use settings::Settings as _; +use task::{Shell, ShellBuilder}; pub use terminal::*; use action_log::ActionLog; @@ -34,7 +34,7 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt::Display, mem, path::PathBuf, sync::Arc}; use ui::App; -use util::{ResultExt, get_system_shell}; +use util::{ResultExt, get_default_system_shell}; use uuid::Uuid; #[derive(Debug)] @@ -786,7 +786,6 @@ pub struct AcpThread { token_usage: Option, prompt_capabilities: acp::PromptCapabilities, _observe_prompt_capabilities: Task>, - determine_shell: Shared>, terminals: HashMap>, } @@ -873,20 +872,6 @@ impl AcpThread { } }); - let determine_shell = cx - .background_spawn(async move { - if cfg!(windows) { - return get_system_shell(); - } - - if which::which("bash").is_ok() { - "bash".into() - } else { - get_system_shell() - } - }) - .shared(); - Self { action_log, shared_buffers: Default::default(), @@ -901,7 +886,6 @@ impl AcpThread { prompt_capabilities, _observe_prompt_capabilities: task, terminals: HashMap::default(), - determine_shell, } } @@ -1940,28 +1924,13 @@ impl AcpThread { pub fn create_terminal( &self, - mut command: String, + command: String, args: Vec, extra_env: Vec, cwd: Option, output_byte_limit: Option, cx: &mut Context, ) -> Task>> { - for arg in args { - command.push(' '); - command.push_str(&arg); - } - - let shell_command = if cfg!(windows) { - format!("$null | & {{{}}}", command.replace("\"", "'")) - } else if let Some(cwd) = cwd.as_ref().and_then(|cwd| cwd.as_os_str().to_str()) { - // Make sure once we're *inside* the shell, we cd into `cwd` - format!("(cd {cwd}; {}) self.project.update(cx, |project, cx| { project.directory_environment(dir.as_path().into(), cx) @@ -1982,20 +1951,30 @@ impl AcpThread { let project = self.project.clone(); let language_registry = project.read(cx).languages().clone(); - let determine_shell = self.determine_shell.clone(); let terminal_id = acp::TerminalId(Uuid::new_v4().to_string().into()); let terminal_task = cx.spawn({ let terminal_id = terminal_id.clone(); async move |_this, cx| { - let program = determine_shell.await; let env = env.await; + let (command, args) = ShellBuilder::new( + project + .update(cx, |project, cx| { + project + .remote_client() + .and_then(|r| r.read(cx).default_system_shell()) + })? + .as_deref(), + &Shell::Program(get_default_system_shell()), + ) + .redirect_stdin_to_dev_null() + .build(Some(command), &args); let terminal = project .update(cx, |project, cx| { project.create_terminal_task( task::SpawnInTerminal { - command: Some(program), - args, + command: Some(command.clone()), + args: args.clone(), cwd: cwd.clone(), env, ..Default::default() @@ -2008,7 +1987,7 @@ impl AcpThread { cx.new(|cx| { Terminal::new( terminal_id, - command, + &format!("{} {}", command, args.join(" ")), cwd, output_byte_limit.map(|l| l as usize), terminal, diff --git a/crates/acp_thread/src/terminal.rs b/crates/acp_thread/src/terminal.rs index a927083b0bd576f1580ba261d4028407fcea7a5c..888c7698c3d2270769f3afbe712ecba7d08b055f 100644 --- a/crates/acp_thread/src/terminal.rs +++ b/crates/acp_thread/src/terminal.rs @@ -28,7 +28,7 @@ pub struct TerminalOutput { impl Terminal { pub fn new( id: acp::TerminalId, - command: String, + command_label: &str, working_dir: Option, output_byte_limit: Option, terminal: Entity, @@ -40,7 +40,7 @@ impl Terminal { id, command: cx.new(|cx| { Markdown::new( - format!("```\n{}\n```", command).into(), + format!("```\n{}\n```", command_label).into(), Some(language_registry.clone()), None, cx, diff --git a/crates/assistant_tools/Cargo.toml b/crates/assistant_tools/Cargo.toml index 5a8ca8a5e995fd2c738eb3b309f2bb4ebe9595a1..9b9b8196d1c342c536d605306a1a062e73768c56 100644 --- a/crates/assistant_tools/Cargo.toml +++ b/crates/assistant_tools/Cargo.toml @@ -63,7 +63,6 @@ ui.workspace = true util.workspace = true watch.workspace = true web_search.workspace = true -which.workspace = true workspace-hack.workspace = true workspace.workspace = true diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index ce3b639cb2c46d3f736490c0b2153260f970963c..17e2ba12f706387859ca3393aa44f5c05570e50a 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -52,7 +52,7 @@ pub fn init(http_client: Arc, cx: &mut App) { assistant_tool::init(cx); let registry = ToolRegistry::global(cx); - registry.register_tool(TerminalTool::new(cx)); + registry.register_tool(TerminalTool); registry.register_tool(CreateDirectoryTool); registry.register_tool(CopyPathTool); registry.register_tool(DeletePathTool); diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index 1605003671621b90e58a5f62e521c0aba2c990c6..8014a39e23137ad71b91e5c24d5d79699b530e5d 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -6,7 +6,7 @@ use action_log::ActionLog; use agent_settings; use anyhow::{Context as _, Result, anyhow}; use assistant_tool::{Tool, ToolCard, ToolResult, ToolUseStatus}; -use futures::{FutureExt as _, future::Shared}; +use futures::FutureExt as _; use gpui::{ AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task, TextStyleRefinement, WeakEntity, Window, @@ -26,11 +26,12 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +use task::{Shell, ShellBuilder}; use terminal_view::TerminalView; use theme::ThemeSettings; use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*}; use util::{ - ResultExt, get_system_shell, markdown::MarkdownInlineCode, size::format_file_size, + ResultExt, get_default_system_shell, markdown::MarkdownInlineCode, size::format_file_size, time::duration_alt_display, }; use workspace::Workspace; @@ -45,29 +46,10 @@ pub struct TerminalToolInput { cd: String, } -pub struct TerminalTool { - determine_shell: Shared>, -} +pub struct TerminalTool; impl TerminalTool { pub const NAME: &str = "terminal"; - - pub(crate) fn new(cx: &mut App) -> Self { - let determine_shell = cx.background_spawn(async move { - if cfg!(windows) { - return get_system_shell(); - } - - if which::which("bash").is_ok() { - "bash".into() - } else { - get_system_shell() - } - }); - Self { - determine_shell: determine_shell.shared(), - } - } } impl Tool for TerminalTool { @@ -135,19 +117,6 @@ impl Tool for TerminalTool { Ok(dir) => dir, Err(err) => return Task::ready(Err(err)).into(), }; - let program = self.determine_shell.clone(); - let command = if cfg!(windows) { - format!("$null | & {{{}}}", input.command.replace("\"", "'")) - } else if let Some(cwd) = working_dir - .as_ref() - .and_then(|cwd| cwd.as_os_str().to_str()) - { - // Make sure once we're *inside* the shell, we cd into `cwd` - format!("(cd {cwd}; {}) Task::ready(None).shared(), }; + let remote_shell = project.update(cx, |project, cx| { + project + .remote_client() + .and_then(|r| r.read(cx).default_system_shell()) + }); let env = cx.spawn(async move |_| { let mut env = env.await.unwrap_or_default(); @@ -171,8 +145,13 @@ impl Tool for TerminalTool { let task = cx.background_spawn(async move { let env = env.await; let pty_system = native_pty_system(); - let program = program.await; - let mut cmd = CommandBuilder::new(program); + let (command, args) = ShellBuilder::new( + remote_shell.as_deref(), + &Shell::Program(get_default_system_shell()), + ) + .redirect_stdin_to_dev_null() + .build(Some(input.command.clone()), &[]); + let mut cmd = CommandBuilder::new(command); cmd.args(args); for (k, v) in env { cmd.env(k, v); @@ -208,16 +187,22 @@ impl Tool for TerminalTool { }; }; + let command = input.command.clone(); let terminal = cx.spawn({ let project = project.downgrade(); async move |cx| { - let program = program.await; + let (command, args) = ShellBuilder::new( + remote_shell.as_deref(), + &Shell::Program(get_default_system_shell()), + ) + .redirect_stdin_to_dev_null() + .build(Some(input.command), &[]); let env = env.await; project .update(cx, |project, cx| { project.create_terminal_task( task::SpawnInTerminal { - command: Some(program), + command: Some(command), args, cwd, env, @@ -230,14 +215,8 @@ impl Tool for TerminalTool { } }); - let command_markdown = cx.new(|cx| { - Markdown::new( - format!("```bash\n{}\n```", input.command).into(), - None, - None, - cx, - ) - }); + let command_markdown = + cx.new(|cx| Markdown::new(format!("```bash\n{}\n```", command).into(), None, None, cx)); let card = cx.new(|cx| { TerminalToolCard::new( @@ -288,7 +267,7 @@ impl Tool for TerminalTool { let previous_len = content.len(); let (processed_content, finished_with_empty_output) = process_content( &content, - &input.command, + &command, exit_status.map(portable_pty::ExitStatus::from), ); @@ -740,7 +719,6 @@ mod tests { if cfg!(windows) { return; } - init_test(&executor, cx); let fs = Arc::new(RealFs::new(None, executor)); @@ -763,7 +741,7 @@ mod tests { }; let result = cx.update(|cx| { TerminalTool::run( - Arc::new(TerminalTool::new(cx)), + Arc::new(TerminalTool), serde_json::to_value(input).unwrap(), Arc::default(), project.clone(), @@ -783,7 +761,6 @@ mod tests { if cfg!(windows) { return; } - init_test(&executor, cx); let fs = Arc::new(RealFs::new(None, executor)); @@ -798,7 +775,7 @@ mod tests { let check = |input, expected, cx: &mut App| { let headless_result = TerminalTool::run( - Arc::new(TerminalTool::new(cx)), + Arc::new(TerminalTool), serde_json::to_value(input).unwrap(), Arc::default(), project.clone(), diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 34dfe4a4287c80a91efd285dc020657f7e0a0fe8..27af3eea17c9858d11e01cd7187975e804736ba2 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1131,7 +1131,7 @@ impl ToolchainLister for PythonToolchainProvider { let activate_keyword = match shell { ShellKind::Cmd => ".", ShellKind::Nushell => "overlay use", - ShellKind::Powershell => ".", + ShellKind::PowerShell => ".", ShellKind::Fish => "source", ShellKind::Csh => "source", ShellKind::Posix => "source", @@ -1141,7 +1141,7 @@ impl ToolchainLister for PythonToolchainProvider { ShellKind::Csh => "activate.csh", ShellKind::Fish => "activate.fish", ShellKind::Nushell => "activate.nu", - ShellKind::Powershell => "activate.ps1", + ShellKind::PowerShell => "activate.ps1", ShellKind::Cmd => "activate.bat", }; let path = prefix.join(BINARY_DIR).join(activate_script_name); @@ -1165,7 +1165,7 @@ impl ToolchainLister for PythonToolchainProvider { ShellKind::Fish => Some(format!("\"{pyenv}\" shell - fish {version}")), ShellKind::Posix => Some(format!("\"{pyenv}\" shell - sh {version}")), ShellKind::Nushell => Some(format!("\"{pyenv}\" shell - nu {version}")), - ShellKind::Powershell => None, + ShellKind::PowerShell => None, ShellKind::Csh => None, ShellKind::Cmd => None, }) diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 04f98d6dba6794116be9a6dcf4d2cbb32cfb85b2..94e9999e1344efbc391476e22d107f10052d7694 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -179,7 +179,7 @@ impl Project { } }; - let shell = { + let (shell, env) = { env.extend(spawn_task.env); match remote_client { Some(remote_client) => match activation_script.clone() { @@ -189,8 +189,14 @@ impl Project { let args = vec!["-c".to_owned(), format!("{activation_script}; {to_run}")]; create_remote_shell( - Some((&shell, &args)), - &mut env, + Some(( + &remote_client + .read(cx) + .shell() + .unwrap_or_else(get_default_system_shell), + &args, + )), + env, path, remote_client, cx, @@ -201,7 +207,7 @@ impl Project { .command .as_ref() .map(|command| (command, &spawn_task.args)), - &mut env, + env, path, remote_client, cx, @@ -220,13 +226,16 @@ impl Project { #[cfg(not(windows))] let arg = format!("{activation_script}; {to_run}"); - Shell::WithArguments { - program: shell, - args: vec!["-c".to_owned(), arg], - title_override: None, - } + ( + Shell::WithArguments { + program: shell, + args: vec!["-c".to_owned(), arg], + title_override: None, + }, + env, + ) } - _ => { + _ => ( if let Some(program) = spawn_task.command { Shell::WithArguments { program, @@ -235,8 +244,9 @@ impl Project { } } else { Shell::System - } - } + }, + env, + ), }, } }; @@ -330,7 +340,7 @@ impl Project { .map(|p| self.active_toolchain(p, LanguageName::new("Python"), cx)) .collect::>(); let remote_client = self.remote_client.clone(); - let shell = match &remote_client { + let shell_kind = ShellKind::new(&match &remote_client { Some(remote_client) => remote_client .read(cx) .shell() @@ -344,7 +354,7 @@ impl Project { } => program.clone(), Shell::System => get_system_shell(), }, - }; + }); let lang_registry = self.languages.clone(); let fs = self.fs.clone(); @@ -361,7 +371,7 @@ impl Project { let lister = language?.toolchain_lister(); return Some( lister? - .activation_script(&toolchain, ShellKind::new(&shell), fs.as_ref()) + .activation_script(&toolchain, shell_kind, fs.as_ref()) .await, ); } @@ -370,12 +380,12 @@ impl Project { .await .unwrap_or_default(); project.update(cx, move |this, cx| { - let shell = { + let (shell, env) = { match remote_client { Some(remote_client) => { - create_remote_shell(None, &mut env, path, remote_client, cx)? + create_remote_shell(None, env, path, remote_client, cx)? } - None => settings.shell, + None => (settings.shell, env), } }; TerminalBuilder::new( @@ -545,11 +555,11 @@ fn quote_arg(argument: &str, quote: bool) -> String { fn create_remote_shell( spawn_command: Option<(&String, &Vec)>, - env: &mut HashMap, + mut env: HashMap, working_directory: Option>, remote_client: Entity, cx: &mut App, -) -> Result { +) -> Result<(Shell, HashMap)> { // Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed // to properly display colors. // We do not have the luxury of assuming the host has it installed, @@ -565,18 +575,20 @@ fn create_remote_shell( let command = remote_client.read(cx).build_command( program, args.as_slice(), - env, + &env, working_directory.map(|path| path.display().to_string()), None, )?; - *env = command.env; log::debug!("Connecting to a remote server: {:?}", command.program); let host = remote_client.read(cx).connection_options().display_name(); - Ok(Shell::WithArguments { - program: command.program, - args: command.args, - title_override: Some(format!("{} — Terminal", host).into()), - }) + Ok(( + Shell::WithArguments { + program: command.program, + args: command.args, + title_override: Some(format!("{} — Terminal", host).into()), + }, + command.env, + )) } diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index 0ccea81ec69425554ad8faf7d63e528728403594..0363fc721a2d51971f49112af520b5dd34b52cb1 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -772,6 +772,10 @@ impl RemoteClient { Some(self.remote_connection()?.shell()) } + pub fn default_system_shell(&self) -> Option { + Some(self.remote_connection()?.default_system_shell()) + } + pub fn shares_network_interface(&self) -> bool { self.remote_connection() .map_or(false, |connection| connection.shares_network_interface()) @@ -1062,6 +1066,7 @@ pub(crate) trait RemoteConnection: Send + Sync { fn connection_options(&self) -> RemoteConnectionOptions; fn path_style(&self) -> PathStyle; fn shell(&self) -> String; + fn default_system_shell(&self) -> String; #[cfg(any(test, feature = "test-support"))] fn simulate_disconnect(&self, _: &AsyncApp) {} @@ -1503,6 +1508,10 @@ mod fake { fn shell(&self) -> String { "sh".to_owned() } + + fn default_system_shell(&self) -> String { + "sh".to_owned() + } } pub(super) struct Delegate; diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 932cb7145e4404c4529c483562e626205831d145..2a589ea836fc2576c4afd0fcff1e7b35aa6fde73 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -37,6 +37,7 @@ pub(crate) struct SshRemoteConnection { ssh_platform: RemotePlatform, ssh_path_style: PathStyle, ssh_shell: String, + ssh_default_system_shell: String, _temp_dir: TempDir, } @@ -105,6 +106,10 @@ impl RemoteConnection for SshRemoteConnection { self.ssh_shell.clone() } + fn default_system_shell(&self) -> String { + self.ssh_default_system_shell.clone() + } + fn build_command( &self, input_program: Option, @@ -347,6 +352,7 @@ impl SshRemoteConnection { _ => PathStyle::Posix, }; let ssh_shell = socket.shell().await; + let ssh_default_system_shell = String::from("/bin/sh"); let mut this = Self { socket, @@ -356,6 +362,7 @@ impl SshRemoteConnection { ssh_path_style, ssh_platform, ssh_shell, + ssh_default_system_shell, }; let (release_channel, version, commit) = cx.update(|cx| { diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index 2b4d29eafeede14f305c4d21f61188b858253285..6b386ee361c763e30c9e31c15b47c836ef922dae 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -29,6 +29,7 @@ pub(crate) struct WslRemoteConnection { remote_binary_path: Option, platform: RemotePlatform, shell: String, + default_system_shell: String, connection_options: WslConnectionOptions, } @@ -56,6 +57,7 @@ impl WslRemoteConnection { remote_binary_path: None, platform: RemotePlatform { os: "", arch: "" }, shell: String::new(), + default_system_shell: String::from("/bin/sh"), }; delegate.set_status(Some("Detecting WSL environment"), cx); this.platform = this.detect_platform().await?; @@ -84,7 +86,11 @@ impl WslRemoteConnection { .run_wsl_command("sh", &["-c", "echo $SHELL"]) .await .ok() - .and_then(|shell_path| shell_path.trim().split('/').next_back().map(str::to_string)) + .and_then(|shell_path| { + Path::new(shell_path.trim()) + .file_name() + .map(|it| it.to_str().unwrap().to_owned()) + }) .unwrap_or_else(|| "bash".to_string())) } @@ -427,6 +433,10 @@ impl RemoteConnection for WslRemoteConnection { fn shell(&self) -> String { self.shell.clone() } + + fn default_system_shell(&self) -> String { + self.default_system_shell.clone() + } } /// `wslpath` is a executable available in WSL, it's a linux binary. diff --git a/crates/task/src/shell_builder.rs b/crates/task/src/shell_builder.rs index 4688ac0eb9dd306d0dedbe07c98adbfb5df4f45b..c3f0646c02cc427a07505c2ff30157e84d2ca0fe 100644 --- a/crates/task/src/shell_builder.rs +++ b/crates/task/src/shell_builder.rs @@ -10,7 +10,7 @@ pub enum ShellKind { Posix, Csh, Fish, - Powershell, + PowerShell, Nushell, Cmd, } @@ -21,7 +21,7 @@ impl fmt::Display for ShellKind { ShellKind::Posix => write!(f, "sh"), ShellKind::Csh => write!(f, "csh"), ShellKind::Fish => write!(f, "fish"), - ShellKind::Powershell => write!(f, "powershell"), + ShellKind::PowerShell => write!(f, "powershell"), ShellKind::Nushell => write!(f, "nu"), ShellKind::Cmd => write!(f, "cmd"), } @@ -43,7 +43,7 @@ impl ShellKind { || program == "pwsh" || program.ends_with("pwsh.exe") { - ShellKind::Powershell + ShellKind::PowerShell } else if program == "cmd" || program.ends_with("cmd.exe") { ShellKind::Cmd } else if program == "nu" { @@ -61,7 +61,7 @@ impl ShellKind { fn to_shell_variable(self, input: &str) -> String { match self { - Self::Powershell => Self::to_powershell_variable(input), + Self::PowerShell => Self::to_powershell_variable(input), Self::Cmd => Self::to_cmd_variable(input), Self::Posix => input.to_owned(), Self::Fish => input.to_owned(), @@ -184,7 +184,7 @@ impl ShellKind { fn args_for_shell(&self, interactive: bool, combined_command: String) -> Vec { match self { - ShellKind::Powershell => vec!["-C".to_owned(), combined_command], + ShellKind::PowerShell => vec!["-C".to_owned(), combined_command], ShellKind::Cmd => vec!["/C".to_owned(), combined_command], ShellKind::Posix | ShellKind::Nushell | ShellKind::Fish | ShellKind::Csh => interactive .then(|| "-i".to_owned()) @@ -196,7 +196,7 @@ impl ShellKind { pub fn command_prefix(&self) -> Option { match self { - ShellKind::Powershell => Some('&'), + ShellKind::PowerShell => Some('&'), ShellKind::Nushell => Some('^'), _ => None, } @@ -210,6 +210,7 @@ pub struct ShellBuilder { program: String, args: Vec, interactive: bool, + redirect_stdin: bool, kind: ShellKind, } @@ -231,6 +232,7 @@ impl ShellBuilder { args, interactive: true, kind, + redirect_stdin: false, } } pub fn non_interactive(mut self) -> Self { @@ -241,7 +243,7 @@ impl ShellBuilder { /// Returns the label to show in the terminal tab pub fn command_label(&self, command_label: &str) -> String { match self.kind { - ShellKind::Powershell => { + ShellKind::PowerShell => { format!("{} -C '{}'", self.program, command_label) } ShellKind::Cmd => { @@ -256,6 +258,12 @@ impl ShellBuilder { } } } + + pub fn redirect_stdin_to_dev_null(mut self) -> Self { + self.redirect_stdin = true; + self + } + /// Returns the program and arguments to run this task in a shell. pub fn build( mut self, @@ -263,11 +271,24 @@ impl ShellBuilder { task_args: &[String], ) -> (String, Vec) { if let Some(task_command) = task_command { - let combined_command = task_args.iter().fold(task_command, |mut command, arg| { + let mut combined_command = task_args.iter().fold(task_command, |mut command, arg| { command.push(' '); command.push_str(&self.kind.to_shell_variable(arg)); command }); + if self.redirect_stdin { + match self.kind { + ShellKind::Posix | ShellKind::Nushell | ShellKind::Fish | ShellKind::Csh => { + combined_command.push_str(" { + combined_command.insert_str(0, "$null | "); + } + ShellKind::Cmd => { + combined_command.push_str("< NUL"); + } + } + } self.args .extend(self.kind.args_for_shell(self.interactive, combined_command)); From 53b2f37452189870c93d4514604f903d5ed885d9 Mon Sep 17 00:00:00 2001 From: Hichem Date: Mon, 15 Sep 2025 15:00:22 +0200 Subject: [PATCH 033/721] Enhance layout and styling of tool list in AgentConfiguration (#38195) Improve the layout and styling of the tool list in the AgentConfiguration, ensuring better responsiveness and visual clarity. closes #38194 image --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/agent_configuration.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index faa6b524ff49fffbd5c8113e164b2c9ab158d71a..e52b818eb3bac1966b66611efd5b5c59485f4a0a 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -818,6 +818,8 @@ impl AgentConfiguration { ) .child( h_flex() + .flex_1() + .min_w_0() .child( Disclosure::new( "tool-list-disclosure", @@ -841,17 +843,19 @@ impl AgentConfiguration { .id(SharedString::from(format!("tooltip-{}", item_id))) .h_full() .w_3() - .mx_1() + .ml_1() + .mr_1p5() .justify_center() .tooltip(Tooltip::text(tooltip_text)) .child(status_indicator), ) - .child(Label::new(item_id).ml_0p5()) + .child(Label::new(item_id).truncate()) .child( div() .id("extension-source") .mt_0p5() .mx_1() + .flex_none() .tooltip(Tooltip::text(source_tooltip)) .child( Icon::new(source_icon) @@ -873,7 +877,8 @@ impl AgentConfiguration { ) .child( h_flex() - .gap_1() + .gap_0p5() + .flex_none() .child(context_server_configuration_menu) .child( Switch::new("context-server-switch", is_running.into()) From 6d6c3d648a32f2b88073fe59a744ee7ba09e4c28 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:20:04 +0200 Subject: [PATCH 034/721] lsp: Fix overnotifying about open buffers for unrelated servers (#38196) Do not report all open buffers to new instances of the same language server, as they can respond with ~spurious errors. This regressed in https://github.com/zed-industries/zed/pull/34142 Closes https://github.com/zed-industries/zed/issues/35017 Release Notes: - Fixed Zed overly notifying language servers about open buffers, which could've resulted in confusing errors in multi-language projects (in e.g. Go). --- crates/project/src/lsp_store.rs | 12 ++++++++---- crates/project/src/manifest_tree/server_tree.rs | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 2040b10bb39ceff6d9d6d06a5dbfb0f68f41a459..37a97a35ab8300248d6e1b68b628ed6439c5e8e0 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -10540,7 +10540,10 @@ impl LspStore { for (worktree_id, servers) in &local.lsp_tree.instances { if *worktree_id != key.worktree_id { for server_map in servers.roots.values() { - if server_map.contains_key(&key.name) { + if server_map + .values() + .any(|(node, _)| node.id() == Some(server_id)) + { worktrees_using_server.push(*worktree_id); } } @@ -10550,6 +10553,7 @@ impl LspStore { let mut buffer_paths_registered = Vec::new(); self.buffer_store.clone().update(cx, |buffer_store, cx| { + let mut lsp_adapters = HashMap::default(); for buffer_handle in buffer_store.buffers() { let buffer = buffer_handle.read(cx); let file = match File::from_dyn(buffer.file()) { @@ -10562,9 +10566,9 @@ impl LspStore { }; if !worktrees_using_server.contains(&file.worktree.read(cx).id()) - || !self - .languages - .lsp_adapters(&language.name()) + || !lsp_adapters + .entry(language.name()) + .or_insert_with(|| self.languages.lsp_adapters(&language.name())) .iter() .any(|a| a.name == key.name) { diff --git a/crates/project/src/manifest_tree/server_tree.rs b/crates/project/src/manifest_tree/server_tree.rs index 48e2007d47f1ebd3c950f0d03e80dcccad515389..17a183e5b08dcab8ba6b6da861d2ec12559092b5 100644 --- a/crates/project/src/manifest_tree/server_tree.rs +++ b/crates/project/src/manifest_tree/server_tree.rs @@ -114,6 +114,10 @@ impl InnerTreeNode { }), } } + + pub(crate) fn id(&self) -> Option { + self.id.get().copied() + } } impl LanguageServerTree { From 7ea94a32be09e196272f8ab0c00f4cc46a368943 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 15 Sep 2025 11:07:14 -0400 Subject: [PATCH 035/721] Create failed tool call entries for missing tools (#38207) Release Notes: - When an agent requests a tool that doesn't exist, this is now treated as a failed tool call instead of stopping the thread. --- crates/acp_thread/src/acp_thread.rs | 91 ++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index c89b742742536308a6064fe9ddabff3f8e73c341..68e5266f06aa8bddfaa252bdc1cf5b21891c7f10 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -1111,9 +1111,33 @@ impl AcpThread { let update = update.into(); let languages = self.project.read(cx).languages().clone(); - let ix = self - .index_for_tool_call(update.id()) - .context("Tool call not found")?; + let ix = match self.index_for_tool_call(update.id()) { + Some(ix) => ix, + None => { + // Tool call not found - create a failed tool call entry + let failed_tool_call = ToolCall { + id: update.id().clone(), + label: cx.new(|cx| Markdown::new("Tool call not found".into(), None, None, cx)), + kind: acp::ToolKind::Fetch, + content: vec![ToolCallContent::ContentBlock(ContentBlock::new( + acp::ContentBlock::Text(acp::TextContent { + text: "Tool call not found".to_string(), + annotations: None, + meta: None, + }), + &languages, + cx, + ))], + status: ToolCallStatus::Failed, + locations: Vec::new(), + resolved_locations: Vec::new(), + raw_input: None, + raw_output: None, + }; + self.push_entry(AgentThreadEntry::ToolCall(failed_tool_call), cx); + return Ok(()); + } + }; let AgentThreadEntry::ToolCall(call) = &mut self.entries[ix] else { unreachable!() }; @@ -3160,4 +3184,65 @@ mod tests { Task::ready(Ok(())) } } + + #[gpui::test] + async fn test_tool_call_not_found_creates_failed_entry(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + let connection = Rc::new(FakeAgentConnection::new()); + let thread = cx + .update(|cx| connection.new_thread(project, Path::new(path!("/test")), cx)) + .await + .unwrap(); + + // Try to update a tool call that doesn't exist + let nonexistent_id = acp::ToolCallId("nonexistent-tool-call".into()); + thread.update(cx, |thread, cx| { + let result = thread.handle_session_update( + acp::SessionUpdate::ToolCallUpdate(acp::ToolCallUpdate { + id: nonexistent_id.clone(), + fields: acp::ToolCallUpdateFields { + status: Some(acp::ToolCallStatus::Completed), + ..Default::default() + }, + meta: None, + }), + cx, + ); + + // The update should succeed (not return an error) + assert!(result.is_ok()); + + // There should now be exactly one entry in the thread + assert_eq!(thread.entries.len(), 1); + + // The entry should be a failed tool call + if let AgentThreadEntry::ToolCall(tool_call) = &thread.entries[0] { + assert_eq!(tool_call.id, nonexistent_id); + assert!(matches!(tool_call.status, ToolCallStatus::Failed)); + assert_eq!(tool_call.kind, acp::ToolKind::Fetch); + + // Check that the content contains the error message + assert_eq!(tool_call.content.len(), 1); + if let ToolCallContent::ContentBlock(content_block) = &tool_call.content[0] { + match content_block { + ContentBlock::Markdown { markdown } => { + let markdown_text = markdown.read(cx).source(); + assert!(markdown_text.contains("Tool call not found")); + } + ContentBlock::Empty => panic!("Expected markdown content, got empty"), + ContentBlock::ResourceLink { .. } => { + panic!("Expected markdown content, got resource link") + } + } + } else { + panic!("Expected ContentBlock, got: {:?}", tool_call.content[0]); + } + } else { + panic!("Expected ToolCall entry, got: {:?}", thread.entries[0]); + } + }); + } } From 35e5aa4e71baeee1eb8b6afa832b28ed6a0f13cf Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Sep 2025 09:18:07 -0600 Subject: [PATCH 036/721] Re-add VSCode syntax node motions (#38208) Closes #ISSUE Release Notes: - (preview only) restored ctrl-shift-{left,right} for Larger/Smaller syntax node. This is VSCode's default and avoids the breaking change from #37874 --- assets/keymaps/default-windows.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index e5839964ad545f3994d675da817a5f4571b88db4..78d5e4e698daefee5a57b04d6a8548fb948233b1 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -497,6 +497,8 @@ "shift-alt-down": "editor::DuplicateLineDown", "shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection "shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection + "ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version) + "ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version) "ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection "ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word "ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand From 63586ff2e452832e3e1250d1e9cf3938d377d832 Mon Sep 17 00:00:00 2001 From: Kaan Kuscu Date: Mon, 15 Sep 2025 18:51:03 +0300 Subject: [PATCH 037/721] Add new injections for Go (#37605) support for injecting sql, json, yaml, xml, html, css, js, lua and csv value if you use `/* lang */` before string literals, highlights them **Example:** ```go const sqlQuery = /* sql */ "SELECT * FROM users;" // highlights as SQL code ``` Screenshot 2025-09-05 at 06 17 49 Closes #ISSUE Release Notes: - Go: Added support for injecting sql, json, yaml, xml, html, css, js, lua and csv language highlights into string literals, when they are prefixed with `/* lang */` **Example:** ```go const sqlQuery = /* sql */ "SELECT * FROM users;" // Will be highlighted as SQL code ``` --- crates/languages/src/go/injections.scm | 363 ++++++++++++++++++++++++- 1 file changed, 362 insertions(+), 1 deletion(-) diff --git a/crates/languages/src/go/injections.scm b/crates/languages/src/go/injections.scm index 2be0844d97b7f9f16e8832b5b8aebbb0d0043e5d..7bb68d760e1a556ef93a9477dc578c88d9350dcb 100644 --- a/crates/languages/src/go/injections.scm +++ b/crates/languages/src/go/injections.scm @@ -10,4 +10,365 @@ (raw_string_literal) (interpreted_string_literal) ] @injection.content - (#set! injection.language "regex"))) + (#set! injection.language "regex") + )) + +; INJECT SQL +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*sql\\s*\\*\\/") ; /* sql */ or /*sql*/ + (#set! injection.language "sql") +) + +; INJECT JSON +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*json\\s*\\*\\/") ; /* json */ or /*json*/ + (#set! injection.language "json") +) + +; INJECT YAML +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*yaml\\s*\\*\\/") ; /* yaml */ or /*yaml*/ + (#set! injection.language "yaml") +) + +; INJECT XML +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*xml\\s*\\*\\/") ; /* xml */ or /*xml*/ + (#set! injection.language "xml") +) + +; INJECT HTML +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*html\\s*\\*\\/") ; /* html */ or /*html*/ + (#set! injection.language "html") +) + +; INJECT JS +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*js\\s*\\*\\/") ; /* js */ or /*js*/ + (#set! injection.language "javascript") +) + +; INJECT CSS +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*css\\s*\\*\\/") ; /* css */ or /*css*/ + (#set! injection.language "css") +) + +; INJECT LUA +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*lua\\s*\\*\\/") ; /* lua */ or /*lua*/ + (#set! injection.language "lua") +) + +; INJECT BASH +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*bash\\s*\\*\\/") ; /* bash */ or /*bash*/ + (#set! injection.language "bash") +) + +; INJECT CSV +( + [ + ; var, const or short declaration of raw or interpreted string literal + ((comment) @comment + . + (expression_list + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a literal element (to struct field eg.) + ((comment) @comment + . + (literal_element + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content + )) + + ; when passing as a function parameter + ((comment) @comment + . + [ + (interpreted_string_literal) + (raw_string_literal) + ] @injection.content) + ] + + (#match? @comment "^\\/\\*\\s*csv\\s*\\*\\/") ; /* csv */ or /*csv*/ + (#set! injection.language "csv") +) From 8b9c74726a636bb7774777a9203a41be48488846 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Mon, 15 Sep 2025 11:03:45 -0500 Subject: [PATCH 038/721] docs: Call out Omarchy specifically in regards to issues with `amdvlk` (#38214) Closes #28851 Release Notes: - N/A *or* Added/Fixed/Improved ... --- docs/src/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/linux.md b/docs/src/linux.md index 4a66445b78902cde0d96ca17dd1e22abaa9ee96d..a3220e11cbe1ff25ac6c5fe736de0f88c796942d 100644 --- a/docs/src/linux.md +++ b/docs/src/linux.md @@ -151,7 +151,7 @@ If you're using an AMD GPU and Zed crashes when selecting long lines, try settin If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880)) -If you are using `amdvlk` you may find that zed only opens when run with `sudo $(which zed)`. To fix this, remove the `amdvlk` and `lib32-amdvlk` packages and install mesa/vulkan instead. ([#14141](https://github.com/zed-industries/zed/issues/14141)). +If you are using `amdvlk`, the default open-source AMD graphics driver, you may find that Zed consistently fails to launch. This is a known issue for some users, for example on Omarchy (see issue [#28851](https://github.com/zed-industries/zed/issues/28851)). To fix this, you will need to use a different driver. We recommend removing the `amdvlk` and `lib32-amdvlk` packages and installing `vulkan-radeon` instead (see issue [#14141](https://github.com/zed-industries/zed/issues/14141)). For more information, the [Arch guide to Vulkan](https://wiki.archlinux.org/title/Vulkan) has some good steps that translate well to most distributions. From 6384966ab546bad67e8ce16f13d37abe32ea363d Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:35:39 -0300 Subject: [PATCH 039/721] agent: Improve some items in the settings view UI (#38199) All described in each commit; mostly small things, simplifying/clearing up the UI. Release Notes: - N/A --- crates/agent_ui/src/agent_configuration.rs | 58 +++++++++++++++------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index e52b818eb3bac1966b66611efd5b5c59485f4a0a..aacfb423539496e6c5cb93ad8c12f1ed8ede346a 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -274,13 +274,28 @@ impl AgentConfiguration { *is_expanded = !*is_expanded; } })), - ) - .when(provider.is_authenticated(cx), |parent| { + ), + ) + .child( + v_flex() + .w_full() + .px_2() + .gap_1() + .when(is_expanded, |parent| match configuration_view { + Some(configuration_view) => parent.child(configuration_view), + None => parent.child(Label::new(format!( + "No configuration view for {provider_name}", + ))), + }) + .when(is_expanded && provider.is_authenticated(cx), |parent| { parent.child( Button::new( SharedString::from(format!("new-thread-{provider_id}")), "Start New Thread", ) + .full_width() + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) .icon_position(IconPosition::Start) .icon(IconName::Thread) .icon_size(IconSize::Small) @@ -297,17 +312,6 @@ impl AgentConfiguration { ) }), ) - .child( - div() - .w_full() - .px_2() - .when(is_expanded, |parent| match configuration_view { - Some(configuration_view) => parent.child(configuration_view), - None => parent.child(Label::new(format!( - "No configuration view for {provider_name}", - ))), - }), - ) } fn render_provider_configuration_section( @@ -561,11 +565,28 @@ impl AgentConfiguration { .color(Color::Muted), ), ) - .children( - context_server_ids.into_iter().map(|context_server_id| { - self.render_context_server(context_server_id, window, cx) - }), - ) + .map(|parent| { + if context_server_ids.is_empty() { + parent.child( + h_flex() + .p_4() + .justify_center() + .border_1() + .border_dashed() + .border_color(cx.theme().colors().border.opacity(0.6)) + .rounded_sm() + .child( + Label::new("No MCP servers added yet.") + .color(Color::Muted) + .size(LabelSize::Small), + ), + ) + } else { + parent.children(context_server_ids.into_iter().map(|context_server_id| { + self.render_context_server(context_server_id, window, cx) + })) + } + }) .child( h_flex() .justify_between() @@ -1128,6 +1149,7 @@ impl AgentConfiguration { SharedString::from(format!("start_acp_thread-{name}")), "Start New Thread", ) + .layer(ElevationIndex::ModalSurface) .label_size(LabelSize::Small) .icon(IconName::Thread) .icon_position(IconPosition::Start) From 9046091164b2addc3238f4d33671a5e17eb54f2d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Sep 2025 11:10:28 -0700 Subject: [PATCH 040/721] Add a test that would have caught the bug last week (#38222) This adds a test to make sure that the default value of the auto update setting is always true. We manually re-applied the broken code from last week, and confirmed that this test fails with that code. Release Notes: - N/A --------- Co-authored-by: Ben Kunkle Co-authored-by: Conrad Irwin --- crates/auto_update/Cargo.toml | 3 +++ crates/auto_update/src/auto_update.rs | 19 +++++++++++++++++++ crates/project/src/direnv.rs | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 1a772710c98f8437932d6e8918df65d003d7962e..35cef84f0366b16d9e31b2416e7a6a10173ff5ef 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -32,3 +32,6 @@ workspace-hack.workspace = true [target.'cfg(not(target_os = "windows"))'.dependencies] which.workspace = true + +[dev-dependencies] +gpui = { workspace = true, "features" = ["test-support"] } diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index c1e318ee84f5228156826c340c3463fde2ef1bb8..ac5b03d40d615ea2887d8726ba248304b34c9874 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -992,8 +992,27 @@ pub fn finalize_auto_update_on_quit() { #[cfg(test)] mod tests { + use gpui::TestAppContext; + use settings::default_settings; + use super::*; + #[gpui::test] + fn test_auto_update_defaults_to_true(cx: &mut TestAppContext) { + cx.update(|cx| { + let mut store = SettingsStore::new(cx); + store + .set_default_settings(&default_settings(), cx) + .expect("Unable to set default settings"); + store + .set_user_settings("{}", cx) + .expect("Unable to set user settings"); + cx.set_global(store); + AutoUpdateSetting::register(cx); + assert!(AutoUpdateSetting::get_global(cx).0); + }); + } + #[test] fn test_stable_does_not_update_when_fetched_version_is_not_higher() { let release_channel = ReleaseChannel::Stable; diff --git a/crates/project/src/direnv.rs b/crates/project/src/direnv.rs index 9ba0ad10e3173a1930186db28f7efb5d9a8267f7..32f4963fd19805e369c696cfd30c8ef599bd06f3 100644 --- a/crates/project/src/direnv.rs +++ b/crates/project/src/direnv.rs @@ -65,7 +65,7 @@ pub async fn load_direnv_environment( let output = String::from_utf8_lossy(&direnv_output.stdout); if output.is_empty() { // direnv outputs nothing when it has no changes to apply to environment variables - return Ok(HashMap::new()); + return Ok(HashMap::default()); } match serde_json::from_str(&output) { From 0784bb81923d4e8f89b75c1a449ede2d28e6329c Mon Sep 17 00:00:00 2001 From: Kenny Date: Mon, 15 Sep 2025 15:57:23 -0600 Subject: [PATCH 041/721] docs: Add "Copy as Markdown" button to toolbar (#38218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a "Copy as Markdown" button to the documentation toolbar that allows users to easily copy the raw markdown content of any documentation page. This feature is inspired by similar implementations on sites like [Better Auth docs](https://www.better-auth.com/docs/installation) and [Cloudflare Workers docs](https://developers.cloudflare.com/workers/) which provide easy ways for users to copy documentation content. ## Features - **Button placement**: Positioned between theme toggle and search icon for optimal UX - **Content fetching**: Retrieves raw markdown from GitHub's API for the current page - **Consistent styling**: Matches existing toolbar button patterns ## Test plan - [x] Copy functionality works on all documentation pages - [x] Toast notifications appear and disappear correctly - [x] Button icon animations work properly (spinner → checkmark → copy) - [x] Styling matches other toolbar buttons - [x] Works in both light and dark themes ## Screenshots The button appears as a copy icon between the theme and search buttons in the left toolbar. image image --------- Co-authored-by: Danilo Leal --- docs/theme/css/variables.css | 10 +++ docs/theme/index.hbs | 8 ++- docs/theme/plugins.css | 37 +++++++++++ docs/theme/plugins.js | 119 +++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 2 deletions(-) diff --git a/docs/theme/css/variables.css b/docs/theme/css/variables.css index 1fe0e7dc8514d46acf202c6c995a7f50f5acab2b..dceba25af87e62ee64459984950d3e6921421d39 100644 --- a/docs/theme/css/variables.css +++ b/docs/theme/css/variables.css @@ -87,6 +87,11 @@ --download-btn-border-hover: hsla(220, 60%, 50%, 0.2); --download-btn-shadow: hsla(220, 40%, 60%, 0.1); + --toast-bg: hsla(220, 93%, 98%); + --toast-border: hsla(220, 93%, 42%, 0.3); + --toast-border-success: hsla(120, 73%, 42%, 0.3); + --toast-border-error: hsla(0, 90%, 50%, 0.3); + --footer-btn-bg: hsl(220, 60%, 98%, 0.4); --footer-btn-bg-hover: hsl(220, 60%, 93%, 0.5); --footer-btn-border: hsla(220, 60%, 40%, 0.15); @@ -166,6 +171,11 @@ --download-btn-border-hover: hsla(220, 90%, 80%, 0.4); --download-btn-shadow: hsla(220, 50%, 60%, 0.15); + --toast-bg: hsla(220, 20%, 98%, 0.05); + --toast-border: hsla(220, 93%, 70%, 0.2); + --toast-border-success: hsla(120, 90%, 60%, 0.3); + --toast-border-error: hsla(0, 90%, 80%, 0.3); + --footer-btn-bg: hsl(220, 90%, 95%, 0.01); --footer-btn-bg-hover: hsl(220, 90%, 50%, 0.05); --footer-btn-border: hsla(220, 90%, 90%, 0.05); diff --git a/docs/theme/index.hbs b/docs/theme/index.hbs index 4339a02d1722d0d64e67b35de66889d9a849e9a4..86008ed690a9644b32227f68fbec148b94907640 100644 --- a/docs/theme/index.hbs +++ b/docs/theme/index.hbs @@ -131,7 +131,7 @@ - + + {{#if search_enabled}} - {{/if}} diff --git a/docs/theme/plugins.css b/docs/theme/plugins.css index 9d5d09fe736a96eeb0f26f6bc3c7a20a55664f31..8c9f0c438e8e1ecd43cd770183d0a6a3bbfe0a4f 100644 --- a/docs/theme/plugins.css +++ b/docs/theme/plugins.css @@ -6,3 +6,40 @@ kbd.keybinding { display: inline-block; margin: 0 2px; } + +#copy-markdown-toggle i { + font-weight: 500 !important; + -webkit-text-stroke: 0.5px currentColor; +} + +.copy-toast { + position: fixed; + top: 72px; + right: 16px; + padding: 12px 16px; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + color: var(--fg); + background: var(--toast-bg); + border: 1px solid var(--toast-border); + z-index: 1000; + opacity: 0; + transform: translateY(-10px); + transition: all 0.1s ease-in-out; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + max-width: 280px; +} + +.copy-toast.success { + border-color: var(--toast-border-success); +} + +.copy-toast.error { + border-color: var(--toast-border-error); +} + +.copy-toast.show { + opacity: 1; + transform: translateY(0); +} diff --git a/docs/theme/plugins.js b/docs/theme/plugins.js index 76a295353f7abc391ba4d84998a636a8ed6dab36..44c4c59978d31bd24ed0f0a28266868bb9951e51 100644 --- a/docs/theme/plugins.js +++ b/docs/theme/plugins.js @@ -110,3 +110,122 @@ function darkModeToggle() { } }); } + +const copyMarkdown = () => { + const copyButton = document.getElementById("copy-markdown-toggle"); + if (!copyButton) return; + + // Store the original icon class, loading state, and timeout reference + const originalIconClass = "fa fa-copy"; + let isLoading = false; + let iconTimeoutId = null; + + const getCurrentPagePath = () => { + const pathname = window.location.pathname; + + // Handle root docs path + if (pathname === "/docs/" || pathname === "/docs") { + return "getting-started.md"; + } + + // Remove /docs/ prefix and .html suffix, then add .md + const cleanPath = pathname + .replace(/^\/docs\//, "") + .replace(/\.html$/, "") + .replace(/\/$/, ""); + + return cleanPath ? cleanPath + ".md" : "getting-started.md"; + }; + + const showToast = (message, isSuccess = true) => { + // Remove existing toast if any + const existingToast = document.getElementById("copy-toast"); + existingToast?.remove(); + + const toast = document.createElement("div"); + toast.id = "copy-toast"; + toast.className = `copy-toast ${isSuccess ? "success" : "error"}`; + toast.textContent = message; + + document.body.appendChild(toast); + + // Show toast with animation + setTimeout(() => { + toast.classList.add("show"); + }, 10); + + // Hide and remove toast after 2 seconds + setTimeout(() => { + toast.classList.remove("show"); + setTimeout(() => { + toast.parentNode?.removeChild(toast); + }, 300); + }, 2000); + }; + + const changeButtonIcon = (iconClass, duration = 1000) => { + const icon = copyButton.querySelector("i"); + if (!icon) return; + + // Clear any existing timeout + if (iconTimeoutId) { + clearTimeout(iconTimeoutId); + iconTimeoutId = null; + } + + icon.className = iconClass; + + if (duration > 0) { + iconTimeoutId = setTimeout(() => { + icon.className = originalIconClass; + iconTimeoutId = null; + }, duration); + } + }; + + const fetchAndCopyMarkdown = async () => { + // Prevent multiple simultaneous requests + if (isLoading) return; + + try { + isLoading = true; + changeButtonIcon("fa fa-spinner fa-spin", 0); // Don't auto-restore spinner + + const pagePath = getCurrentPagePath(); + const rawUrl = `https://raw.githubusercontent.com/zed-industries/zed/main/docs/src/${pagePath}`; + + const response = await fetch(rawUrl); + if (!response.ok) { + throw new Error( + `Failed to fetch markdown: ${response.status} ${response.statusText}`, + ); + } + + const markdownContent = await response.text(); + + // Copy to clipboard using modern API + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(markdownContent); + } else { + // Fallback: throw error if clipboard API isn't available + throw new Error("Clipboard API not supported in this browser"); + } + + changeButtonIcon("fa fa-check", 1000); + showToast("Page content copied to clipboard!"); + } catch (error) { + console.error("Error copying markdown:", error); + changeButtonIcon("fa fa-exclamation-triangle", 2000); + showToast("Failed to copy markdown. Please try again.", false); + } finally { + isLoading = false; + } + }; + + copyButton.addEventListener("click", fetchAndCopyMarkdown); +}; + +// Initialize functionality when DOM is loaded +document.addEventListener("DOMContentLoaded", () => { + copyMarkdown(); +}); From 853e625259d89cc7aa375c838141eee1652c9265 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Mon, 15 Sep 2025 16:29:58 -0600 Subject: [PATCH 042/721] edit predictions: Add new excerpt logic (not yet used) (#38226) Release Notes: - N/A --------- Co-authored-by: agus --- Cargo.lock | 16 + Cargo.toml | 2 + crates/edit_prediction_context/Cargo.toml | 29 + crates/edit_prediction_context/LICENSE-GPL | 1 + .../src/edit_prediction_context.rs | 3 + crates/edit_prediction_context/src/excerpt.rs | 595 ++++++++++++++++++ crates/language/src/buffer.rs | 73 ++- crates/language/src/outline.rs | 9 + crates/multi_buffer/src/multi_buffer.rs | 12 + crates/outline_panel/src/outline_panel.rs | 1 + 10 files changed, 718 insertions(+), 23 deletions(-) create mode 100644 crates/edit_prediction_context/Cargo.toml create mode 120000 crates/edit_prediction_context/LICENSE-GPL create mode 100644 crates/edit_prediction_context/src/edit_prediction_context.rs create mode 100644 crates/edit_prediction_context/src/excerpt.rs diff --git a/Cargo.lock b/Cargo.lock index caeb7e714da6c7f2c689d51e82500cc69d68f35d..2b1427a435a07c7485388a668e51035d55b56b39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5039,6 +5039,22 @@ dependencies = [ "zeta", ] +[[package]] +name = "edit_prediction_context" +version = "0.1.0" +dependencies = [ + "gpui", + "indoc", + "language", + "log", + "pretty_assertions", + "text", + "tree-sitter", + "util", + "workspace-hack", + "zlog", +] + [[package]] name = "editor" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index de1d216561b9d3074eecff5c36d6405a29c4f7d3..5cad901d18d5832f0d593c6c436613e507cdabe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ members = [ "crates/docs_preprocessor", "crates/edit_prediction", "crates/edit_prediction_button", + "crates/edit_prediction_context", "crates/editor", "crates/eval", "crates/explorer_command_injector", @@ -312,6 +313,7 @@ icons = { path = "crates/icons" } image_viewer = { path = "crates/image_viewer" } edit_prediction = { path = "crates/edit_prediction" } edit_prediction_button = { path = "crates/edit_prediction_button" } +edit_prediction_context = { path = "crates/edit_prediction_context" } inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } jj = { path = "crates/jj" } diff --git a/crates/edit_prediction_context/Cargo.toml b/crates/edit_prediction_context/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6729dcd39b67c5374d745b1811b73a8b8af4f2aa --- /dev/null +++ b/crates/edit_prediction_context/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "edit_prediction_context" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/edit_prediction_context.rs" + +[dependencies] +language.workspace = true +workspace-hack.workspace = true +tree-sitter.workspace = true +text.workspace = true +log.workspace = true +util.workspace = true + +[dev-dependencies] +gpui = { workspace = true, features = ["test-support"] } +indoc.workspace = true +language = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true +text = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +zlog.workspace = true diff --git a/crates/edit_prediction_context/LICENSE-GPL b/crates/edit_prediction_context/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/edit_prediction_context/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e6cad45a8032cb1afaafd95eb10c80e61cff097 --- /dev/null +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -0,0 +1,3 @@ +mod excerpt; + +pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions}; diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a20da76f71834d226707fe36f93d3667158372c --- /dev/null +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -0,0 +1,595 @@ +use language::BufferSnapshot; +use std::ops::Range; +use text::{OffsetRangeExt as _, Point, ToOffset as _, ToPoint as _}; +use tree_sitter::{Node, TreeCursor}; +use util::RangeExt; + +// TODO: +// +// - Test parent signatures +// +// - Decide whether to count signatures against the excerpt size. Could instead defer this to prompt +// planning. +// +// - Still return an excerpt even if the line around the cursor doesn't fit (e.g. for a markdown +// paragraph). +// +// - Truncation of long lines. +// +// - Filter outer syntax layers that don't support edit prediction. + +#[derive(Debug, Clone)] +pub struct EditPredictionExcerptOptions { + /// Limit for the number of bytes in the window around the cursor. + pub max_bytes: usize, + /// Minimum number of bytes in the window around the cursor. When syntax tree selection results + /// in an excerpt smaller than this, it will fall back on line-based selection. + pub min_bytes: usize, + /// Target ratio of bytes before the cursor divided by total bytes in the window. + pub target_before_cursor_over_total_bytes: f32, + /// Whether to include parent signatures + pub include_parent_signatures: bool, +} + +#[derive(Clone)] +pub struct EditPredictionExcerpt { + pub range: Range, + pub parent_signature_ranges: Vec>, + pub size: usize, +} + +impl EditPredictionExcerpt { + /// Selects an excerpt around a buffer position, attempting to choose logical boundaries based + /// on TreeSitter structure and approximately targeting a goal ratio of bytesbefore vs after the + /// cursor. When `include_parent_signatures` is true, the excerpt also includes the signatures + /// of parent outline items. + /// + /// First tries to use AST node boundaries to select the excerpt, and falls back on line-based + /// expansion. + /// + /// Returns `None` if the line around the cursor doesn't fit. + pub fn select_from_buffer( + query_point: Point, + buffer: &BufferSnapshot, + options: &EditPredictionExcerptOptions, + ) -> Option { + if buffer.len() <= options.max_bytes { + log::debug!( + "using entire file for excerpt since source length ({}) <= window max bytes ({})", + buffer.len(), + options.max_bytes + ); + return Some(EditPredictionExcerpt::new(0..buffer.len(), Vec::new())); + } + + let query_offset = query_point.to_offset(buffer); + let query_range = Point::new(query_point.row, 0).to_offset(buffer) + ..Point::new(query_point.row + 1, 0).to_offset(buffer); + if query_range.len() >= options.max_bytes { + return None; + } + + // TODO: Don't compute text / annotation_range / skip converting to and from anchors. + let outline_items = if options.include_parent_signatures { + buffer + .outline_items_containing(query_range.clone(), false, None) + .into_iter() + .flat_map(|item| { + Some(ExcerptOutlineItem { + item_range: item.range.to_offset(&buffer), + signature_range: item.signature_range?.to_offset(&buffer), + }) + }) + .collect() + } else { + Vec::new() + }; + + let excerpt_selector = ExcerptSelector { + query_offset, + query_range, + outline_items: &outline_items, + buffer, + options, + }; + + if let Some(excerpt_ranges) = excerpt_selector.select_tree_sitter_nodes() { + if excerpt_ranges.size >= options.min_bytes { + return Some(excerpt_ranges); + } + log::debug!( + "tree-sitter excerpt was {} bytes, smaller than min of {}, falling back on line-based selection", + excerpt_ranges.size, + options.min_bytes + ); + } else { + log::debug!( + "couldn't find excerpt via tree-sitter, falling back on line-based selection" + ); + } + + excerpt_selector.select_lines() + } + + fn new(range: Range, parent_signature_ranges: Vec>) -> Self { + let size = range.len() + + parent_signature_ranges + .iter() + .map(|r| r.len()) + .sum::(); + Self { + range, + parent_signature_ranges, + size, + } + } + + fn with_expanded_range(&self, new_range: Range) -> Self { + if !new_range.contains_inclusive(&self.range) { + // this is an issue because parent_signature_ranges may be incorrect + log::error!("bug: with_expanded_range called with disjoint range"); + } + let mut parent_signature_ranges = Vec::with_capacity(self.parent_signature_ranges.len()); + let mut size = new_range.len(); + for range in &self.parent_signature_ranges { + if range.contains_inclusive(&new_range) { + break; + } + parent_signature_ranges.push(range.clone()); + size += range.len(); + } + Self { + range: new_range, + parent_signature_ranges, + size, + } + } + + fn parent_signatures_size(&self) -> usize { + self.size - self.range.len() + } +} + +struct ExcerptSelector<'a> { + query_offset: usize, + query_range: Range, + outline_items: &'a [ExcerptOutlineItem], + buffer: &'a BufferSnapshot, + options: &'a EditPredictionExcerptOptions, +} + +struct ExcerptOutlineItem { + item_range: Range, + signature_range: Range, +} + +impl<'a> ExcerptSelector<'a> { + /// Finds the largest node that is smaller than the window size and contains `query_range`. + fn select_tree_sitter_nodes(&self) -> Option { + let selected_layer_root = self.select_syntax_layer()?; + let mut cursor = selected_layer_root.walk(); + + loop { + let excerpt_range = node_line_start(cursor.node()).to_offset(&self.buffer) + ..node_line_end(cursor.node()).to_offset(&self.buffer); + if excerpt_range.contains_inclusive(&self.query_range) { + let excerpt = self.make_excerpt(excerpt_range); + if excerpt.size <= self.options.max_bytes { + return Some(self.expand_to_siblings(&mut cursor, excerpt)); + } + } else { + // TODO: Should still be able to handle this case via AST nodes. For example, this + // can happen if the cursor is between two methods in a large class file. + return None; + } + + if cursor + .goto_first_child_for_byte(self.query_range.start) + .is_none() + { + return None; + } + } + } + + /// Select the smallest syntax layer that exceeds max_len, or the largest if none exceed max_len. + fn select_syntax_layer(&self) -> Option> { + let mut smallest_exceeding_max_len: Option> = None; + let mut largest: Option> = None; + for layer in self + .buffer + .syntax_layers_for_range(self.query_range.start..self.query_range.start, true) + { + let layer_range = layer.node().byte_range(); + if !layer_range.contains_inclusive(&self.query_range) { + continue; + } + + if layer_range.len() > self.options.max_bytes { + match &smallest_exceeding_max_len { + None => smallest_exceeding_max_len = Some(layer.node()), + Some(existing) => { + if layer_range.len() < existing.byte_range().len() { + smallest_exceeding_max_len = Some(layer.node()); + } + } + } + } else { + match &largest { + None => largest = Some(layer.node()), + Some(existing) if layer_range.len() > existing.byte_range().len() => { + largest = Some(layer.node()) + } + _ => {} + } + } + } + + smallest_exceeding_max_len.or(largest) + } + + // motivation for this and `goto_previous_named_sibling` is to avoid including things like + // trailing unnamed "}" in body nodes + fn goto_next_named_sibling(cursor: &mut TreeCursor) -> bool { + while cursor.goto_next_sibling() { + if cursor.node().is_named() { + return true; + } + } + false + } + + fn goto_previous_named_sibling(cursor: &mut TreeCursor) -> bool { + while cursor.goto_previous_sibling() { + if cursor.node().is_named() { + return true; + } + } + false + } + + fn expand_to_siblings( + &self, + cursor: &mut TreeCursor, + mut excerpt: EditPredictionExcerpt, + ) -> EditPredictionExcerpt { + let mut forward_cursor = cursor.clone(); + let backward_cursor = cursor; + let mut forward_done = !Self::goto_next_named_sibling(&mut forward_cursor); + let mut backward_done = !Self::goto_previous_named_sibling(backward_cursor); + loop { + if backward_done && forward_done { + break; + } + + let mut forward = None; + while !forward_done { + let new_end = node_line_end(forward_cursor.node()).to_offset(&self.buffer); + if new_end > excerpt.range.end { + let new_excerpt = excerpt.with_expanded_range(excerpt.range.start..new_end); + if new_excerpt.size <= self.options.max_bytes { + forward = Some(new_excerpt); + break; + } else { + log::debug!("halting forward expansion, as it doesn't fit"); + forward_done = true; + break; + } + } + forward_done = !Self::goto_next_named_sibling(&mut forward_cursor); + } + + let mut backward = None; + while !backward_done { + let new_start = node_line_start(backward_cursor.node()).to_offset(&self.buffer); + if new_start < excerpt.range.start { + let new_excerpt = excerpt.with_expanded_range(new_start..excerpt.range.end); + if new_excerpt.size <= self.options.max_bytes { + backward = Some(new_excerpt); + break; + } else { + log::debug!("halting backward expansion, as it doesn't fit"); + backward_done = true; + break; + } + } + backward_done = !Self::goto_previous_named_sibling(backward_cursor); + } + + let go_forward = match (forward, backward) { + (Some(forward), Some(backward)) => { + let go_forward = self.is_better_excerpt(&forward, &backward); + if go_forward { + excerpt = forward; + } else { + excerpt = backward; + } + go_forward + } + (Some(forward), None) => { + log::debug!("expanding forward, since backward expansion has halted"); + excerpt = forward; + true + } + (None, Some(backward)) => { + log::debug!("expanding backward, since forward expansion has halted"); + excerpt = backward; + false + } + (None, None) => break, + }; + + if go_forward { + forward_done = !Self::goto_next_named_sibling(&mut forward_cursor); + } else { + backward_done = !Self::goto_previous_named_sibling(backward_cursor); + } + } + + excerpt + } + + fn select_lines(&self) -> Option { + // early return if line containing query_offset is already too large + let excerpt = self.make_excerpt(self.query_range.clone()); + if excerpt.size > self.options.max_bytes { + log::debug!( + "excerpt for cursor line is {} bytes, which exceeds the window", + excerpt.size + ); + return None; + } + let signatures_size = excerpt.parent_signatures_size(); + let bytes_remaining = self.options.max_bytes.saturating_sub(signatures_size); + + let before_bytes = + (self.options.target_before_cursor_over_total_bytes * bytes_remaining as f32) as usize; + + let start_point = { + let offset = self.query_offset.saturating_sub(before_bytes); + let point = offset.to_point(self.buffer); + Point::new(point.row + 1, 0) + }; + let start_offset = start_point.to_offset(&self.buffer); + let end_point = { + let offset = start_offset + bytes_remaining; + let point = offset.to_point(self.buffer); + Point::new(point.row, 0) + }; + let end_offset = end_point.to_offset(&self.buffer); + + // this could be expanded further since recalculated `signature_size` may be smaller, but + // skipping that for now for simplicity + // + // TODO: could also consider checking if lines immediately before / after fit. + let excerpt = self.make_excerpt(start_offset..end_offset); + if excerpt.size > self.options.max_bytes { + log::error!( + "bug: line-based excerpt selection has size {}, \ + which is {} bytes larger than the max size", + excerpt.size, + excerpt.size - self.options.max_bytes + ); + } + return Some(excerpt); + } + + fn make_excerpt(&self, range: Range) -> EditPredictionExcerpt { + let parent_signature_ranges = self + .outline_items + .iter() + .filter(|item| item.item_range.contains_inclusive(&range)) + .map(|item| item.signature_range.clone()) + .collect(); + EditPredictionExcerpt::new(range, parent_signature_ranges) + } + + /// Returns `true` if the `forward` excerpt is a better choice than the `backward` excerpt. + fn is_better_excerpt( + &self, + forward: &EditPredictionExcerpt, + backward: &EditPredictionExcerpt, + ) -> bool { + let forward_ratio = self.excerpt_range_ratio(forward); + let backward_ratio = self.excerpt_range_ratio(backward); + let forward_delta = + (forward_ratio - self.options.target_before_cursor_over_total_bytes).abs(); + let backward_delta = + (backward_ratio - self.options.target_before_cursor_over_total_bytes).abs(); + let forward_is_better = forward_delta <= backward_delta; + if forward_is_better { + log::debug!( + "expanding forward since {} is closer than {} to {}", + forward_ratio, + backward_ratio, + self.options.target_before_cursor_over_total_bytes + ); + } else { + log::debug!( + "expanding backward since {} is closer than {} to {}", + backward_ratio, + forward_ratio, + self.options.target_before_cursor_over_total_bytes + ); + } + forward_is_better + } + + /// Returns the ratio of bytes before the cursor over bytes within the range. + fn excerpt_range_ratio(&self, excerpt: &EditPredictionExcerpt) -> f32 { + let Some(bytes_before_cursor) = self.query_offset.checked_sub(excerpt.range.start) else { + log::error!("bug: edit prediction cursor offset is not outside the excerpt"); + return 0.0; + }; + bytes_before_cursor as f32 / excerpt.range.len() as f32 + } +} + +fn node_line_start(node: Node) -> Point { + Point::new(node.start_position().row as u32, 0) +} + +fn node_line_end(node: Node) -> Point { + Point::new(node.end_position().row as u32 + 1, 0) +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::{AppContext, TestAppContext}; + use language::{Buffer, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust}; + use util::test::{generate_marked_text, marked_text_offsets_by}; + + fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot { + let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang().into(), cx)); + buffer.read_with(cx, |buffer, _| buffer.snapshot()) + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) + .with_outline_query(include_str!("../../languages/src/rust/outline.scm")) + .unwrap() + } + + fn cursor_and_excerpt_range(text: &str) -> (String, usize, Range) { + let (text, offsets) = marked_text_offsets_by(text, vec!['ˇ', '«', '»']); + (text, offsets[&'ˇ'][0], offsets[&'«'][0]..offsets[&'»'][0]) + } + + fn check_example(options: EditPredictionExcerptOptions, text: &str, cx: &mut TestAppContext) { + let (text, cursor, expected_excerpt) = cursor_and_excerpt_range(text); + + let buffer = create_buffer(&text, cx); + let cursor_point = cursor.to_point(&buffer); + + let excerpt = EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &options) + .expect("Should select an excerpt"); + pretty_assertions::assert_eq!( + generate_marked_text(&text, std::slice::from_ref(&excerpt.range), false), + generate_marked_text(&text, &[expected_excerpt], false) + ); + assert!(excerpt.size <= options.max_bytes); + assert!(excerpt.range.contains(&cursor)); + } + + #[gpui::test] + fn test_ast_based_selection_current_node(cx: &mut TestAppContext) { + zlog::init_test(); + let text = r#" +fn main() { + let x = 1; +« let ˇy = 2; +» let z = 3; +}"#; + + let options = EditPredictionExcerptOptions { + max_bytes: 20, + min_bytes: 10, + target_before_cursor_over_total_bytes: 0.5, + include_parent_signatures: false, + }; + + check_example(options, text, cx); + } + + #[gpui::test] + fn test_ast_based_selection_parent_node(cx: &mut TestAppContext) { + zlog::init_test(); + let text = r#" +fn foo() {} + +«fn main() { + let x = 1; + let ˇy = 2; + let z = 3; +} +» +fn bar() {}"#; + + let options = EditPredictionExcerptOptions { + max_bytes: 65, + min_bytes: 10, + target_before_cursor_over_total_bytes: 0.5, + include_parent_signatures: false, + }; + + check_example(options, text, cx); + } + + #[gpui::test] + fn test_ast_based_selection_expands_to_siblings(cx: &mut TestAppContext) { + zlog::init_test(); + let text = r#" +fn main() { +« let x = 1; + let ˇy = 2; + let z = 3; +»}"#; + + let options = EditPredictionExcerptOptions { + max_bytes: 50, + min_bytes: 10, + target_before_cursor_over_total_bytes: 0.5, + include_parent_signatures: false, + }; + + check_example(options, text, cx); + } + + #[gpui::test] + fn test_line_based_selection(cx: &mut TestAppContext) { + zlog::init_test(); + let text = r#" +fn main() { + let x = 1; +« if true { + let ˇy = 2; + } + let z = 3; +»}"#; + + let options = EditPredictionExcerptOptions { + max_bytes: 60, + min_bytes: 45, + target_before_cursor_over_total_bytes: 0.5, + include_parent_signatures: false, + }; + + check_example(options, text, cx); + } + + #[gpui::test] + fn test_line_based_selection_with_before_cursor_ratio(cx: &mut TestAppContext) { + zlog::init_test(); + let text = r#" + fn main() { +« let a = 1; + let b = 2; + let c = 3; + let ˇd = 4; + let e = 5; + let f = 6; +» + let g = 7; + }"#; + + let options = EditPredictionExcerptOptions { + max_bytes: 120, + min_bytes: 10, + target_before_cursor_over_total_bytes: 0.6, + include_parent_signatures: false, + }; + + check_example(options, text, cx); + } +} diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index f03df08a55b2759885d133b9a7dc3556b549a184..77270807644830a233cbd6f3c47e4912ff47f543 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -3310,18 +3310,25 @@ impl BufferSnapshot { /// Iterates over every [`SyntaxLayer`] in the buffer. pub fn syntax_layers(&self) -> impl Iterator> + '_ { - self.syntax - .layers_for_range(0..self.len(), &self.text, true) + self.syntax_layers_for_range(0..self.len(), true) } pub fn syntax_layer_at(&self, position: D) -> Option> { let offset = position.to_offset(self); - self.syntax - .layers_for_range(offset..offset, &self.text, false) + self.syntax_layers_for_range(offset..offset, false) .filter(|l| l.node().end_byte() > offset) .last() } + pub fn syntax_layers_for_range( + &self, + range: Range, + include_hidden: bool, + ) -> impl Iterator> + '_ { + self.syntax + .layers_for_range(range, &self.text, include_hidden) + } + pub fn smallest_syntax_layer_containing( &self, range: Range, @@ -3859,9 +3866,12 @@ impl BufferSnapshot { text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, - body_range: item.body_range.map(|body_range| { - self.anchor_after(body_range.start)..self.anchor_before(body_range.end) - }), + signature_range: item + .signature_range + .map(|r| self.anchor_after(r.start)..self.anchor_before(r.end)), + body_range: item + .body_range + .map(|r| self.anchor_after(r.start)..self.anchor_before(r.end)), annotation_range: annotation_row_range.map(|annotation_range| { self.anchor_after(Point::new(annotation_range.start, 0)) ..self.anchor_before(Point::new( @@ -3901,38 +3911,51 @@ impl BufferSnapshot { let mut open_point = None; let mut close_point = None; + + let mut signature_start = None; + let mut signature_end = None; + let mut extend_signature_range = |node: tree_sitter::Node| { + if signature_start.is_none() { + signature_start = Some(Point::from_ts_point(node.start_position())); + } + signature_end = Some(Point::from_ts_point(node.end_position())); + }; + let mut buffer_ranges = Vec::new(); + let mut add_to_buffer_ranges = |node: tree_sitter::Node, node_is_name| { + let mut range = node.start_byte()..node.end_byte(); + let start = node.start_position(); + if node.end_position().row > start.row { + range.end = range.start + self.line_len(start.row as u32) as usize - start.column; + } + + if !range.is_empty() { + buffer_ranges.push((range, node_is_name)); + } + }; + for capture in mat.captures { - let node_is_name; if capture.index == config.name_capture_ix { - node_is_name = true; + add_to_buffer_ranges(capture.node, true); + extend_signature_range(capture.node); } else if Some(capture.index) == config.context_capture_ix || (Some(capture.index) == config.extra_context_capture_ix && include_extra_context) { - node_is_name = false; + add_to_buffer_ranges(capture.node, false); + extend_signature_range(capture.node); } else { if Some(capture.index) == config.open_capture_ix { open_point = Some(Point::from_ts_point(capture.node.end_position())); } else if Some(capture.index) == config.close_capture_ix { close_point = Some(Point::from_ts_point(capture.node.start_position())); } - - continue; - } - - let mut range = capture.node.start_byte()..capture.node.end_byte(); - let start = capture.node.start_position(); - if capture.node.end_position().row > start.row { - range.end = range.start + self.line_len(start.row as u32) as usize - start.column; - } - - if !range.is_empty() { - buffer_ranges.push((range, node_is_name)); } } + if buffer_ranges.is_empty() { return None; } + let mut text = String::new(); let mut highlight_ranges = Vec::new(); let mut name_ranges = Vec::new(); @@ -3941,7 +3964,6 @@ impl BufferSnapshot { true, ); let mut last_buffer_range_end = 0; - for (buffer_range, is_name) in buffer_ranges { let space_added = !text.is_empty() && buffer_range.start > last_buffer_range_end; if space_added { @@ -3983,12 +4005,17 @@ impl BufferSnapshot { last_buffer_range_end = buffer_range.end; } + let signature_range = signature_start + .zip(signature_end) + .map(|(start, end)| start..end); + Some(OutlineItem { depth: 0, // We'll calculate the depth later range: item_point_range, text, highlight_ranges, name_ranges, + signature_range, body_range: open_point.zip(close_point).map(|(start, end)| start..end), annotation_range: None, }) diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index d96cd90e03142c6498ae17bc63e1787d99e8557a..09c556cf98f58ea26925e1df8bde9d43ec72e6c7 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -19,6 +19,7 @@ pub struct OutlineItem { pub text: String, pub highlight_ranges: Vec<(Range, HighlightStyle)>, pub name_ranges: Vec>, + pub signature_range: Option>, pub body_range: Option>, pub annotation_range: Option>, } @@ -35,6 +36,10 @@ impl OutlineItem { text: self.text.clone(), highlight_ranges: self.highlight_ranges.clone(), name_ranges: self.name_ranges.clone(), + signature_range: self + .signature_range + .as_ref() + .map(|r| r.start.to_point(buffer)..r.end.to_point(buffer)), body_range: self .body_range .as_ref() @@ -208,6 +213,7 @@ mod tests { text: "class Foo".to_string(), highlight_ranges: vec![], name_ranges: vec![6..9], + signature_range: None, body_range: None, annotation_range: None, }, @@ -217,6 +223,7 @@ mod tests { text: "private".to_string(), highlight_ranges: vec![], name_ranges: vec![], + signature_range: None, body_range: None, annotation_range: None, }, @@ -241,6 +248,7 @@ mod tests { text: "fn process".to_string(), highlight_ranges: vec![], name_ranges: vec![3..10], + signature_range: None, body_range: None, annotation_range: None, }, @@ -250,6 +258,7 @@ mod tests { text: "struct DataProcessor".to_string(), highlight_ranges: vec![], name_ranges: vec![7..20], + signature_range: None, body_range: None, annotation_range: None, }, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f9d99473fbd196152418376d1bfbf63e21ad8b00..1d4c26cf126089261e3534af1da9083d2c126093 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -6129,6 +6129,12 @@ impl MultiBufferSnapshot { text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, + signature_range: item.signature_range.and_then(|signature_range| { + Some( + self.anchor_in_excerpt(*excerpt_id, signature_range.start)? + ..self.anchor_in_excerpt(*excerpt_id, signature_range.end)?, + ) + }), body_range: item.body_range.and_then(|body_range| { Some( self.anchor_in_excerpt(*excerpt_id, body_range.start)? @@ -6169,6 +6175,12 @@ impl MultiBufferSnapshot { text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, + signature_range: item.signature_range.and_then(|signature_range| { + Some( + self.anchor_in_excerpt(excerpt_id, signature_range.start)? + ..self.anchor_in_excerpt(excerpt_id, signature_range.end)?, + ) + }), body_range: item.body_range.and_then(|body_range| { Some( self.anchor_in_excerpt(excerpt_id, body_range.start)? diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 5b1c5b313d29751e53e08bd479eeb72d15527373..bde35a44cb06967a71e585203f3e5f6ee72f96d5 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -2481,6 +2481,7 @@ impl OutlinePanel { &OutlineItem { depth, annotation_range: None, + signature_range: None, range: search_data.context_range.clone(), text: search_data.context_text.clone(), highlight_ranges: search_data From 3dbccc828e4589ab9ea31299d7117e81ec251727 Mon Sep 17 00:00:00 2001 From: Alvaro Parker <64918109+AlvaroParker@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:48:04 -0300 Subject: [PATCH 043/721] Fix hover element on ACP thread mode selector (#38204) Closes #38197 This will render `^ click to also ...` on MacOS and `Ctrl + click to also ...` on Windows and Linux. |Before|After| |-|-| | image | image| On Mac: image Release Notes: - Fixed keymap hint when hovering over mode selector --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/acp/mode_selector.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/agent_ui/src/acp/mode_selector.rs b/crates/agent_ui/src/acp/mode_selector.rs index d4d424f41a36652881a50b65bc3dbe00c18fdcde..410874126665b7d622c7cf45e81596dce7f96823 100644 --- a/crates/agent_ui/src/acp/mode_selector.rs +++ b/crates/agent_ui/src/acp/mode_selector.rs @@ -107,13 +107,15 @@ impl ModeSelector { .text_sm() .text_color(Color::Muted.color(cx)) .child("Hold") - .child(div().pt_0p5().children(ui::render_modifiers( - &gpui::Modifiers::secondary_key(), - PlatformStyle::platform(), - None, - Some(ui::TextSize::Default.rems(cx).into()), - true, - ))) + .child(h_flex().flex_shrink_0().children( + ui::render_modifiers( + &gpui::Modifiers::secondary_key(), + PlatformStyle::platform(), + None, + Some(ui::TextSize::Default.rems(cx).into()), + true, + ), + )) .child(div().map(|this| { if is_default { this.child("to also unset as default") From ceb907e0dc3ce91215a51c2dde288e6c0b9562ff Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Tue, 16 Sep 2025 00:55:02 +0200 Subject: [PATCH 044/721] onboarding: Add scrollbar to pages (#38093) Closes #37214 This PR adds a scrollbar to the onboarding view and additionally ensures the scroll state is properly reset when switching between the different pages each time. Release Notes: - N/A --- crates/onboarding/src/onboarding.rs | 35 +++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index 9dcf27c7cbebf6621bbeb558619944c768e63fb6..835db1734d986b33d3f58c8d9f1a4883458cba31 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -5,8 +5,8 @@ use db::kvp::KEY_VALUE_STORE; use fs::Fs; use gpui::{ Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter, - FocusHandle, Focusable, Global, IntoElement, KeyContext, Render, SharedString, Subscription, - Task, WeakEntity, Window, actions, + FocusHandle, Focusable, Global, IntoElement, KeyContext, Render, ScrollHandle, SharedString, + Subscription, Task, WeakEntity, Window, actions, }; use notifications::status_toast::{StatusToast, ToastIcon}; use schemars::JsonSchema; @@ -15,7 +15,7 @@ use settings::{SettingsStore, VsCodeSettingsSource}; use std::sync::Arc; use ui::{ Avatar, ButtonLike, FluentBuilder, Headline, KeyBinding, ParentElement as _, - StatefulInteractiveElement, Vector, VectorName, prelude::*, rems_from_px, + StatefulInteractiveElement, Vector, VectorName, WithScrollbar, prelude::*, rems_from_px, }; use workspace::{ AppState, Workspace, WorkspaceId, @@ -237,6 +237,7 @@ struct Onboarding { focus_handle: FocusHandle, selected_page: SelectedPage, user_store: Entity, + scroll_handle: ScrollHandle, _settings_subscription: Subscription, } @@ -256,6 +257,7 @@ impl Onboarding { Self { workspace: workspace.weak_handle(), focus_handle: cx.focus_handle(), + scroll_handle: ScrollHandle::new(), selected_page: SelectedPage::Basics, user_store: workspace.user_store().clone(), _settings_subscription: cx @@ -280,6 +282,7 @@ impl Onboarding { } self.selected_page = page; + self.scroll_handle.set_offset(Default::default()); cx.notify(); cx.emit(ItemEvent::UpdateTab); } @@ -584,16 +587,23 @@ impl Render for Onboarding { .gap_12() .child(self.render_nav(window, cx)) .child( - v_flex() - .id("page-content") + div() .size_full() - .max_w_full() - .min_w_0() - .pl_12() - .border_l_1() - .border_color(cx.theme().colors().border_variant.opacity(0.5)) - .overflow_y_scroll() - .child(self.render_page(window, cx)), + .pr_6() + .child( + v_flex() + .id("page-content") + .size_full() + .max_w_full() + .min_w_0() + .pl_12() + .border_l_1() + .border_color(cx.theme().colors().border_variant.opacity(0.5)) + .overflow_y_scroll() + .child(self.render_page(window, cx)) + .track_scroll(&self.scroll_handle), + ) + .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx), ), ) } @@ -632,6 +642,7 @@ impl Item for Onboarding { workspace: self.workspace.clone(), user_store: self.user_store.clone(), selected_page: self.selected_page, + scroll_handle: ScrollHandle::new(), focus_handle: cx.focus_handle(), _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), })) From 6446963a0ca93f3d311db261a457c7d3879cafa5 Mon Sep 17 00:00:00 2001 From: Owen Kelly Date: Tue, 16 Sep 2025 10:27:25 +1000 Subject: [PATCH 045/721] agent: Make assistant panel input size configurable (#37975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release Notes: - Added the `agent. message_editor_min_lines `setting to allow users to customize the agent panel message editor default size by using a different minimum number of lines. Screenshot 2025-09-11 at 5 47 18 pm --------- Co-authored-by: Danilo Leal --- assets/settings/default.json | 6 +++++- crates/agent_settings/src/agent_settings.rs | 13 +++++++++++++ crates/agent_ui/src/acp/thread_view.rs | 12 +++++------- docs/src/ai/agent-settings.md | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5dcb418184f592e941d23763f7fe39898413cb79..daab56dc93976728667ed6995cb445f363ee2be8 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -914,7 +914,11 @@ /// Whether to have terminal cards in the agent panel expanded, showing the whole command output. /// /// Default: true - "expand_terminal_card": true + "expand_terminal_card": true, + // Minimum number of lines to display in the agent message editor. + // + // Default: 4 + "message_editor_min_lines": 4 }, // The settings for slash commands. "slash_commands": { diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index e850945a40f46f31543fad2631216139706b405a..153b64a3950718c3a8e8770a362b7c43a9bf443f 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -75,6 +75,7 @@ pub struct AgentSettings { pub expand_edit_card: bool, pub expand_terminal_card: bool, pub use_modifier_to_send: bool, + pub message_editor_min_lines: usize, } impl AgentSettings { @@ -107,6 +108,10 @@ impl AgentSettings { model, }); } + + pub fn set_message_editor_max_lines(&self) -> usize { + self.message_editor_min_lines * 2 + } } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] @@ -320,6 +325,10 @@ pub struct AgentSettingsContent { /// /// Default: false use_modifier_to_send: Option, + /// Minimum number of lines of height the agent message editor should have. + /// + /// Default: 4 + message_editor_min_lines: Option, } #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] @@ -472,6 +481,10 @@ impl Settings for AgentSettings { &mut settings.use_modifier_to_send, value.use_modifier_to_send, ); + merge( + &mut settings.message_editor_min_lines, + value.message_editor_min_lines, + ); settings .model_parameters diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index ceed3f8f98fbf95f5b4278d8354e30e90299b18d..73b1fee3caa60ce14bceef7245470c0ec0c02a3d 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -71,9 +71,6 @@ use crate::{ RejectOnce, ToggleBurnMode, ToggleProfileSelector, }; -pub const MIN_EDITOR_LINES: usize = 4; -pub const MAX_EDITOR_LINES: usize = 8; - #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum ThreadFeedback { Positive, @@ -357,8 +354,8 @@ impl AcpThreadView { agent.name(), &placeholder, editor::EditorMode::AutoHeight { - min_lines: MIN_EDITOR_LINES, - max_lines: Some(MAX_EDITOR_LINES), + min_lines: AgentSettings::get_global(cx).message_editor_min_lines, + max_lines: Some(AgentSettings::get_global(cx).set_message_editor_max_lines()), }, window, cx, @@ -857,10 +854,11 @@ impl AcpThreadView { cx, ) } else { + let agent_settings = AgentSettings::get_global(cx); editor.set_mode( EditorMode::AutoHeight { - min_lines: MIN_EDITOR_LINES, - max_lines: Some(MAX_EDITOR_LINES), + min_lines: agent_settings.message_editor_min_lines, + max_lines: Some(agent_settings.set_message_editor_max_lines()), }, cx, ) diff --git a/docs/src/ai/agent-settings.md b/docs/src/ai/agent-settings.md index d78f812e4704123be34144e84709df71474c82c0..d13e7733878339165293a250d1d5f253ef799e5d 100644 --- a/docs/src/ai/agent-settings.md +++ b/docs/src/ai/agent-settings.md @@ -170,6 +170,21 @@ The default value is `false`. > This setting is available via the Agent Panel's settings UI. +### Message Editor Size + +Use the `message_editor_min_lines` setting to control minimum number of lines of height the agent message editor should have. +It is set to `4` by default, and the max number of lines is always double of the minimum. + +```json +{ + "agent": { + "message_editor_min_lines": 4 + } +} +``` + +> This setting is currently available only in Preview. + ### Modifier to Send Make a modifier (`cmd` on macOS, `ctrl` on Linux) required to send messages. From 555b6ee4e5aaa67e124411aeca121b4ae94649d7 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:06:45 -0300 Subject: [PATCH 046/721] agent: Add small UI fixes (#38231) Release Notes: - N/A --- crates/agent_ui/src/acp/thread_history.rs | 30 +++++++++++++---------- crates/agent_ui/src/acp/thread_view.rs | 10 +++++--- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_history.rs b/crates/agent_ui/src/acp/thread_history.rs index ed508ea18da7df3426fc13b137b97f37267ed283..cd696f33fa44976e0784c79d1945b548feb20a50 100644 --- a/crates/agent_ui/src/acp/thread_history.rs +++ b/crates/agent_ui/src/acp/thread_history.rs @@ -500,20 +500,24 @@ impl Render for AcpThreadHistory { ), ) } else { - view.pr_5() - .child( - uniform_list( - "thread-history", - self.visible_items.len(), - cx.processor(|this, range: Range, window, cx| { - this.render_list_items(range, window, cx) - }), - ) - .p_1() - .track_scroll(self.scroll_handle.clone()) - .flex_grow(), + view.child( + uniform_list( + "thread-history", + self.visible_items.len(), + cx.processor(|this, range: Range, window, cx| { + this.render_list_items(range, window, cx) + }), ) - .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx) + .p_1() + .pr_4() + .track_scroll(self.scroll_handle.clone()) + .flex_grow(), + ) + .vertical_scrollbar_for( + self.scroll_handle.clone(), + window, + cx, + ) } }) } diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 73b1fee3caa60ce14bceef7245470c0ec0c02a3d..8e8f908bc2eea651babb73749e26cb2d6474f74f 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -3182,10 +3182,14 @@ impl AcpThreadView { }; Button::new(SharedString::from(method_id.clone()), name) - .when(ix == 0, |el| { - el.style(ButtonStyle::Tinted(ui::TintColor::Warning)) - }) .label_size(LabelSize::Small) + .map(|this| { + if ix == 0 { + this.style(ButtonStyle::Tinted(TintColor::Warning)) + } else { + this.style(ButtonStyle::Outlined) + } + }) .on_click({ cx.listener(move |this, _, window, cx| { telemetry::event!( From d9860775921d0217c822380e719cd7ae81c0f967 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 15 Sep 2025 22:30:56 -0400 Subject: [PATCH 047/721] client: Hide usage when not available (#38234) Release Notes: - N/A --- crates/client/src/user.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 63626e8ce1f3b25c742f227a56556545762367c3..de0668b406c512eabfc70f4702466f013eb8c515 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -754,6 +754,10 @@ impl UserStore { } pub fn model_request_usage(&self) -> Option { + if self.plan().is_some_and(|plan| plan.is_v2()) { + return None; + } + self.model_request_usage } From 1c09985fb3a8ab43c251f53c0968cb01e7d41335 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 16 Sep 2025 09:31:28 +0200 Subject: [PATCH 048/721] worktree: Add more context to `log_err` calls (#38239) Release Notes: - N/A --- crates/worktree/src/worktree.rs | 99 +++++++++++++++++---------- crates/worktree/src/worktree_tests.rs | 10 +-- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index a0a48782ede132c9c1e31439c156fbbe4dcca1d8..d3f1976500fb19d31a2d2f44c7d63933552eec15 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -483,7 +483,11 @@ impl Worktree { true }); - let root_file_handle = fs.open_handle(&abs_path).await.log_err(); + let root_file_handle = fs + .open_handle(&abs_path) + .await + .context("failed to open local worktree root") + .log_err(); cx.new(move |cx: &mut Context| { let mut snapshot = LocalSnapshot { @@ -605,8 +609,7 @@ impl Worktree { { let mut lock = background_snapshot.lock(); lock.0 - .apply_remote_update(update.clone(), &settings.file_scan_inclusions) - .log_err(); + .apply_remote_update(update.clone(), &settings.file_scan_inclusions); lock.1.push(update); } snapshot_updated_tx.send(()).await.ok(); @@ -2484,7 +2487,7 @@ impl Snapshot { &mut self, update: proto::UpdateWorktree, always_included_paths: &PathMatcher, - ) -> Result<()> { + ) { log::debug!( "applying remote worktree update. {} entries updated, {} removed", update.updated_entries.len(), @@ -2507,7 +2510,7 @@ impl Snapshot { } for entry in update.updated_entries { - let entry = Entry::try_from((&self.root_char_bag, always_included_paths, entry))?; + let entry = Entry::from((&self.root_char_bag, always_included_paths, entry)); if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) { entries_by_path_edits.push(Edit::Remove(PathKey(path.clone()))); } @@ -2532,8 +2535,6 @@ impl Snapshot { if update.is_last_update { self.completed_scan_id = update.scan_id as usize; } - - Ok(()) } pub fn entry_count(&self) -> usize { @@ -3159,7 +3160,8 @@ impl BackgroundScannerState { dot_git_path, fs, watcher, - ); + ) + .log_err(); } fn insert_git_repository_for_path( @@ -3168,12 +3170,25 @@ impl BackgroundScannerState { dot_git_path: Arc, fs: &dyn Fs, watcher: &dyn Watcher, - ) -> Option { - let work_dir_entry = self.snapshot.entry_for_path(work_directory.path_key().0)?; + ) -> Result { + let work_dir_entry = self + .snapshot + .entry_for_path(work_directory.path_key().0) + .with_context(|| { + format!( + "working directory `{}` not indexed", + work_directory.path_key().0.display() + ) + })?; let work_directory_abs_path = self .snapshot .work_directory_abs_path(&work_directory) - .log_err()?; + .with_context(|| { + format!( + "invalid working directory: {}", + work_directory.path_key().0.display() + ) + })?; let dot_git_abs_path: Arc = self .snapshot @@ -3185,9 +3200,15 @@ impl BackgroundScannerState { let (repository_dir_abs_path, common_dir_abs_path) = discover_git_paths(&dot_git_abs_path, fs); - watcher.add(&common_dir_abs_path).log_err(); + watcher + .add(&common_dir_abs_path) + .context("failed to add common directory to watcher") + .log_err(); if !repository_dir_abs_path.starts_with(&common_dir_abs_path) { - watcher.add(&repository_dir_abs_path).log_err(); + watcher + .add(&repository_dir_abs_path) + .context("failed to add repository directory to watcher") + .log_err(); } let work_directory_id = work_dir_entry.id; @@ -3207,7 +3228,7 @@ impl BackgroundScannerState { .insert(work_directory_id, local_repository.clone()); log::trace!("inserting new local git repository"); - Some(local_repository) + Ok(local_repository) } } @@ -3228,7 +3249,10 @@ async fn is_git_dir(path: &Path, fs: &dyn Fs) -> bool { } async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { - let contents = fs.load(abs_path).await?; + let contents = fs + .load(abs_path) + .await + .with_context(|| format!("failed to load gitignore file at {}", abs_path.display()))?; let parent = abs_path.parent().unwrap_or_else(|| Path::new("/")); let mut builder = GitignoreBuilder::new(parent); for line in contents.lines() { @@ -3850,12 +3874,15 @@ impl BackgroundScanner { .ignores_by_parent_abs_path .extend(ignores); let containing_git_repository = repo.and_then(|(ancestor_dot_git, work_directory)| { - self.state.lock().insert_git_repository_for_path( - work_directory, - ancestor_dot_git.as_path().into(), - self.fs.as_ref(), - self.watcher.as_ref(), - )?; + self.state + .lock() + .insert_git_repository_for_path( + work_directory, + ancestor_dot_git.as_path().into(), + self.fs.as_ref(), + self.watcher.as_ref(), + ) + .log_err()?; Some(ancestor_dot_git) }); @@ -3866,7 +3893,7 @@ impl BackgroundScanner { if let Some(global_gitignore_path) = global_gitignore_path.as_ref() { build_gitignore(global_gitignore_path, self.fs.as_ref()) .await - .log_err() + .ok() .map(Arc::new) } else { None @@ -4661,12 +4688,14 @@ impl BackgroundScanner { 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(), - ); + state + .insert_git_repository_for_path( + work_directory, + ancestor_dot_git.as_path().into(), + self.fs.as_ref(), + self.watcher.as_ref(), + ) + .log_err(); } } } @@ -5611,12 +5640,10 @@ impl<'a> From<&'a Entry> for proto::Entry { } } -impl<'a> TryFrom<(&'a CharBag, &PathMatcher, proto::Entry)> for Entry { - type Error = anyhow::Error; - - fn try_from( - (root_char_bag, always_included, entry): (&'a CharBag, &PathMatcher, proto::Entry), - ) -> Result { +impl From<(&CharBag, &PathMatcher, proto::Entry)> for Entry { + fn from( + (root_char_bag, always_included, entry): (&CharBag, &PathMatcher, proto::Entry), + ) -> Self { let kind = if entry.is_dir { EntryKind::Dir } else { @@ -5626,7 +5653,7 @@ impl<'a> TryFrom<(&'a CharBag, &PathMatcher, proto::Entry)> for Entry { let path = Arc::::from_proto(entry.path); let char_bag = char_bag_for_path(*root_char_bag, &path); let is_always_included = always_included.is_match(path.as_ref()); - Ok(Entry { + Entry { id: ProjectEntryId::from_proto(entry.id), kind, path, @@ -5642,7 +5669,7 @@ impl<'a> TryFrom<(&'a CharBag, &PathMatcher, proto::Entry)> for Entry { is_private: false, char_bag, is_fifo: entry.is_fifo, - }) + } } } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 796840367a0dca717fda7f3973eb8bcc17dc5f57..5022c8965192a36b50e3b0a325a87b3988e28efd 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -1258,8 +1258,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { move |update| { snapshot .lock() - .apply_remote_update(update, &settings.file_scan_inclusions) - .unwrap(); + .apply_remote_update(update, &settings.file_scan_inclusions); async { true } } }); @@ -1489,8 +1488,7 @@ async fn test_random_worktree_operations_during_initial_scan( for update in updates.lock().iter() { if update.scan_id >= updated_snapshot.scan_id() as u64 { updated_snapshot - .apply_remote_update(update.clone(), &settings.file_scan_inclusions) - .unwrap(); + .apply_remote_update(update.clone(), &settings.file_scan_inclusions); } } @@ -1625,9 +1623,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() { for update in updates.lock().iter() { if update.scan_id >= prev_snapshot.scan_id() as u64 { - prev_snapshot - .apply_remote_update(update.clone(), &settings.file_scan_inclusions) - .unwrap(); + prev_snapshot.apply_remote_update(update.clone(), &settings.file_scan_inclusions); } } From f321d02207775c548ee9d7e9b88915fff76112af Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 16 Sep 2025 10:07:02 +0200 Subject: [PATCH 049/721] auto_update: Show update error on hover and open logs on click (#38241) Release Notes: - Improved error reporting when auto-updating fails --- .../src/activity_indicator.rs | 44 +++++++++---------- crates/auto_update/src/auto_update.rs | 14 +++--- crates/title_bar/src/title_bar.rs | 6 +-- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 1870ab74db214b518bb0b543166067e636f14965..f35b2ad17879c57b15ac8579e6b50a26110ff21d 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -1,4 +1,4 @@ -use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage, VersionCheckType}; +use auto_update::{AutoUpdateStatus, AutoUpdater, DismissMessage, VersionCheckType}; use editor::Editor; use extension_host::{ExtensionOperation, ExtensionStore}; use futures::StreamExt; @@ -280,18 +280,13 @@ impl ActivityIndicator { }); } - fn dismiss_error_message( - &mut self, - _: &DismissErrorMessage, - _: &mut Window, - cx: &mut Context, - ) { - let error_dismissed = if let Some(updater) = &self.auto_updater { - updater.update(cx, |updater, cx| updater.dismiss_error(cx)) + fn dismiss_message(&mut self, _: &DismissMessage, _: &mut Window, cx: &mut Context) { + let dismissed = if let Some(updater) = &self.auto_updater { + updater.update(cx, |updater, cx| updater.dismiss(cx)) } else { false }; - if error_dismissed { + if dismissed { return; } @@ -513,7 +508,7 @@ impl ActivityIndicator { on_click: Some(Arc::new(move |this, window, cx| { this.statuses .retain(|status| !downloading.contains(&status.name)); - this.dismiss_error_message(&DismissErrorMessage, window, cx) + this.dismiss_message(&DismissMessage, window, cx) })), tooltip_message: None, }); @@ -542,7 +537,7 @@ impl ActivityIndicator { on_click: Some(Arc::new(move |this, window, cx| { this.statuses .retain(|status| !checking_for_update.contains(&status.name)); - this.dismiss_error_message(&DismissErrorMessage, window, cx) + this.dismiss_message(&DismissMessage, window, cx) })), tooltip_message: None, }); @@ -650,13 +645,14 @@ impl ActivityIndicator { .and_then(|updater| match &updater.read(cx).status() { AutoUpdateStatus::Checking => Some(Content { icon: Some( - Icon::new(IconName::Download) + Icon::new(IconName::LoadCircle) .size(IconSize::Small) + .with_rotate_animation(3) .into_any_element(), ), message: "Checking for Zed updates…".to_string(), on_click: Some(Arc::new(|this, window, cx| { - this.dismiss_error_message(&DismissErrorMessage, window, cx) + this.dismiss_message(&DismissMessage, window, cx) })), tooltip_message: None, }), @@ -668,19 +664,20 @@ impl ActivityIndicator { ), message: "Downloading Zed update…".to_string(), on_click: Some(Arc::new(|this, window, cx| { - this.dismiss_error_message(&DismissErrorMessage, window, cx) + this.dismiss_message(&DismissMessage, window, cx) })), tooltip_message: Some(Self::version_tooltip_message(version)), }), AutoUpdateStatus::Installing { version } => Some(Content { icon: Some( - Icon::new(IconName::Download) + Icon::new(IconName::LoadCircle) .size(IconSize::Small) + .with_rotate_animation(3) .into_any_element(), ), message: "Installing Zed update…".to_string(), on_click: Some(Arc::new(|this, window, cx| { - this.dismiss_error_message(&DismissErrorMessage, window, cx) + this.dismiss_message(&DismissMessage, window, cx) })), tooltip_message: Some(Self::version_tooltip_message(version)), }), @@ -690,17 +687,18 @@ impl ActivityIndicator { on_click: Some(Arc::new(move |_, _, cx| workspace::reload(cx))), tooltip_message: Some(Self::version_tooltip_message(version)), }), - AutoUpdateStatus::Errored => Some(Content { + AutoUpdateStatus::Errored { error } => Some(Content { icon: Some( Icon::new(IconName::Warning) .size(IconSize::Small) .into_any_element(), ), - message: "Auto update failed".to_string(), + message: "Failed to update Zed".to_string(), on_click: Some(Arc::new(|this, window, cx| { - this.dismiss_error_message(&DismissErrorMessage, window, cx) + window.dispatch_action(Box::new(workspace::OpenLog), cx); + this.dismiss_message(&DismissMessage, window, cx); })), - tooltip_message: None, + tooltip_message: Some(format!("{error}")), }), AutoUpdateStatus::Idle => None, }) @@ -738,7 +736,7 @@ impl ActivityIndicator { })), message, on_click: Some(Arc::new(|this, window, cx| { - this.dismiss_error_message(&Default::default(), window, cx) + this.dismiss_message(&Default::default(), window, cx) })), tooltip_message: None, }) @@ -777,7 +775,7 @@ impl Render for ActivityIndicator { let result = h_flex() .id("activity-indicator") .on_action(cx.listener(Self::show_error_message)) - .on_action(cx.listener(Self::dismiss_error_message)); + .on_action(cx.listener(Self::dismiss_message)); let Some(content) = self.content_to_render(cx) else { return result; }; diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index ac5b03d40d615ea2887d8726ba248304b34c9874..e6274cf3988d4bd35dead9c988a294c922c2aaf3 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -34,7 +34,7 @@ actions!( /// Checks for available updates. Check, /// Dismisses the update error message. - DismissErrorMessage, + DismissMessage, /// Opens the release notes for the current version in a browser. ViewReleaseNotes, ] @@ -55,14 +55,14 @@ pub enum VersionCheckType { Semantic(SemanticVersion), } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub enum AutoUpdateStatus { Idle, Checking, Downloading { version: VersionCheckType }, Installing { version: VersionCheckType }, Updated { version: VersionCheckType }, - Errored, + Errored { error: Arc }, } impl AutoUpdateStatus { @@ -383,7 +383,9 @@ impl AutoUpdater { } UpdateCheckType::Manual => { log::error!("auto-update failed: error:{:?}", error); - AutoUpdateStatus::Errored + AutoUpdateStatus::Errored { + error: Arc::new(error), + } } }; @@ -402,8 +404,8 @@ impl AutoUpdater { self.status.clone() } - pub fn dismiss_error(&mut self, cx: &mut Context) -> bool { - if self.status == AutoUpdateStatus::Idle { + pub fn dismiss(&mut self, cx: &mut Context) -> bool { + if let AutoUpdateStatus::Idle = self.status { return false; } self.status = AutoUpdateStatus::Idle; diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index a6285e1d1d8593c73b2b0c6a79913cb0f16f6e00..129b5645641a01ba22d6993621b92a17664f5c8a 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -637,9 +637,9 @@ impl TitleBar { Some(AutoUpdateStatus::Installing { .. }) | Some(AutoUpdateStatus::Downloading { .. }) | Some(AutoUpdateStatus::Checking) => "Updating...", - Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { - "Please update Zed to Collaborate" - } + Some(AutoUpdateStatus::Idle) + | Some(AutoUpdateStatus::Errored { .. }) + | None => "Please update Zed to Collaborate", }; Some( From c0710fa8ca3c3f579e76fe8d658c79ae6f497107 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 16 Sep 2025 12:18:10 +0200 Subject: [PATCH 050/721] agent_servers: Set proxy env for all ACP agents (#38247) - Use ProxySettings::proxy_url to read from settings or env - Export HTTP(S)_PROXY and NO_PROXY for agent CLIs - Add read_no_proxy_from_env and move parsing from main Closes https://github.com/zed-industries/claude-code-acp/issues/46 Release Notes: - acp: Pass proxy settings through to all ACP agents --- Cargo.lock | 1 + crates/agent_servers/Cargo.toml | 1 + crates/agent_servers/src/agent_servers.rs | 28 ++++++++++++++++++++++- crates/agent_servers/src/claude.rs | 5 ++-- crates/agent_servers/src/custom.rs | 5 ++-- crates/agent_servers/src/gemini.rs | 23 ++++--------------- crates/client/src/client.rs | 16 ++++++++++++- crates/http_client/src/http_client.rs | 6 +++++ crates/zed/src/main.rs | 12 +--------- 9 files changed, 62 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b1427a435a07c7485388a668e51035d55b56b39..deb0dba0dac2f766d243a21801902bee66d156b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,7 @@ dependencies = [ "futures 0.3.31", "gpui", "gpui_tokio", + "http_client", "indoc", "language", "language_model", diff --git a/crates/agent_servers/Cargo.toml b/crates/agent_servers/Cargo.toml index bb3fe6ff9078535b500e28f4beeab957929546a5..a168da05c83482f9d5b34118a74ee5e1f15e2e37 100644 --- a/crates/agent_servers/Cargo.toml +++ b/crates/agent_servers/Cargo.toml @@ -30,6 +30,7 @@ fs.workspace = true futures.workspace = true gpui.workspace = true gpui_tokio = { workspace = true, optional = true } +http_client.workspace = true indoc.workspace = true language.workspace = true language_model.workspace = true diff --git a/crates/agent_servers/src/agent_servers.rs b/crates/agent_servers/src/agent_servers.rs index 2c2900cb79328249355704606652c54d08f072e5..b9751d7f63053bf073bcc8181f0cc2f8211d5c9f 100644 --- a/crates/agent_servers/src/agent_servers.rs +++ b/crates/agent_servers/src/agent_servers.rs @@ -7,15 +7,19 @@ mod gemini; pub mod e2e_tests; pub use claude::*; +use client::ProxySettings; +use collections::HashMap; pub use custom::*; use fs::Fs; pub use gemini::*; +use http_client::read_no_proxy_from_env; use project::agent_server_store::AgentServerStore; use acp_thread::AgentConnection; use anyhow::Result; -use gpui::{App, Entity, SharedString, Task}; +use gpui::{App, AppContext, Entity, SharedString, Task}; use project::Project; +use settings::SettingsStore; use std::{any::Any, path::Path, rc::Rc, sync::Arc}; pub use acp::AcpConnection; @@ -77,3 +81,25 @@ impl dyn AgentServer { self.into_any().downcast().ok() } } + +/// Load the default proxy environment variables to pass through to the agent +pub fn load_proxy_env(cx: &mut App) -> HashMap { + let proxy_url = cx + .read_global(|settings: &SettingsStore, _| settings.get::(None).proxy_url()); + let mut env = HashMap::default(); + + if let Some(proxy_url) = &proxy_url { + let env_var = if proxy_url.scheme() == "https" { + "HTTPS_PROXY" + } else { + "HTTP_PROXY" + }; + env.insert(env_var.to_owned(), proxy_url.to_string()); + } + + if let Some(no_proxy) = read_no_proxy_from_env() { + env.insert("NO_PROXY".to_owned(), no_proxy); + } + + env +} diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index c75c9539abe5fdd03293d98719d4a905b368c4a4..dafb7ea5b706af8e52562681d634dadf07ae488f 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -10,7 +10,7 @@ use anyhow::{Context as _, Result}; use gpui::{App, AppContext as _, SharedString, Task}; use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME}; -use crate::{AgentServer, AgentServerDelegate}; +use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; #[derive(Clone)] @@ -60,6 +60,7 @@ impl AgentServer for ClaudeCode { let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); + let extra_env = load_proxy_env(cx); let default_mode = self.default_mode(cx); cx.spawn(async move |cx| { @@ -70,7 +71,7 @@ impl AgentServer for ClaudeCode { .context("Claude Code is not registered")?; anyhow::Ok(agent.get_command( root_dir.as_deref(), - Default::default(), + extra_env, delegate.status_tx, delegate.new_version_available, &mut cx.to_async(), diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index f035952a7939201e4b7d990b97e1fc695105d505..7c8924a3dd65e9f963b418aa6872b1bc14886040 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -1,4 +1,4 @@ -use crate::AgentServerDelegate; +use crate::{AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; use agent_client_protocol as acp; use anyhow::{Context as _, Result}; @@ -65,6 +65,7 @@ impl crate::AgentServer for CustomAgentServer { let is_remote = delegate.project.read(cx).is_via_remote_server(); let default_mode = self.default_mode(cx); let store = delegate.store.downgrade(); + let extra_env = load_proxy_env(cx); cx.spawn(async move |cx| { let (command, root_dir, login) = store @@ -76,7 +77,7 @@ impl crate::AgentServer for CustomAgentServer { })?; anyhow::Ok(agent.get_command( root_dir.as_deref(), - Default::default(), + extra_env, delegate.status_tx, delegate.new_version_available, &mut cx.to_async(), diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index e897bbd747b2ba7cf3947ed5cd3e594408bc1c2a..9407a42e68d34e38e78f2103b29f980f874fb3db 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -1,15 +1,12 @@ use std::rc::Rc; use std::{any::Any, path::Path}; -use crate::{AgentServer, AgentServerDelegate}; +use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; use anyhow::{Context as _, Result}; -use client::ProxySettings; -use collections::HashMap; -use gpui::{App, AppContext, SharedString, Task}; +use gpui::{App, SharedString, Task}; use language_models::provider::google::GoogleLanguageModelProvider; use project::agent_server_store::GEMINI_NAME; -use settings::SettingsStore; #[derive(Clone)] pub struct Gemini; @@ -37,14 +34,12 @@ impl AgentServer for Gemini { let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); - let proxy_url = cx.read_global(|settings: &SettingsStore, _| { - settings.get::(None).proxy.clone() - }); + let mut extra_env = load_proxy_env(cx); let default_mode = self.default_mode(cx); cx.spawn(async move |cx| { - let mut extra_env = HashMap::default(); extra_env.insert("SURFACE".to_owned(), "zed".to_owned()); + if let Some(api_key) = cx .update(GoogleLanguageModelProvider::api_key_for_gemini_cli)? .await @@ -52,7 +47,7 @@ impl AgentServer for Gemini { { extra_env.insert("GEMINI_API_KEY".into(), api_key); } - let (mut command, root_dir, login) = store + let (command, root_dir, login) = store .update(cx, |store, cx| { let agent = store .get_external_agent(&GEMINI_NAME.into()) @@ -67,14 +62,6 @@ impl AgentServer for Gemini { })?? .await?; - // Add proxy flag if proxy settings are configured in Zed and not in the args - if let Some(proxy_url_value) = &proxy_url - && !command.args.iter().any(|arg| arg.contains("--proxy")) - { - command.args.push("--proxy".into()); - command.args.push(proxy_url_value.clone()); - } - let connection = crate::acp::connect( name, command, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index cb8185c7ed326ed7d45726a99077c53903118316..8888c73f4496d39b33cb868444f39234f7d074f7 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -22,7 +22,7 @@ use futures::{ channel::oneshot, future::BoxFuture, }; use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions}; -use http_client::{HttpClient, HttpClientWithUrl, http}; +use http_client::{HttpClient, HttpClientWithUrl, http, read_proxy_from_env}; use parking_lot::RwLock; use postage::watch; use proxy::connect_proxy_stream; @@ -132,6 +132,20 @@ pub struct ProxySettings { pub proxy: Option, } +impl ProxySettings { + pub fn proxy_url(&self) -> Option { + self.proxy + .as_ref() + .and_then(|input| { + input + .parse::() + .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e)) + .ok() + }) + .or_else(read_proxy_from_env) + } +} + impl Settings for ProxySettings { type FileContent = ProxySettingsContent; diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index 62468573ed29687c0436e98a0174baa515b0ee3d..1429b7bf941fab5b1b508b977e898b8e153942d1 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -318,6 +318,12 @@ pub fn read_proxy_from_env() -> Option { .and_then(|env| env.parse().ok()) } +pub fn read_no_proxy_from_env() -> Option { + const ENV_VARS: &[&str] = &["NO_PROXY", "no_proxy"]; + + ENV_VARS.iter().find_map(|var| std::env::var(var).ok()) +} + pub struct BlockedHttpClient; impl BlockedHttpClient { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 63ee30043b61fd55af2edad7cd4a7a407af743bd..882c29e7b0f25c0f076e142bb7006ea4450c565e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -19,7 +19,6 @@ use git::GitHostingProviderRegistry; use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, UpdateGlobal as _}; use gpui_tokio::Tokio; -use http_client::{Url, read_proxy_from_env}; use language::LanguageRegistry; use onboarding::{FIRST_OPEN, show_onboarding_view}; use prompt_store::PromptBuilder; @@ -398,16 +397,7 @@ pub fn main() { std::env::consts::OS, std::env::consts::ARCH ); - let proxy_str = ProxySettings::get_global(cx).proxy.to_owned(); - let proxy_url = proxy_str - .as_ref() - .and_then(|input| { - input - .parse::() - .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e)) - .ok() - }) - .or_else(read_proxy_from_env); + let proxy_url = ProxySettings::get_global(cx).proxy_url(); let http = { let _guard = Tokio::handle(cx).enter(); From c6472fd7a8cfe8dc71e2f10101ddc8b3151c082a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 16 Sep 2025 12:23:49 +0200 Subject: [PATCH 051/721] agent_settings: Fix schema validation rejecting custom llm providers (#38248) Closes https://github.com/zed-industries/zed/issues/37989 Release Notes: - N/A --- crates/agent_settings/src/agent_settings.rs | 37 +++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 153b64a3950718c3a8e8770a362b7c43a9bf443f..71516b568c2db65d8266d4233d6f7d2256379e1e 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -364,21 +364,30 @@ impl JsonSchema for LanguageModelProviderSetting { } fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + // list the builtin providers as a subset so that we still auto complete them in the settings json_schema!({ - "enum": [ - "amazon-bedrock", - "anthropic", - "copilot_chat", - "deepseek", - "google", - "lmstudio", - "mistral", - "ollama", - "openai", - "openrouter", - "vercel", - "x_ai", - "zed.dev" + "anyOf": [ + { + "type": "string", + "enum": [ + "amazon-bedrock", + "anthropic", + "copilot_chat", + "deepseek", + "google", + "lmstudio", + "mistral", + "ollama", + "openai", + "openrouter", + "vercel", + "x_ai", + "zed.dev" + ] + }, + { + "type": "string", + } ] }) } From a7cb64c64dc8a13a2164cf3cd2b0c0642f69d261 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 16 Sep 2025 14:11:06 +0200 Subject: [PATCH 052/721] Remove unused agent server settings module (#38250) This was no longer in the module graph (the settings moved elsewhere) so cleaning up the dead code. Release Notes: - N/A --- crates/agent_servers/src/settings.rs | 125 --------------------------- 1 file changed, 125 deletions(-) delete mode 100644 crates/agent_servers/src/settings.rs diff --git a/crates/agent_servers/src/settings.rs b/crates/agent_servers/src/settings.rs deleted file mode 100644 index 9a610465be5516664dafd9cd4cb46be96ad89c8b..0000000000000000000000000000000000000000 --- a/crates/agent_servers/src/settings.rs +++ /dev/null @@ -1,125 +0,0 @@ -use agent_client_protocol as acp; -use std::path::PathBuf; - -use crate::AgentServerCommand; -use anyhow::Result; -use collections::HashMap; -use gpui::{App, SharedString}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; - -pub fn init(cx: &mut App) { - AllAgentServersSettings::register(cx); -} - -#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "agent_servers")] -pub struct AllAgentServersSettings { - pub gemini: Option, - pub claude: Option, - - /// Custom agent servers configured by the user - #[serde(flatten)] - pub custom: HashMap, -} - -#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] -pub struct BuiltinAgentServerSettings { - /// Absolute path to a binary to be used when launching this agent. - /// - /// This can be used to run a specific binary without automatic downloads or searching `$PATH`. - #[serde(rename = "command")] - pub path: Option, - /// If a binary is specified in `command`, it will be passed these arguments. - pub args: Option>, - /// If a binary is specified in `command`, it will be passed these environment variables. - pub env: Option>, - /// Whether to skip searching `$PATH` for an agent server binary when - /// launching this agent. - /// - /// This has no effect if a `command` is specified. Otherwise, when this is - /// `false`, Zed will search `$PATH` for an agent server binary and, if one - /// is found, use it for threads with this agent. If no agent binary is - /// found on `$PATH`, Zed will automatically install and use its own binary. - /// When this is `true`, Zed will not search `$PATH`, and will always use - /// its own binary. - /// - /// Default: true - pub ignore_system_version: Option, - /// The default mode for new threads. - /// - /// Note: Not all agents support modes. - /// - /// Default: None - #[serde(skip_serializing_if = "Option::is_none")] - pub default_mode: Option, -} - -impl BuiltinAgentServerSettings { - pub(crate) fn custom_command(self) -> Option { - self.path.map(|path| AgentServerCommand { - path, - args: self.args.unwrap_or_default(), - env: self.env, - }) - } -} - -impl From for BuiltinAgentServerSettings { - fn from(value: AgentServerCommand) -> Self { - BuiltinAgentServerSettings { - path: Some(value.path), - args: Some(value.args), - env: value.env, - ..Default::default() - } - } -} - -#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] -pub struct CustomAgentServerSettings { - #[serde(flatten)] - pub command: AgentServerCommand, - /// The default mode for new threads. - /// - /// Note: Not all agents support modes. - /// - /// Default: None - #[serde(skip_serializing_if = "Option::is_none")] - pub default_mode: Option, -} - -impl settings::Settings for AllAgentServersSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - let mut settings = AllAgentServersSettings::default(); - - for AllAgentServersSettings { - gemini, - claude, - custom, - } in sources.defaults_and_customizations() - { - if gemini.is_some() { - settings.gemini = gemini.clone(); - } - if claude.is_some() { - settings.claude = claude.clone(); - } - - // Merge custom agents - for (name, config) in custom { - // Skip built-in agent names to avoid conflicts - if name != "gemini" && name != "claude" { - settings.custom.insert(name.clone(), config.clone()); - } - } - } - - Ok(settings) - } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} -} From 173074f2485232b94ab178000ec9656ceefbb3aa Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 16 Sep 2025 14:12:45 +0200 Subject: [PATCH 053/721] search: Re-issue project search if search query is stale on replacement (#38251) Closes https://github.com/zed-industries/zed/issues/34897 Release Notes: - Fixed project search replacement replacing stale search results --- crates/project/src/search.rs | 1 - crates/search/src/project_search.rs | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index f2c6091e0cb00b8da1a752e3d25afe3389e8c818..953fa4f1aafdca87ccd1e8dbcfec145505660642 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -64,7 +64,6 @@ pub enum SearchQuery { include_ignored: bool, inner: SearchInputs, }, - Regex { regex: Regex, replacement: Option, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 3d9248e3c7e64893cc6a72d0e034d7a7597edf29..eaad5dad65b75c1fffcabb400eb6a2dea1fb1811 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -679,7 +679,18 @@ impl ProjectSearchView { self.included_opened_only = !self.included_opened_only; } + pub fn replacement(&self, cx: &App) -> String { + self.replacement_editor.read(cx).text(cx) + } + fn replace_next(&mut self, _: &ReplaceNext, window: &mut Window, cx: &mut Context) { + if let Some(last_search_query_text) = &self.entity.read(cx).last_search_query_text + && self.query_editor.read(cx).text(cx) != *last_search_query_text + { + // search query has changed, restart search and bail + self.search(cx); + return; + } if self.entity.read(cx).match_ranges.is_empty() { return; } @@ -699,14 +710,17 @@ impl ProjectSearchView { self.select_match(Direction::Next, window, cx) } } - pub fn replacement(&self, cx: &App) -> String { - self.replacement_editor.read(cx).text(cx) - } fn replace_all(&mut self, _: &ReplaceAll, window: &mut Window, cx: &mut Context) { + if let Some(last_search_query_text) = &self.entity.read(cx).last_search_query_text + && self.query_editor.read(cx).text(cx) != *last_search_query_text + { + // search query has changed, restart search and bail + self.search(cx); + return; + } if self.active_match_index.is_none() { return; } - let Some(query) = self.entity.read(cx).active_query.as_ref() else { return; }; From af3bc45a2657a6a924f09d4ffc62c209b2d08600 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Sep 2025 08:06:16 -0600 Subject: [PATCH 054/721] Drop ellipses from About Zed menu item (#38211) Follow the macOS app style guideline. Release Notes: - N/A --- crates/zed/src/zed/app_menus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index dea36e3ea2b2dd4319d9cb5bb156d5b0956c7535..cc4398d0a71fc9f74282a08b7d2b470c4a268da7 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -10,7 +10,7 @@ pub fn app_menus() -> Vec { Menu { name: "Zed".into(), items: vec![ - MenuItem::action("About Zed…", zed_actions::About), + MenuItem::action("About Zed", zed_actions::About), MenuItem::action("Check for Updates", auto_update::Check), MenuItem::separator(), MenuItem::submenu(Menu { From a01a2ed0e0844ded77bb386616d608220e9c33db Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Tue, 16 Sep 2025 20:06:14 +0530 Subject: [PATCH 055/721] languages: Add Tailwind CSS support for TypeScript (#38254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #37028 I noticed many projects use Tailwind in plain TypeScript (.ts) files, so it makes sense to support them out of the box, alongside .js and .tsx files we already handle. For example, see [supabase](https://github.com/supabase/supabase/blob/master/packages/ui/src/lib/theme/defaultTheme.ts). Note: You’ll still need to add `"classFunctions": ["cva", "cx"],` manually for Tailwind completions to work in `cva` type methods. This is because you don’t want completions on every string, only in specific methods or regex matches. This is documented. Release Notes: - Added out-of-the-box support for Tailwind completions in `.ts` files. --- crates/languages/src/lib.rs | 1 + crates/languages/src/tailwind.rs | 2 ++ crates/languages/src/typescript/config.toml | 4 +++- docs/src/languages/tailwindcss.md | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index f71234a7d5c5219b679acdd12610da4fe6e857ad..93a072b2ac5a9f4a6d21729eaad9a852fe95b929 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -286,6 +286,7 @@ pub fn init(languages: Arc, fs: Arc, node: NodeRuntime "HEEX", "HTML", "JavaScript", + "TypeScript", "PHP", "Svelte", "TSX", diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index db539edabb6c10253f2fd0d688eafa46082d427a..e1b50a5ccaabb7770d13abc79fbac1da5fa4cbbe 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -146,6 +146,7 @@ impl LspAdapter for TailwindLspAdapter { "html": "html", "css": "css", "javascript": "javascript", + "typescript": "typescript", "typescriptreact": "typescriptreact", }, }))) @@ -178,6 +179,7 @@ impl LspAdapter for TailwindLspAdapter { (LanguageName::new("HTML"), "html".to_string()), (LanguageName::new("CSS"), "css".to_string()), (LanguageName::new("JavaScript"), "javascript".to_string()), + (LanguageName::new("TypeScript"), "typescript".to_string()), (LanguageName::new("TSX"), "typescriptreact".to_string()), (LanguageName::new("Svelte"), "svelte".to_string()), (LanguageName::new("Elixir"), "phoenix-heex".to_string()), diff --git a/crates/languages/src/typescript/config.toml b/crates/languages/src/typescript/config.toml index 2344f6209da7756049438669ee55d5376fdb47f8..fe56e496ec717895e72f37dda9146fbb30b50e88 100644 --- a/crates/languages/src/typescript/config.toml +++ b/crates/languages/src/typescript/config.toml @@ -21,9 +21,11 @@ word_characters = ["#", "$"] prettier_parser_name = "typescript" tab_size = 2 debuggers = ["JavaScript"] +scope_opt_in_language_servers = ["tailwindcss-language-server"] [overrides.string] -completion_query_characters = ["."] +completion_query_characters = ["-", "."] +opt_into_language_servers = ["tailwindcss-language-server"] prefer_label_for_snippet = true [overrides.function_name_before_type_arguments] diff --git a/docs/src/languages/tailwindcss.md b/docs/src/languages/tailwindcss.md index 83b01774020c1332881b359af4014934340f837a..4409a12bf0dde643f60bb46ae2887c3aa48ca002 100644 --- a/docs/src/languages/tailwindcss.md +++ b/docs/src/languages/tailwindcss.md @@ -13,6 +13,7 @@ To configure the Tailwind CSS language server, refer [to the extension settings] "lsp": { "tailwindcss-language-server": { "settings": { + "classFunctions": ["cva", "cx"], "experimental": { "classRegex": ["[cls|className]\\s\\:\\=\\s\"([^\"]*)"], }, From e885a939baa45f8822e02a1e5b94cc4cf83fe8c3 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Tue, 16 Sep 2025 20:06:32 +0530 Subject: [PATCH 056/721] git_ui: Add tooltip for branch picker items (#38261) Closes #38256 image Release Notes: - Added tooltip to Git branch picker items, making it easier to distinguish long branch names. --- crates/git_ui/src/branch_picker.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 42558b7b79bca1892b9d36ae4b39bb3cdb196d4f..9a0751c8eded6bbb2bb61e62723b03ef8a0a2149 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -521,6 +521,14 @@ impl PickerDelegate for BranchListDelegate { .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) + .tooltip({ + let branch_name = entry.branch.name().to_string(); + if entry.is_new { + Tooltip::text(format!("Create branch \"{}\"", branch_name)) + } else { + Tooltip::text(branch_name) + } + }) .child( v_flex() .w_full() From 53513cab236efd2232abd7b897298dcf890cd4b5 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 16 Sep 2025 22:47:10 +0800 Subject: [PATCH 057/721] Fix filled button hover background (#38235) Release Notes: - Fixed filled button hover background. ## Before https://github.com/user-attachments/assets/fbc75890-d1a4-4a0c-b54e-ca2c7e63a661 ## After https://github.com/user-attachments/assets/a3595b01-e143-4cd0-8bc4-90db9ccfbf74 This appears to be a minor calculation error, not an intentional use of this value. If we pass `0.92` to `fade_out`, the calculated will be `alpha: 0.08`. --------- Co-authored-by: Danilo Leal --- crates/ui/src/components/button/button_like.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 477fc57b22f9178edc2123a76fcaf68701f8fb4d..d38b919bffe3df2e918266d7d76dbb1e4f02bf97 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -217,7 +217,7 @@ impl ButtonStyle { match self { ButtonStyle::Filled => { let mut filled_background = element_bg_from_elevation(elevation, cx); - filled_background.fade_out(0.92); + filled_background.fade_out(0.5); ButtonLikeStyles { background: filled_background, From 5674445a61d6bbc891f44c8d75c29593c92151a5 Mon Sep 17 00:00:00 2001 From: VBB <70429241+RocketRide9@users.noreply.github.com> Date: Tue, 16 Sep 2025 23:33:55 +0700 Subject: [PATCH 058/721] Move keyboard shortcut for `pane::GoForward` (#38221) Move keyboard shortcut for `pane:GoForward` so it's going to be displayed as a shortcut hint in UI. Currently `Forward` is shown as a hint, which isn't consistent with `GoBack` action and can be confusing. Release Notes: - Improved the displayed keybinding for the `pane::GoForward` action on Linux. --- assets/keymaps/default-linux.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 6b4c4e0fac95cf751c21cfaa0770d1279a35adcc..8ca0a5d42094db8b4b37c7e6919da0f7a6bd41db 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -462,8 +462,8 @@ "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", "back": "pane::GoBack", "ctrl-alt--": "pane::GoBack", - "ctrl-alt-_": "pane::GoForward", "forward": "pane::GoForward", + "ctrl-alt-_": "pane::GoForward", "ctrl-alt-g": "search::SelectNextMatch", "f3": "search::SelectNextMatch", "ctrl-alt-shift-g": "search::SelectPreviousMatch", From 673a98a27728180c2bd0c5c0c4b8d5092cfc907c Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Tue, 16 Sep 2025 21:18:39 +0200 Subject: [PATCH 059/721] Fix a number of spelling mistakes (#38281) My pre push hooks keep failing on these. This is easier then disabling and re-enabling those hooks all the time :) Closes #ISSUE Release Notes: - N/A --- crates/assistant_tools/src/edit_agent/create_file_parser.rs | 4 ++-- .../fixtures/use_wasi_sdk_in_compile_parser_to_wasm/before.rs | 2 +- crates/editor/src/element.rs | 2 +- crates/inspector_ui/README.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/assistant_tools/src/edit_agent/create_file_parser.rs b/crates/assistant_tools/src/edit_agent/create_file_parser.rs index 5126f9c6b1fe4ee5cc600ae93b7300b7af09451f..2272434d796a92e53b741f8ed5f4303d94f88489 100644 --- a/crates/assistant_tools/src/edit_agent/create_file_parser.rs +++ b/crates/assistant_tools/src/edit_agent/create_file_parser.rs @@ -160,7 +160,7 @@ mod tests { &mut parser, &mut rng ), - // This output is marlformed, so we're doing our best effort + // This output is malformed, so we're doing our best effort "Hello world\n```\n\nThe end\n".to_string() ); } @@ -182,7 +182,7 @@ mod tests { &mut parser, &mut rng ), - // This output is marlformed, so we're doing our best effort + // This output is malformed, so we're doing our best effort "```\nHello world\n```\n".to_string() ); } diff --git a/crates/assistant_tools/src/edit_agent/evals/fixtures/use_wasi_sdk_in_compile_parser_to_wasm/before.rs b/crates/assistant_tools/src/edit_agent/evals/fixtures/use_wasi_sdk_in_compile_parser_to_wasm/before.rs index b51c74c798d88b3f84303ffe41f4ac2590e7f236..cfa28fe1ad6091c9adda22f610e1cf13166f8dfb 100644 --- a/crates/assistant_tools/src/edit_agent/evals/fixtures/use_wasi_sdk_in_compile_parser_to_wasm/before.rs +++ b/crates/assistant_tools/src/edit_agent/evals/fixtures/use_wasi_sdk_in_compile_parser_to_wasm/before.rs @@ -916,7 +916,7 @@ impl Loader { if !found_non_static { found_non_static = true; eprintln!( - "Warning: Found non-static non-tree-sitter functions in the external scannner" + "Warning: Found non-static non-tree-sitter functions in the external scanner" ); } eprintln!(" `{function_name}`"); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 603dcdece22053d131441929fe4270371e803463..230c3160fb616984f71ce12b7b3b6b8d459d6356 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9694,7 +9694,7 @@ impl EditorScrollbars { editor_bounds.bottom_left(), size( // The horizontal viewport size differs from the space available for the - // horizontal scrollbar, so we have to manually stich it together here. + // horizontal scrollbar, so we have to manually stitch it together here. editor_bounds.size.width - right_margin, scrollbar_width, ), diff --git a/crates/inspector_ui/README.md b/crates/inspector_ui/README.md index 5c720dfea2df3ff2ddf75112fec8793ba1851ed1..74886e611108fd4fd3f5f5015746f913e2697cae 100644 --- a/crates/inspector_ui/README.md +++ b/crates/inspector_ui/README.md @@ -68,7 +68,7 @@ With both approaches, would need to record the buffer version and use that when * Mode to navigate to source code on every element change while picking. -* Tracking of more source locations - currently the source location is often in a ui compoenent. Ideally this would have a way for the components to indicate that they are probably not the source location the user is looking for. +* Tracking of more source locations - currently the source location is often in a ui component. Ideally this would have a way for the components to indicate that they are probably not the source location the user is looking for. - Could have `InspectorElementId` be `Vec<(ElementId, Option)>`, but if there are multiple code paths that construct the same element this would cause them to be considered different. From ee912366a39e9451b36fb5f9c8810da57b173af4 Mon Sep 17 00:00:00 2001 From: George Waters Date: Tue, 16 Sep 2025 12:30:32 -0700 Subject: [PATCH 060/721] Check if virtual environment is in worktree root (#37510) The problem from issue #37509 comes from local virtual environments created with certain approaches (including the 'simple' way of `python -m venv`) not having a `.project` file with the path to the project's root directory. When the toolchains are sorted, a virtual environment in the project is not treated as being for that project and therefore is not prioritized. With this change, if a toolchain does not have a `project` associated with it, we check to see if it is a virtual environment, and if it is we use its parent directory as the `project`. This will make it the top priority (i.e. the default) if there are no other virtual environments for a project, which is what should be expected. Closes #37509 Release Notes: - Improved python toolchain prioritization of local virtual environments. --- Cargo.lock | 1 + Cargo.toml | 1 + crates/languages/Cargo.toml | 1 + crates/languages/src/python.rs | 50 ++++++++++++++++++++++++++++++---- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index deb0dba0dac2f766d243a21801902bee66d156b1..64f0a4b41f04a11b5813ba8e371bc1587d12a0ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9321,6 +9321,7 @@ dependencies = [ "pet-fs", "pet-poetry", "pet-reporter", + "pet-virtualenv", "pretty_assertions", "project", "regex", diff --git a/Cargo.toml b/Cargo.toml index 5cad901d18d5832f0d593c6c436613e507cdabe1..d6f74e2685c3c350f3d4a415148dce4c96dfd49f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -584,6 +584,7 @@ pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", re pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } +pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } portable-pty = "0.9.0" postage = { version = "0.5", features = ["futures-traits"] } pretty_assertions = { version = "1.3.0", features = ["unstable"] } diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 7ebafd8fdd9e2310c207d47a7d911516a498d00c..f08c548ddcb10a311e1d5b29a9bf50a7b5bc4fb1 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -57,6 +57,7 @@ pet-core.workspace = true pet-fs.workspace = true pet-poetry.workspace = true pet-reporter.workspace = true +pet-virtualenv.workspace = true pet.workspace = true project.workspace = true regex.workspace = true diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 27af3eea17c9858d11e01cd7187975e804736ba2..d989f4d6dc22d2e58752d6d635b62091d8bd63d6 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -16,6 +16,7 @@ use node_runtime::{NodeRuntime, VersionStrategy}; use pet_core::Configuration; use pet_core::os_environment::Environment; use pet_core::python_environment::{PythonEnvironment, PythonEnvironmentKind}; +use pet_virtualenv::is_virtualenv_dir; use project::Fs; use project::lsp_store::language_server_settings; use serde_json::{Value, json}; @@ -900,6 +901,21 @@ fn python_module_name_from_relative_path(relative_path: &str) -> String { .to_string() } +fn is_python_env_global(k: &PythonEnvironmentKind) -> bool { + matches!( + k, + PythonEnvironmentKind::Homebrew + | PythonEnvironmentKind::Pyenv + | PythonEnvironmentKind::GlobalPaths + | PythonEnvironmentKind::MacPythonOrg + | PythonEnvironmentKind::MacCommandLineTools + | PythonEnvironmentKind::LinuxGlobal + | PythonEnvironmentKind::MacXCode + | PythonEnvironmentKind::WindowsStore + | PythonEnvironmentKind::WindowsRegistry + ) +} + fn python_env_kind_display(k: &PythonEnvironmentKind) -> &'static str { match k { PythonEnvironmentKind::Conda => "Conda", @@ -966,6 +982,26 @@ async fn get_worktree_venv_declaration(worktree_root: &Path) -> Option { Some(venv_name.trim().to_string()) } +fn get_venv_parent_dir(env: &PythonEnvironment) -> Option { + // If global, we aren't a virtual environment + if let Some(kind) = env.kind + && is_python_env_global(&kind) + { + return None; + } + + // Check to be sure we are a virtual environment using pet's most generic + // virtual environment type, VirtualEnv + let venv = env + .executable + .as_ref() + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) + .filter(|p| is_virtualenv_dir(p))?; + + venv.parent().map(|parent| parent.to_path_buf()) +} + #[async_trait] impl ToolchainLister for PythonToolchainProvider { async fn list( @@ -1025,11 +1061,15 @@ impl ToolchainLister for PythonToolchainProvider { }); // Compare project paths against worktree root - let proj_ordering = || match (&lhs.project, &rhs.project) { - (Some(l), Some(r)) => (r == &wr).cmp(&(l == &wr)), - (Some(l), None) if l == &wr => Ordering::Less, - (None, Some(r)) if r == &wr => Ordering::Greater, - _ => Ordering::Equal, + let proj_ordering = || { + let lhs_project = lhs.project.clone().or_else(|| get_venv_parent_dir(lhs)); + let rhs_project = rhs.project.clone().or_else(|| get_venv_parent_dir(rhs)); + match (&lhs_project, &rhs_project) { + (Some(l), Some(r)) => (r == &wr).cmp(&(l == &wr)), + (Some(l), None) if l == &wr => Ordering::Less, + (None, Some(r)) if r == &wr => Ordering::Greater, + _ => Ordering::Equal, + } }; // Compare environment priorities From 26202e5af2341655e0d3fc6b2e62ec1faa22ea47 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Sep 2025 17:45:25 -0400 Subject: [PATCH 061/721] language_models: Use `message` field from Cloud error responses, if present (#38286) This PR updates the Cloud language model provider to use the `message` field from the Cloud error response, if it is present. Previously we would always show the entire JSON payload in the error message, but with this change we can show just the user-facing `message` the error response is in a shape that we recognize. Release Notes: - N/A --- crates/language_models/src/provider/cloud.rs | 51 +++++++++++--------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 421e34e658fb604e21fdfc4af52b3c4c7874fd70..6393117185e9104bec77f14fe334e94bb18679db 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -541,29 +541,36 @@ where impl From for LanguageModelCompletionError { fn from(error: ApiError) -> Self { - if let Ok(cloud_error) = serde_json::from_str::(&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::().ok()) - .and_then(|code| StatusCode::from_u16(code).ok()) - .unwrap_or(error.status) - }; + if let Ok(cloud_error) = serde_json::from_str::(&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::().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), + }; + } + + return LanguageModelCompletionError::from_http_status( + PROVIDER_NAME, + error.status, + cloud_error.message, + None, + ); } let retry_after = None; From 0343b5ff064ab975a2366e7c9bbe636772e6a019 Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Tue, 16 Sep 2025 23:49:26 +0200 Subject: [PATCH 062/721] Add new crate denoise required by audio (#38217) The audio crate will use the denoise crate to remove background noises from microphone input. We intent to contribute this to rodio. Before that can happen a PR needs to land in candle. Until then this lives here. Uses a candle fork which removes the dependency on `protoc` and has the PR's mentioned above already applied. Release Notes: - N/A --------- Co-authored-by: Mikayla --- Cargo.lock | 577 +++++++++++++++++- Cargo.toml | 1 + crates/denoise/Cargo.toml | 21 + crates/denoise/LICENSE-GPL | 1 + crates/denoise/README.md | 20 + crates/denoise/examples/denoise.rs | 11 + crates/denoise/examples/enable_disable.rs | 23 + .../models/model_1_converted_simplified.onnx | Bin 0 -> 1466621 bytes .../models/model_2_converted_simplified.onnx | Bin 0 -> 2525727 bytes crates/denoise/src/engine.rs | 204 +++++++ crates/denoise/src/lib.rs | 270 ++++++++ tooling/workspace-hack/Cargo.toml | 66 +- 12 files changed, 1156 insertions(+), 38 deletions(-) create mode 100644 crates/denoise/Cargo.toml create mode 120000 crates/denoise/LICENSE-GPL create mode 100644 crates/denoise/README.md create mode 100644 crates/denoise/examples/denoise.rs create mode 100644 crates/denoise/examples/enable_disable.rs create mode 100644 crates/denoise/models/model_1_converted_simplified.onnx create mode 100644 crates/denoise/models/model_2_converted_simplified.onnx create mode 100644 crates/denoise/src/engine.rs create mode 100644 crates/denoise/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 64f0a4b41f04a11b5813ba8e371bc1587d12a0ce..4709dd34f4ab090d06e49827fc3b3b04eece6659 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -688,6 +688,9 @@ name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arc-swap" @@ -2187,7 +2190,7 @@ dependencies = [ "bitflags 2.9.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.11.0", "lazy_static", "lazycell", "log", @@ -2685,6 +2688,53 @@ dependencies = [ "serde", ] +[[package]] +name = "candle-core" +version = "0.9.1" +source = "git+https://github.com/zed-industries/candle?branch=9.1-patched#724d75eb3deebefe83f2a7381a45d4fac6eda383" +dependencies = [ + "byteorder", + "float8", + "gemm 0.17.1", + "half", + "memmap2", + "num-traits", + "num_cpus", + "rand 0.9.1", + "rand_distr", + "rayon", + "safetensors", + "thiserror 1.0.69", + "ug", + "yoke", + "zip 1.1.4", +] + +[[package]] +name = "candle-nn" +version = "0.9.1" +source = "git+https://github.com/zed-industries/candle?branch=9.1-patched#724d75eb3deebefe83f2a7381a45d4fac6eda383" +dependencies = [ + "candle-core", + "half", + "libc", + "num-traits", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "candle-onnx" +version = "0.9.1" +source = "git+https://github.com/zed-industries/candle?branch=9.1-patched#724d75eb3deebefe83f2a7381a45d4fac6eda383" +dependencies = [ + "candle-core", + "candle-nn", + "prost 0.12.6", +] + [[package]] name = "cap-fs-ext" version = "3.4.4" @@ -4635,6 +4685,20 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +[[package]] +name = "denoise" +version = "0.1.0" +dependencies = [ + "candle-core", + "candle-onnx", + "log", + "realfft", + "rodio", + "rustfft", + "thiserror 2.0.12", + "workspace-hack", +] + [[package]] name = "der" version = "0.6.1" @@ -4666,6 +4730,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "derive_more" version = "0.99.19" @@ -4821,7 +4896,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.0", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4979,6 +5054,25 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +[[package]] +name = "dyn-stack" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e53799688f5632f364f8fb387488dd05db9fe45db7011be066fc20e7027f8b" +dependencies = [ + "bytemuck", + "reborrow", +] + +[[package]] +name = "dyn-stack" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490bd48eb68fffcfed519b4edbfd82c69cbe741d175b84f0e0cbe8c57cbe0bdd" +dependencies = [ + "bytemuck", +] + [[package]] name = "ec4rs" version = "1.2.0" @@ -5239,6 +5333,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "enumflags2" version = "0.7.11" @@ -5869,6 +5975,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" +[[package]] +name = "float8" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4203231de188ebbdfb85c11f3c20ca2b063945710de04e7b59268731e728b462" +dependencies = [ + "half", + "num-traits", + "rand 0.9.1", + "rand_distr", +] + [[package]] name = "float_next_after" version = "1.0.0" @@ -6323,6 +6441,243 @@ dependencies = [ "thread_local", ] +[[package]] +name = "gemm" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-c32 0.17.1", + "gemm-c64 0.17.1", + "gemm-common 0.17.1", + "gemm-f16 0.17.1", + "gemm-f32 0.17.1", + "gemm-f64 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-c32 0.18.2", + "gemm-c64 0.18.2", + "gemm-common 0.18.2", + "gemm-f16 0.18.2", + "gemm-f32 0.18.2", + "gemm-f64 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-common" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" +dependencies = [ + "bytemuck", + "dyn-stack 0.10.0", + "half", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.18.22", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", + "sysctl 0.5.5", +] + +[[package]] +name = "gemm-common" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3" +dependencies = [ + "bytemuck", + "dyn-stack 0.13.0", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.21.5", + "raw-cpuid 11.6.0", + "rayon", + "seq-macro", + "sysctl 0.6.0", +] + +[[package]] +name = "gemm-f16" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "gemm-f32 0.17.1", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f16" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "gemm-f32 0.18.2", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + [[package]] name = "generator" version = "0.8.5" @@ -7597,9 +7952,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ + "bytemuck", "cfg-if", "crunchy", "num-traits", + "rand 0.9.1", + "rand_distr", ] [[package]] @@ -10191,6 +10549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", + "stable_deref_trait", ] [[package]] @@ -10457,12 +10816,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - [[package]] name = "naga" version = "25.0.1" @@ -10836,6 +11189,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -12529,6 +12883,15 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -12837,7 +13200,7 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap 0.8.3", + "multimap", "petgraph", "prost 0.9.0", "prost-types 0.9.0", @@ -12854,9 +13217,9 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes 1.10.1", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.11.0", "log", - "multimap 0.10.0", + "multimap", "once_cell", "petgraph", "prettyplease", @@ -12887,7 +13250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.101", @@ -13028,6 +13391,32 @@ dependencies = [ "wasmtime-math", ] +[[package]] +name = "pulp" +version = "0.18.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6" +dependencies = [ + "bytemuck", + "libm", + "num-complex", + "reborrow", +] + +[[package]] +name = "pulp" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "reborrow", + "version_check", +] + [[package]] name = "qoi" version = "0.4.1" @@ -13204,6 +13593,16 @@ dependencies = [ "getrandom 0.3.2", ] +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.1", +] + [[package]] name = "range-map" version = "0.2.0" @@ -13269,6 +13668,24 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -13317,6 +13734,21 @@ dependencies = [ "font-types", ] +[[package]] +name = "realfft" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +dependencies = [ + "rustfft", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "recent_projects" version = "0.1.0" @@ -14133,6 +14565,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfft" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f140db74548f7c9d7cce60912c9ac414e74df5e718dc947d514b051b42f3f4" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + [[package]] name = "rustix" version = "0.38.44" @@ -14357,6 +14803,16 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safetensors" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "salsa20" version = "0.10.2" @@ -14738,6 +15194,12 @@ dependencies = [ "serde", ] +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" version = "1.0.221" @@ -15692,6 +16154,12 @@ dependencies = [ "workspace-hack", ] +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strict-num" version = "0.1.1" @@ -16182,6 +16650,34 @@ dependencies = [ "libc", ] +[[package]] +name = "sysctl" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" +dependencies = [ + "bitflags 2.9.0", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.9.0", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + [[package]] name = "sysinfo" version = "0.31.4" @@ -17276,6 +17772,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "tree-sitter" version = "0.25.6" @@ -17637,6 +18143,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "ug" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b70b37e9074642bc5f60bb23247fd072a84314ca9e71cdf8527593406a0dd3" +dependencies = [ + "gemm 0.18.2", + "half", + "libloading", + "memmap2", + "num", + "num-traits", + "num_cpus", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", + "tracing", + "yoke", +] + [[package]] name = "ui" version = "0.1.0" @@ -18905,7 +19432,7 @@ dependencies = [ "reqwest 0.11.27", "scratch", "semver", - "zip", + "zip 0.6.6", ] [[package]] @@ -20050,7 +20577,7 @@ dependencies = [ "idna", "indexmap", "inout", - "itertools 0.12.1", + "itertools 0.11.0", "itertools 0.13.0", "jiff", "lazy_static", @@ -20064,6 +20591,7 @@ dependencies = [ "lyon_path", "md-5", "memchr", + "memmap2", "mime_guess", "miniz_oxide", "mio 1.0.3", @@ -20072,8 +20600,10 @@ dependencies = [ "nix 0.29.0", "nix 0.30.1", "nom 7.1.3", + "num", "num-bigint", "num-bigint-dig", + "num-complex", "num-integer", "num-iter", "num-rational", @@ -20089,6 +20619,7 @@ dependencies = [ "phf_shared", "prettyplease", "proc-macro2", + "prost 0.12.6", "prost 0.9.0", "prost-types 0.9.0", "quote", @@ -20096,6 +20627,7 @@ dependencies = [ "rand 0.9.1", "rand_chacha 0.3.1", "rand_core 0.6.4", + "rand_distr", "regalloc2", "regex", "regex-automata", @@ -20125,6 +20657,7 @@ dependencies = [ "sqlx-macros-core", "sqlx-postgres", "sqlx-sqlite", + "stable_deref_trait", "strum 0.26.3", "subtle", "syn 1.0.109", @@ -20158,6 +20691,7 @@ dependencies = [ "windows-sys 0.48.0", "windows-sys 0.52.0", "windows-sys 0.59.0", + "windows-sys 0.60.2", "winnow", "zeroize", "zvariant", @@ -21008,6 +21542,21 @@ dependencies = [ "zstd", ] +[[package]] +name = "zip" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "indexmap", + "num_enum", + "thiserror 1.0.69", +] + [[package]] name = "zlib-rs" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index d6f74e2685c3c350f3d4a415148dce4c96dfd49f..d3bbb85f3dbb90e52fc9b95573f6367a303c6479 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "crates/debugger_tools", "crates/debugger_ui", "crates/deepseek", + "crates/denoise", "crates/diagnostics", "crates/docs_preprocessor", "crates/edit_prediction", diff --git a/crates/denoise/Cargo.toml b/crates/denoise/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a2f43cdfee72a64fbd7e6e60b9414c691c3adfcd --- /dev/null +++ b/crates/denoise/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "denoise" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[dependencies] +candle-core = { version = "0.9.1", git ="https://github.com/zed-industries/candle", branch = "9.1-patched" } +candle-onnx = { version = "0.9.1", git ="https://github.com/zed-industries/candle", branch = "9.1-patched" } +log.workspace = true + +rodio = { workspace = true, features = ["wav_output"] } + +rustfft = { version = "6.2.0", features = ["avx"] } +realfft = "3.4.0" +thiserror.workspace = true +workspace-hack.workspace = true diff --git a/crates/denoise/LICENSE-GPL b/crates/denoise/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..e0f9dbd5d63fef1630c297edc4ceba4790be6f02 --- /dev/null +++ b/crates/denoise/LICENSE-GPL @@ -0,0 +1 @@ +LICENSE-GPL \ No newline at end of file diff --git a/crates/denoise/README.md b/crates/denoise/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d7486da36e9078f2f99c2a6a8226dbce499cae8b --- /dev/null +++ b/crates/denoise/README.md @@ -0,0 +1,20 @@ +Real time streaming audio denoising using a [Dual-Signal Transformation LSTM Network for Real-Time Noise Suppression](https://arxiv.org/abs/2005.07551). + +Trivial to build as it uses the native rust Candle crate for inference. Easy to integrate into any Rodio pipeline. + +```rust + # use rodio::{nz, source::UniformSourceIterator, wav_to_file}; + let file = std::fs::File::open("clips_airconditioning.wav")?; + let decoder = rodio::Decoder::try_from(file)?; + let resampled = UniformSourceIterator::new(decoder, nz!(1), nz!(16_000)); + + let mut denoised = denoise::Denoiser::try_new(resampled)?; + wav_to_file(&mut denoised, "denoised.wav")?; + Result::Ok<(), Box> +``` + +## Acknowledgements & License + +The trained models in this repo are optimized versions of the models in the [breizhn/DTLN](https://github.com/breizhn/DTLN?tab=readme-ov-file#model-conversion-and-real-time-processing-with-onnx). These are licensed under MIT. + +The FFT code was adapted from Datadog's [dtln-rs Repo](https://github.com/DataDog/dtln-rs/tree/main) also licensed under MIT. diff --git a/crates/denoise/examples/denoise.rs b/crates/denoise/examples/denoise.rs new file mode 100644 index 0000000000000000000000000000000000000000..a4d89d7e517e7b35d0f87adbd218cd34b75a4789 --- /dev/null +++ b/crates/denoise/examples/denoise.rs @@ -0,0 +1,11 @@ +use rodio::{nz, source::UniformSourceIterator, wav_to_file}; + +fn main() -> Result<(), Box> { + let file = std::fs::File::open("airconditioning.wav")?; + let decoder = rodio::Decoder::try_from(file)?; + let resampled = UniformSourceIterator::new(decoder, nz!(1), nz!(16_000)); + + let mut denoised = denoise::Denoiser::try_new(resampled)?; + wav_to_file(&mut denoised, "denoised.wav")?; + Ok(()) +} diff --git a/crates/denoise/examples/enable_disable.rs b/crates/denoise/examples/enable_disable.rs new file mode 100644 index 0000000000000000000000000000000000000000..1cffadbce2b0e58cdf56b291cb68a13fc6556b22 --- /dev/null +++ b/crates/denoise/examples/enable_disable.rs @@ -0,0 +1,23 @@ +use std::time::Duration; + +use rodio::Source; +use rodio::wav_to_file; +use rodio::{nz, source::UniformSourceIterator}; + +fn main() -> Result<(), Box> { + let file = std::fs::File::open("clips_airconditioning.wav")?; + let decoder = rodio::Decoder::try_from(file)?; + let resampled = UniformSourceIterator::new(decoder, nz!(1), nz!(16_000)); + + let mut enabled = true; + let denoised = denoise::Denoiser::try_new(resampled)?.periodic_access( + Duration::from_secs(2), + |denoised| { + enabled = !enabled; + denoised.set_enabled(enabled); + }, + ); + + wav_to_file(denoised, "processed.wav")?; + Ok(()) +} diff --git a/crates/denoise/models/model_1_converted_simplified.onnx b/crates/denoise/models/model_1_converted_simplified.onnx new file mode 100644 index 0000000000000000000000000000000000000000..821cb73bd76b1470c0ee814d07bd03c47a613643 GIT binary patch literal 1466621 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

vwGqY5GX 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

jJxt-_ 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

Tx1@3~z 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 }xi<(T$)zGB|T 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 vAM`Gck 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

j5q4*pYqU1pE67E+0X0xMQyqPrT_|$EEl%uE!5>�+K}s*}&!9OyYV7 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 Ao%!ZlRl58443ulc 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≦#t0QQYiVF5c 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 z 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%0D@G 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%0Db_8RRJZRw;tk=KEm0+>N}P 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%0Ar*Zwdro;CnyXDD(mEgyf<4 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%0A>db%_wmMU+(LG`)Wpk8Rf9m|=^Hm_bqO=HH3 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�#NB+MB) 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%0DXJ9kCZ{-iKhWtu-ZEWC^dINm{JuNVW=X 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 z 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:{Uo5^tK;$~7-T-sdx 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 }BslNu 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
    %^gJEVRBgL9Z#TU}gD|-aMH>{@fcTp`oW4 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

    ;FpQSG@?B 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ð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%0AK@_d0Dm@-#-sCFgO->oM3H-_GU%Z!L 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

    Fa9Th$SF=nOku~LuM+sx(ro|!UJ&F$Gt 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[Z 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�x@l*FdNG;$BNg@ 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

    ?SpFKv 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

    }RS77hO4LGn~7k|81gm<>? zqo=S4jT4LTllD4%rf~wlj$464m!u=FjlgAJ_u`|Y({RA-4fyNIEsUMM3I7?Xqe|*n z>^OcG4&0ssotC=@$48pu;_>?Mb;1%cak3k%2%cD0{L`IQy)@;tUv(n&t`R299?I5{ z_ME)yC>Ok~koc;4+;#M5Hn}*1_5ZVH!^~a0@xce`5j~r)xwupBF)wIxqZ@BH5kp)4 zO=jru@NO5yhf)DGnL8M!u}8WJU+r*{HQRl-`C}c; zJzv4Q8ai`c!ZLoMq{D}P8tjXs{oaj42sOtxNvUnNiEPV+heiq6At zg`sG3-w+2%nVXf_X^`wVjMX2P$qZT!lZ$?)IMg(SuPeuaW%oGFq8L7NrIv<$9LVJr zQh!34`}+Q_l6c1^{5yRgcMGWFqBV1Pr{M~gdqneo-)#1(3Fi~%J9G4kaCVN7oS}nv z(7PTnUhYG*W(q< zZNlQYucQveX2I0I1y!GG;*|~0aCcgu#1@alV3WJT{_aO`WQroD+>?4Z1ILk%?-Y!P zm_ZKjP77~hb)YWlDd`z&ilgOz9IO1E{QK%~uhba!{p`ZS$E5OHtKEEhX&47NwKG`RRf|_tR*~0%5Nb`5 z=9RB?Iov;5yj7J=)8FI>tEx>TH{Ug3ZQW2RQcs1Znh)|D0ZO1h@0Pgb$yp&JY$|Dn z&ym0)dxcq6r=Zydac=OUm68Fzh4F~?P7VK0k&v1%$qi>S%%#Py@$Ewv+-0`C!Bp^yu|g} z1-*0;XTF?->pSbg!6T;yn~w%CO}!d2{Ur9(>LBP|DiPtTMpIz-Y0$bNk;e7uL+zu3 zB=*}XSeJZ^wp}?)WYI|BpJK$2cl{V6%qd7zV227B$A6LbXe|aje4ZBBANQh#m+M4> zo2sz=b0al>N`{5Yx}z%Q@JCx+4A{C2v@SiSn8m;0;b<*h*+&uH>{oYPJLDKSFMKQx zTpB|;GndlSk%=%mvzV;SZE395BI!G~2db|upx{bdyyD!S+~*jBmc1TVJ-+XxDR2$^Il2d?Ng14k zk_d^{ei`1z-=mBB{~#p%K9>(3D5lMNEru#uV>c;>v-#&ws9KeVyInir zzWehi(6^Ze50i33_B&}pfIs-o(8Jh~UQ%bIoIY7vV!oO?p44~6PhUEN%D)0}cdw!R zS;rB=92H^k`QCWDle6F)tIs?2p2EL74Roq(BPCmZmA?$wizhDqpq`;l;@$8s(oS*< z-u*8IlQu@nnr#H0>al^^Km9HHA?lov1YMtx!aWP<(DmcmIqaR z<1UjNxh|uMFTc9VIT?Mq0LFmqQZtMB}P(dj$MPd5PDzS!f( zSLd+FED2-G_RzC8k8oCr0+?S5!>KCyqWz6Ts&Jl5WD>wL%(pHRke;}6UsF2G&4d%VU zu=w$Cj5D#JXGsUy9s1!dDN_+)H~^KzT~O4~l=mlgK$qj5WbNysm^-LQ}u8w3sK>@|92Kd*t zfG_Pk3@VaCVcqe$?A28Rjv0ku)Tb7jUhj{kRf!aJjX`e$Vd8%IK??xsAsrUm?c&O(g-{_`aj%6wS!H0*JABDa|% zd3T!^413!TbH`3239(F;5|??6|8_wr)8j~K&v&V)0%l8jjM~qOdF_bnV0=KvJMYC~ zd2l@Eo@{}}+iKX*_bW|1XDU2UFU5Z^TIkH%UbxT2k2YON7VeEPg1|8|_{z-g*rp^f z%MQ`yZPqwtVJ0-U9}>6Ee;^Dwx(X}2q`s!al|rK(u&LiZ3V!*8QqGOys)T*GCH)k1 z`Wh!+_~!ttnIz!I!zz5oFcL#<7jyTMUJ#bmQFtSDxJGF`6|yf5MGtdduDqr|8S$vs93#%dP zE+;`<>#b~nf;aZ?k<--)C&1%JCLhe24B;I+LwQ{lRCwrdWv!Gy8`}XNc3lk?nZrcc zokil-F?G!Dim3Tg;XXhW$M_kSJ%9h3V?*&TPXx>YS(f1)$g zG69iV&bX3>ctf7fK?YN!s0mTNU^fsP?ZNv=~yyZ;@aSHYL%UFZH0 z<(sldD2l+WGnVYMXFlAFK$zQOg{b{{FB#PLf>Rsysi@;s@wxtIq1B}q?S!%D-Z&B( zi|>)e#VRmRH)S>FTQup~BbeCR57ia4`Ja0+&U>_u_y2Xr)Cg5!ciACo8g)Ssa(qeU zc%3Mwxlp0RfIK?=t-WnS-F#o3?3#d0A|*2(#~>m;?;@UHJ43W_wxhHsb1=z|}; znKzbVZ|Owc}_>{vG6g(ZSiWMi_~THh)L(Lj9s3>1 zW;QiK`1luOm23u^zugv!oj-{y+UG&(fl`PVmnkL~{HD~9T-gZ^D=;mPW>rhKK;yWL z@VI5IFz|bwd_eMF*m%*LH<;%@TJ1IAuZ9Nn*KnkY=)*!iyTX%U%hDi%Ea ziUrdlR#5nOF~w#5f^Xe+lVe@JXgWumb}tmb{2 z!B0oqK!U^(tXZ2W>UH=c_VN4+-YLUqh)@D$Q_Wdn+e>&d{h4e|)=;={`Zz7EpA1D0 zG^xgolwS-!+h>Z(-cEcxwWn}qyDm*?y#z-V zry`8lz-Lp_=+W+IeD_v|yG^m<@zTAC2Ps2)O`ltGD_u|YzxGmA>8^{q|Erc(2+7A zEMRLyQ@HWeE(P_e3I9WSLu2TGu`Yr1K zTczx}II~t5?`yzLPnP4zyzlZc<0W?0S3?}|Jqj{jZR1(%M{&h(ZS)qdKuDJ-^v-_{ zepsS|seE2Mm)iv&hlFxfxQ3J|SR%TrnW2BdI~*)A%D>e-VpV@fPFqlh{nEPOjeZ8; zskeb|d+ej=E_-=s+-1ygF~&W5(%&H3fE@!b3(MuceC7H(NWO2)WfLXO=kHBy>6|3_ z@8;p~-)q=6BN!*Xy2h_d??H2!GZ*qE9we@Zfz5XTvcuqm`V4&U*#l3;%;V!0*W@4a z55R)V;nce#il1)(NMk$$ar3?$DDo^3hrZEtd-^7g(;K{~edGvH=g2vpcf16Zn!Zx- zY(@Sc&EMz$JB(Yp$Ks&GIQTd8H_hGKi>i%#iJprrLF;p^Fr$4kTc!8n$CVx2X3C}g z!8adVE%nu%mJefxg$g_^WxM!zbTUTos~|gbbtC&=OpShjw-6+%7g8A83!O7r%zp zif4tRaD{bWt5c_^k0|iJ0*)$ALlZCHOHEaH{CNd`Pwa`U5}#I{bDfoU>Iq&;l~Jx) z0fXjsA-#>P|hd(V8%jM_kLVrX2sV9Gao(0NK2FB6iA7)V&^8-kR@*qfH=-+dZQCNbZVly|YGP<<;11k+TpN~D zWbx_SpXlM5iP)!eIE6ZH7D~@a{^ZhVmi_GJ_EP08A5~7o=0V}`r29{H^62i?Ke~!# zrC~H|^Awn}X)KNq!q3T%&fqUn?=^ab6|SkOqTUxKpt9~#PJTNJi$1U5A~%1Uv-Y#B zsp=WXwHxTF_>Pj?JK&3DdnuqMpPx8)LL6NRj&4)yZ{sHSngPz4HpNcvRkZl{-_m0iL6Lg0ESre$q>=w6*z<#(5M-Q zg!Qj-aMXx_TsHbA?Jw}dee!9%N*+%Ar2K`FgWdY8nuUFEIRS^(JJhJ?4ZOL9s!+Aav^V) zHW+7&#jWon=;xGfxbSf_bqnq(>dRV}GlRXj#4!e!o1W%Tl81Eh11+9A!UVH@-iz|RBroXG6+=SrlExK%ijwX;(GoBE z*nA6ojJL(6j|I@s_Y$oxxCw&tKptwFCQhvAjZci7_* zNzS74dgq0uUX2i6ZxRhfEzkw((C0PVQQ{B->6Z<(W7S4 zN-KZ3e5@N&$!X}MnJaXy=t%wlZWTMb_JR3(?@~y(3Kq$`vQTyg&h`$clL;RnzU-V> z)8s8*KB7oiIL4aP@ufVk#TTZ9B~Z$f{a|(`Mes;cl$%D)qjxR{KgLSF;Ig;EN`qy> z8As`VsIpMJAk9a+M^wlf!z#t<>Py1tW2@xDlMBU<-kXF|J8y|Ihi(;?nK;V>j(?Yz zxt|fT1}&q_)t_J*M^ov=4B^T9SZcG9oaoDADK)ZKT%jt>iyqZblba`uc%ms@obXkU zA9y1sgpUy|Y(EKBZdx$YFN>0GPk`BeZ_!@b<5kI2v3hg>ZMytRtSvhU^RKRuvIZfd z+8l}Vv}FsGc*j7!&0F!I)Tf&HTk3+e#fh&!%;e!&v3SStxj4r%m$d5VU?WFzy_GIF z#am;-l@7u&d7X6EISI`ZMssxsiTU8?BHVvE4vg-nK)B5nQV~;JSB!5G0|ri~&RL2$ zBi$2s{yRqAg)=2i!+Da+Z_&7Af#Q|L9&mQ_Suj+wL93PBgo7IW_||Ma2$3uCsO{6y z>(gOsb^9)#8q|xo?fnBVtzGa*a^udu4e`|O4&ns&MBrB*6h9yyZU@e#f?kGfxG7jJ zGYH0xLF@U9StRs0bd&-=cY!B4(Xt`S!r;sLIPPIBbph6VK;xVO>fNtD#XuFEnqtaA zR4~-Pxk@uT?W6wfY1rrb9Qxj|vFycZFYf+xAfHh61I1`?>rkk{`C!5ajTg%MPT$7; z?9+tPPgk(IR)!e4cpsX_9H!f9Yq4YF14!>-1kwGh_>yp$EN4Z@uPfPd-OC(m*rSRg zpGrIL!?Ch%UavV=YdzYUti#`Zdx-|H7JuB1=P8cuxVZHSKX(d-;5Z`&$9UegDv2*j z+(MUW>$pSDBHTA_H74#+k@lJY;p~AAI9R%mm)}{A@#gcmqrGDJ*^6Do9w{IBcymvD zbodjT*}s$j{dozCPh?^o?t$2k*KyQe0qhTYbp5!iZ>~gyCPp`w&w{ust ztUJK9&ko@Ffd;7Y)r?}ZSAg1sHZio~KfF8o9A}Ju!YvXHaqs1N!6WUL@X2KzEWV{9 zdE480^8$C&dvb{j&Ej!)w@ql3lf!Fv_dyNk=_p3^4pOyNDV}qrhE7hrV~t=GAMv zxygltywLLrg_}NrS9N(*=Qv5M$qz=!lt<&&m+;lynTY$Ga971AUK;iZEZQQlxrfB} ze?1))^bb;*`$6nu{|lT#zVp!wD!ARjUvwYvUs-@cH!{;JgHc6=hNXrM9F)^GQ>@-aDSw4;a9H#@)y*rz5Vrct_}{T+FKv zZNZ;5an!s#4%5fluzpH5BrdYxZH)u4zri-CZ+Jk;oQ`7aWB;C*)WNlhR;Hu8^wHj>sZ{e zaRSHuT7_l-ZB*aW3D5Q^p#!Ss&=_Yd<;KEgBhQMI)ACd3b$te#Wi-M1_yW=7;}Ep^ z>Hv`ieJF9-TQa?>1|N=@lHS7x`Hx$dVfwoP^v(H%psPBMhRsxHHiQm6Mc2;H zhi}_X3)@d+z~(hS=zCB{4#-G`trl_g?#^`3*ku8W${E@gxX~q@X=K)x50C1u)4Z54 zv1;>Im_2(bwf&lddc$_{bnC5nVWl2-xKs#tZI*EMhZo?aSw+7BL%}@%lI+IwD{vvM zH&+dM0Rv2Rc=ElyFuj8hl}>yCSIuVAgs`#TbosBi|9&kfrzmpF=>WlA;+9mEn{s*L zUT8e|N?hUEOI&-TNw|@d2mg5*Q9${6(f#&0d0z)>_-j6vrhM97cA>F5Y~9{0PTiXz zbeB5!179@Jg&v*ZL2wm`Ez@C8TaFNtoh~f!$)*{3vtWhAf8s5N*Mff4FftCF3JzV0 zq}{y^m@k}94&jHP&3ZUpsow`*{EbOHVZHci{A-!zwzY!AqQ5dFzZjaVmkJ+KuL&=H zohE}wJM#S68y1VV9y0r<7?9gKm|%k~fe+&i^4|Avmf_h0J z^msfN{$?J6f-4d7|K9uK^%28`Q96q$+QWr>tZu-5_aI^Z%O6nU(@(tMQ7xDrQs=|@ zsgQNKSrGP|lRYp@B-K@_G*JFctct0jaqT_9|H4^l-(5;=)=>3< z-Qt7GAsqO`8;>sNghu&0sZR479c(LwZ0<=5eC4pQ!b9l)PYn%+?WM@Ncfy-_7iEc0 zwQ*Uo0-FAO1ov)Tp%9aNxcFHW+o_VQuiX)+B^klSkon+tB#*j|Poh;HF9?oiN_fy# z7fy!x^Q752aQMW3^en#@4@$Z~Ew8SVk*5jxHcA-h8D^rs6np;hD3Kl1%5dHW!rGakaD$ zoUy!&UIu#cg*V!CR6mj82B)%BtCqNPUm@4JIiYhGRk{^0jnAZvBFzgQMaBNZcu4RX z2$TU`5|w58^Aw;qs(`p%%A~y?C}kRzFxD;@$Nig5p^9cWNlZtjtBIn4gOs7J?}h=E z0n+#XGAw=B5!ZZh$NKvcV$re)Hq2_I;eOk&AfP)v>Ty=IxZh8n{Y8X;BB?9pwgo=? z9n0a;neXn~jaMI8Ep^S#lk&eX?wel3cXNkxo7q4<9@K}2B}LFzTH}cR^ zbJ%s;4zjtpk*zqI#koo_5s1S5@aA;a4GUPB6ub+S_q#={_-j#sr+FyGXe8-5WwR z|G_brR$1tro9`M;7j|Bz)E9}|BF%QyXP<`y@6!e64_XwUr;KT`5-|G_ zOYz^g!`<~$(8^l}Lnkjom1`Bad2TM2#s7z!jg#=mrbyi8DaVktgjULgQ{TMD=zCcx zbBM-8t)ckt-Ee7`YmTkYjbZAoa9n9AhifnOpt(;Uc?tIx>sC9nTCd#{S?(*%m0a2H zk2hcPcH^e?r&wF%1c%BL%9S2&VduVwc-!;wyd*k<=TzL}Ij_d>+O&AS<1FJL(&s(+ zJyG^UO+KL4OapVrNgbQPVBNDFlvS_8xv9&r_cU+JZ{CN#6}H&>*a1BM{21O248S*! zcS%g}8WhrF5FaGr_cP%*?}`yFTf83Q5}h#2JPwa;jfSpw0l)v7ES7#e3tK;}g}^pN zo?NNGO&$D6eV7Gj%Vx4_&viUBYB>k}h~(B4mF%t)$+9E!IAY6XPANOi*B|ZRPrGti zBifcet`Fte$rY^bbCJ>()vz|GvhMna!qtM_thTsP+QF}v&OB>Un|BbFPq9U(5g|Cl zS{>`B=VSGN^`f4=D?aG#i$-~Q*wroyeg2zi*G^ZCQ-_2O>f zDmN@x$O`dmIAG&=zB(X~zZytyq07k9&yZIcHw$jD>*P<&y5n30 zV@MB96LwDOjmA4|#GG#(G32Nl{yE+qH9sE58hJeCypBRzV~jx)_oJao4AzfM!lhy1 z=$Vv)St{!=!{#fTKOK%EDlWssL@C>47X`T^>Ng?q-Dsy4M^jR9j_{e25bH z9_v8DA8Y=7M^oxHcjEJg5wz;QDL?tHOj`}Cd62^sniiNsTW**$X7u3U2V!X#htlIy zDO8(wpKkUZD}T9fKRup*4*F_@(~@t8pi-_3DYgA^ig&uO{lR>^AL=(UP z=ATM{#XE9f=Zw>`$%k@5cft?)WM&784=2#OFOvZ4bhvTCdSUhbLZXB@)Ma!zRhZ~d z^u&?W$F5PF67ybET;7u>Hop=(xH-Vz0YM62EurA+g*NNZuudE-6<*vy?5+YHJm@#b1JL9;%NSzdlb9lBAm%y3igZ3M2r18xJ~~sTYa7- zsP$e01BVHO4fd2h?D~-1oR&YeCv-0H&WuU%tQfwc_jB9u!e_D zi=^Dg4(N^F!SiM^wJwu#a8uX8z*W;RV65aKT98JHJ&{w-KY|f{@$gg87zf@hrXB8= zxUp|EEHGGw_jv>~|LX}mt*mfg?Ip5Z(3j_V-VvI|8}s%&Bm8@s_k(V#*1NTHHIJ;sL`OI=a!;}pq#O7kZ4ZUHh z>3!EEd6bkhAicG5`_-+fz;{ui71;oMH} zeXcrc#3+H`h%V*OIUcpQ)QkHckl?VpUifl&5;|TtgJU1E*<^DY?GJLM)6Wm#N1M-V zd8%3*S2K|g2JMqClkK1$yXV3*l?Wj_(3gJ9sbT8`U!it9Q>sQBUI;JeuJ#JFG`$6U z^Svc5E78L{A@FC(QcOQMlrI(y!2{(zsmqH4_xaMKih31<#I?zLJhI^G zW1Yo9Rb{yDvlMTZC)2w4O|`Ltz`sWj<_CtcL**3yw)-%i4eLtw z!MC|lK_5F?b>wZ6HPAF1@s$2|Fmf)EJhDx2&EPb-oK3(vU*_`J?^)!~$yz*rL%N^M ztAmgVKZ&oa#((=Il7HPT+OFZk%OhKWyh5Sr*@d#%yN|*d%?#KWI}a}(wBRobemwYz;Cj|Q?qnjRy z1(Ta8`_}?I(q@Rkk;g^5V|J*#$_jg_Cs5ty{%lz|5gl7@vP0x(sC`gG51e&5wVxO7 zm0b7tJye8kD-HQrulEqH8IKDm{3i^tGlRO|Q*3$tIaK~s5UlnU&;qTl_~S%2Pni~k z=T*G~g>ykT=8zi~9J~uVSGUl+HOskJdotw&U*MLd^ReI0BhW*~UU;)5jH(QJ;d6b2 zF{>LX?}11Y`}*kYqV<7uu+}jVLLrPt zIo*NXuAcnl_E8GCvYm(JCWF1}U_qsLIF3K%he{is!K(e~@Wo4F34Z8-Bc2&Tud!#T zadR1a9P|+vePvXKOpQ-H6PqN4!%H!XT;h8fc#2fy_6ni9xmIoVfhlZap zXcF+FCN122!3%4v`(cizhHRsQC%)`>1zyjvp;Jx2IV&7_Vh5|iDqbQ41+}`NrH2c0r;Bk5a*vwlFf|wk!Cm+ zRQfHJnr)B5ly|eqPIn|-&+wyb3i~K<$5^l&-Vq*uJ51YJ6ybD>8-2mQ5PQjxs;*2G znuRubP{3{QdF@O(gENG*o9Qs(mnD=uTP;jF;{{IA?~klJCQBUMi3)yPf%uPCsbE$p z9Cyy4B@X?C_+)z;d;4D5<_$6OtmX5;sJKK-m*y2N#Rp+^pf#yNgAm!4Q#SEhlJaOY?gtkLbtSAK^9hEwj(N@kELV%mhwpLo3KIR0r1kjV10A}`^fZQk4Kf<@_-7qFG!~g3*JHN z*)TRe>j^fS4@i+WZ*q3;L*d<)^2}T3;EU8vCDu1ke||@y>@0{{J&CaX}wE(yBCP5w>paF)lJ1N24<)m ztj67{oTT&4n7p^R@SqM!v`}dXZ=16QpK82;kPXXNVXWl)sQ3el(;V@jabMW5dmQI4 z&*2lHuDH4FFrEs$!mBS|;r59aak$|N5T0M9`xFTEr&E^6A$t+>B6$IVJWs_&K z!f=1mL9P*zkYww7X<_UfjxyT1!T|F-z@K?ek@-S?2It^!YYk6;UC>zI# zsQG-MOi3=`VwO7y-vdkVhRql_8Tpo~x9H$sy*Six=*8u~p7EMV10ni{75?e|i9V#1 zLesJn(ClVg{(jzVj2{pON7qco+=v9|W;I#3ksd77MRvjd$T0Sn_vMmHv-rRhL*Ari z<963@AU5r|!UcD%xqa*|a=(56oU5}@yUCfqogPDhdspLryC}N!xer;NiDc9lIC1kX z?D^~hXU%WGnNMZx(xD0KcStZm zo$dGVn9F%M^G3=@w%ichq%2nPM2Shz9*&;}rSqZsc5(34H9W|i#A4SSaNMLqb}K6u zO4k-sd-WddVEYTUejCWsSGNhHqV}S~DI@N?!4zMa8B4CYckoXgM8z0`vZ!0@*vLYY zmp#}eguQ$M`9TM1Y3N*RlI`TbZ3j^!-CVHku7cLNDV%9#ipTf72e&nrymot6OnLIu z)p$ca92lO)vE@bJyAt?pzs)X7_7VLoJ|fP@(ZnvMKGeOB6ZL!`Ip$`)rfIpqWrKG5 zv_yRhY0gRp8q zocOWs7}u8P(So(0JrO9f_~~W zT(ruQyN%dDH&eI6{ySkDx$r1nEp4Qqj8h$)W1 z;o==-_uVB{v|2vpJ00QzgCAnykYKi(qJiGsTE*Rk4`gO1ZE&yc3ZBaqSg1Nk?EFiK z2Yt#G+@$?$kA%Uv<(o2Zo8gSg(>?jlhgd!n6NbGaJK@0QLC{vTU6}ZGulQ-J9SmP} zoSmN<;)`{$bfe%7ZMB^OOG*k+bx9~YpNU4Oe9Da~k|R|2COojpqd9(up>Dc6WjeW_ zQ%@_NJoP-ZpNfH|$CLR(hpF&uN+pRf1mdUHn}c+Fg#`MX|} zXP*)095gB`*W68cq>fK-wb1aUL@-u*I~+ZIV}#6_+&jJXy}J7ICu3f zIQCQrybpWO`QDG9y)0AoTNKS#FTa%>;<;kZn@ybdd?MD5uMq4sqi|uu0U8)~k7f*< z2|a&BLd)~#^x3UDerR3Ifl`N-uACF)qCFOP%6RC)A((f5DBFxGgnms+`OUp*F)+4S zV%oODg7L#)Nl*#&?NI>RE$bofByvXPcgR=rqr!KFWLszeZze>LU3A}bs1`2Q9y3pN zI_3x2Eu4&Qo<+3eSU%M@{tyNw4~1nXl!^1>M6;a13`3@(cw8jeG7Gf1`;)fz3YUpJ z_t2WgLgBFIF}Wj75bEdT!)2+1d&)8&@Qwl3-}?rw_PG??)B|mbY&q!XKe(Z2$^CVE z;My`3HeF+hlT#G<^AcU*isC2Hd*fY~gR3e8cY8B(3@jHXc)fw|J>JlvSS@f&sS|zv z`v9NUtS6JXPL%UA0W`F3(Hm!bFbeh&PqwNFZxzPUSiUNFn+M4DZ`YwC7Efg_oKK3Q zuSUT7(PzZYtCzqRiHVx8XhG`R047VG;+$8*Xj)pV*h_6BrDdwX&5C@Qc>NYsm4=h< zp0jY79Z1*H9~@`;(P5JzkaVK6>nx)sFwDdf&Rh?qxx3y99fv#@9+zdv0uwdLZp`~4 z{7&>Bt<-Y3>)MS!&in-v!XAnD!j-8dbtmOko|asN%dv~5KkfHTrwNtANU-&md~NIK z=7;x!yGEk$s;MKWL`ZY(N4?Q^S|6@3{wZ5Cc@t>6bYRDyN}|K@pQJzO6x^uZ2Gb(9 z(Y6O;U`yReFMtf;}3 zhg*e$^Kb{OdRry851cAatZ|0VHTPi9;%ebx`7)U6lq}dUnaUP6)nZ!N5iqdH!RDl{ zV$MwkURtHWk2|~bk+K~)um1$Tzx1t?U0#8wbUM;CVLm567>I+NGeCX8BYGMZ#Br{U zf*6${u}PEgYzCh*ZH8^x# z7OmSJjp8$Jp11TUtJ$2PJ?}pVUrJu#lXod}WkOfX{w z=asTMv!?LNVKR=Yh=Z3^mDnM6Fwd^D;*Bpxaf8DYD$6v0sgrY{^6nNm5wFI-t{y~R z^I246s|FShU1-KsN2oseiv9nNrIu$wc-CGQ!&Q|yu6q;|8`$Bo@w)t6bq{(yKg7lZ zUeGfsyI*%jv3!F><@Y)~0OhL{(Q^DAjFmdm)(`Je?~Y3#^6N?*C;m^-dAQ~He{ozx zNTq>>gi@lii+b*JDijilWS7iTc1T8h?>$5+GLoo}p8K50N(tG?-emjQ>v#YDf#cY8Xr()=lkvl^2AH3C zS^U$g%1@pv*}HlBQIeDsYmSa0oc#!lCM&Y567t(85xjQKcrM<$L@ZhG4%ffeVV6h? zvF^e)3?DI)sjo;G^yx^NPDKQFz-SET`_x0v44BZ z{Q9lq$#x6y(T=SgBo4zfso9u%FpP_9UO?X$Y3z2RKYppV)z6n>Et?^rX(ufl`%=F5Z;4oTKNV${1Gy+O1-r!UrN2Y8 zdH>5|m?HGXj&7N3mFJFa|ExH0>La=Gog=g{w+r{lxDA)`lHuVta|-)YK>t}g;9tWg znx`3#Bie|JS04iRH-Ys2XpHcuaVO6nZ6(-Q4yWr2oyjjhUND^82%cVfvQf7c;ltrL z;f=Pjv=^uc2lq^fAL}j_Pt29q|6BmUuf1u1}Rb zQuVWh_9=ITm4AvTC*Zu~$x}x~u?cRqEfHS?52p4#2ZRnfU3txdcK9Q)s(&ujk?r@{ zL0{&T!UVt5W8HdU)m9N-1;q$gUri`c-3X!B*n+^c*uvcHFB;ysmy&+_HbCFi*Nie&4$e zPVTEEXT^`QmH*P{!?0v{V?6~dI>l1IFM7h%3O@>W>I;3Q7mDKOv2e_<8~u58ULJ695UkmwM%I~? z!hFf!6tKNN)a9Cj@!+>Y@tm{bCsYL08zU(5!WHOaS4caOs~~Dg5^2P*09aop?_N?N zl%HKLi`3hgZomJwEvD>!pxxt_~SzhJbbX1oJuaR?y4$KUaW*G@_W~sOwWePo9@#{-c9XC zU9obFHupAv0*`!N!yw~a+7#r${iANfnYMSNCpl%0)$O3d4vXnB*zjC)ckmxYp!4yr zFuPkxTE{2%uTeiFXDcfH1o*c9$#%P@f-qJTuc0U8j784kz|tLbDr<`%uIt zuA5sK4?31{jvks-@SATV;rfdHc;{$2Rtz~T4y?+7 z@9`($tnnefKPZXQ^CQS6cOGdQuaUfNB04QI=0_jYag9S7+lT$2KR*GAmQUccE;9U@ z8;>Wfy41ZHd6woowu(1t9A@uYWANp zSD@P0L%cd*w)pIf)L9ukMO6KH0~-Gpqtn8T+|yd}B)yuAog{v#v7Rg52oxDKYv5q> zIX=BSTvW2VN4?g3#F4Uk_^RJ3nm;^<2ChHJM&~`uE1bX`2Y&(6aVNO`-bDB|NQakq zl{`Z)9oXVcmpXoLZog>Q4my{73Kos%&vSiy;zfyT+}VCO`^Bu{`U(5UC*F{!=ULiU zsTZ>6y98Qsqc2%6Z-#2eopkH&PPTnhie-V%!8v&!-!4>?GOXER%dw5rZ21ipe*O?| z_o{#gdMA0K^HC`W@`HN7dvJWPnZ_s;abTa>7}B&347LTsxJCWp?es!24eSK>2Ik}9 zS^K%XVKUDjY=!^U^@L}yYGB>F=iFd?9ijVioU}}_?vmaXNIrZM&YaN0YE@-+x#a-Q zRr;XLL4SUA$p_xe?#gZ+=CI?5yHLGO#61UZ^6?l~RKK-TwA`9ZHY0D7#`b??s$Gl$ z-qJijzf3Ia@KMx1_7pY+dvf*Sv*3DF%Ca~4|Ya_^@5wAwO~Ac+INf| z4=N|8_x({nK@W|ucIEEXiu~zk0QZ@539WbafR9r*^ER(>_+M2Hef}0k`R#upGT{Nt zY8%R*Lg%x(bnZHtJ0B~yUV!BG8t$z01orN;;KsW${^TRor)qXV!=@_Gj;x_)A5CHM z_t)g#cP?hHUBd$$l3|1E47Pvd1i4}3VViTlSgfLmX;caEeI|=-XB*(ngq<{Uk|6|s zn#2YQqrheASPJtQjF&H;Asf@HVEfR9#vcrz(xm=WXICzN)=BbeeHx0#-lU8AEw6-u z2m0|zs|zsP&=xL7bd)jzHz8+IBB!6w#5@)>nXq@f*Pv5uj5V}U>9pWX(E7*CJ3)QKat@}O#M87)X!4TBupWtBbk=`(#5 zAJ^{$%?a1Y;ei7@ywFc5BuUr-fj87GJj@O1SerfbhLsOV+(p-D0Ns&6H zj3(D{dttr&Dxvv#Axh$z*@wHszo!}0?afJuf>Nt*PEC!91ql3~m}spq<34&S@Agy-0C|!C?U{E;*xwzC9VE}=4KrRuK2YYnn};8XBAp{{>|MVK*AD&;H4CC(LjO^`HF7b^ zKHj6;xIVlx#RDg392Ac99KvUAZpH(D2eG5&Q|Vb!A?Urn3YQ%wu+@SS@ZH@3U7lp% zBc*M4;kzOyY5DTHVH0>^Vk6#j?8RHB)uT?W93XZmwfPm|Cyjpcv89zXqP$wL8h8@8 z=_rkzzXB#E72}@|6X-(pV<94ZHkB&f!J4Y)^s=faSNLZNCMqw{(Z`cD_FbY?XEo^U zG$X$La2{HA$rCU2^cD|xEr%qdC@TJSn73~X!#l%lDE#9Q7m_3+r_` zebt{6HczK>c4!LBwr&tlOtax(P7+J|T_h+gPUfgfTj|w_c>caJox5(hL>Ia_;N?XL z9NM{zqrC%Q%Jg!6IP;>g`^gR(vuZW$)fL#k=_mvng!84aJ}`7+5(j!lB;9aaSr3Osi)cP)EKxn+=IhU>G9Oc3z%{DDtoWGfL})Dqw2|Jq;)5qCdPGv zSv#)4@QhV3zI!)v?(D|*Km+p~Vz}gpJxskYls3mG)a8tnjJY2OUZgIwvSQ66oMHIn`3 zxsS#fkD%UBavbt<5cRl}KndTM;z89|sT0zfmzkZ02V3$5`+>u7uhMZeQ#pzu#cDKk z$ZQPwnTDfAyrD6vd$77-Hm>O}lH$^_^x?m~xT!4`)tl>St(}x{`xqg{8{B}m_pk8! zO1W!h%lT}JI99+0fu9RWVrE{ub*z8U$O1UG$ zoUuIky)zfgix=u^EZN-o5zTU3BLB}PjO#qCurXLB>g|_PQ(ZAXd7z86RUP^2woY~b z@C_--W7qH(DTKYKBZuS}mxU8x_%G$ryF zZ%b|+`<6bwp9I5-im2V^7{xibLRidA7#bc;ZMrt%J^h8^ewPFoa#xA&HKp-%o8y=| zZy=9eGMdh%P3AjIo!M$=Jv)u-PaeJl*(zO$f;v8hQQdFCmBo32|DVA)Kxd>d-X{`v z_f4Vr3#Ww4qCg?O!HE|Rsbt6fwJ>z_40cO84wJwAq9uv1q3CU$09Rr}hpOAK{BNV+ zKQ#?jTp1}UeJY{&;5IXYst-7eR5lCa!5!fZ|6^yu8Q#6xZz;j>}#rV#BuWI5w{@ zEU*c}l*hfWJX?iH`4Bg+UB<6EHw9OD~oyXuh%MKX% zCrR#Od;rEMu7-Q26|mZU9?oCC8!zjWiOFl@aHP&T@VfpPR`2t!-PsMbJp~--a|#a)nTJ!}rGxXm+me%QB-Jfc<)vHz73=!ZGQB3z_F-50zGEdR z&T)bV%5u^@Jso#f)XIFvAFR1BYzS_)897iL1sq~{pN2br1cOc5 zsOy|57#->?4BDpwYAzRPM`;Eu`LvC9PUwa19p*sWTnn%^{U!>jQJ}e|h~`J2n>K=4zarUPt8c>SskuV8 zjuyhdOcP<>S_jc!;$)h8ay(k3y`~RF17LyORqB!-3$~}X(}Tr#g!`4-X@KEgcy}d? zR(U>!tuywBQ#Vz^9lHXt+vgQ9IK$a)(D;GUF1iQbj~M~GZao*pIX_{gW52}W+(n5)nPLZp+R|I=AML)l;WV4Z$K>FHRiHry{l4(O(POPsH^S$?W$Fn&v{pcV});pe?3Az@Mp1vK?U>p3ovHQkHO zsMoH=M4tp!Gp{6?97Avq^ zdS~p=4H0xMT&Eq;r6AvK#@PY=F{#K9oL;8!!PCpAeSaNAhHMvi9L<3tCp&TD=_t5n zTmo7t&udq;&*VgXMV^<_lXOp+;FbZW;i)Vgf=l!GVp%1rmnp--fG7A$G>})8zoq;0 zCXi!WHN8~50H>-)(6ai@_#nX(|NE&;PbK%5*XyOMBxSRoZqY-->`!EWe-2*G`-^Gu zAvpM!7I=2LNDv|33{`14_wvGM|`f8ZkPmhkH<~L!ydfg-_ zSuzF21i!)sD~-5X>g;~KIEI|=R0~sT%W*_r9wjfGD;&7?7!ygIo9~{0EBZ4;&FmV% ze%*RpId~a`pR~qrJrZ#FugO5`1a>!i#|Hjk=*o|A`_MsPs(KvzrkoWHbu8j7V2#7K zM}X$04VYuKnqOzGhq1rTVZN=DIn__+qA`_x>d-IA(bSJTGwy?Z`51U?{hGUYYNAzG zcl)`!vM7GPF}tm~1IumkIptbsQU3RM4i> z(fnIhBy^nB1%K!?h-PkSFwt=wYjrVThu!;OdCgw@Y50jc$lq~|%VBic7l=XL{ZV0T zBwyJbO@%M_(T9pMZg^1+3olL(HgELCk>Bn`&m(HRtJzZxxx&KOv0S(Un;x1>H>4IKgo`sX4cCv5qMn(#eFc z4+2?#ONDqc)CgAe9fq3U<-+PSe}tx9(thB<0Ui*NDMVfB!dsPPU}ja!Pm_vp#pH4P zMDdg?=xajO@4Ol0!<#FOL{}f zsKbUa)LF_NKkQ?H!*(P?_V+nhdf`9Nj}DY_rTVaC^mth1ufvvYA8PL=>GFr{0??Tw ztJ8`==~+J6kNZ_SWgpQK$&E6zZ(rE{Hw@#HRWM$^ zBhD{CJ0^mvjjp8+{9hKb!K3Amu< zr`%j(rq%BCLW{sMI`QlSbq|h!%1L&-K0gf3I{~Y&T8ihdq=={ddZ1B2jj&r)4OOL# zX!A}Zu%6ummSkA)7`qFw;O;0~gD%3YbLnudY@Be!X`dhjLvb0VwQYZOucBkt`@Ej zQN!yc>xJAqYS=JDDeImR%Hx;gqS0nehDd0A~vuHQF2d4OUqqf{K z;Zut*#r3%bX7~0Am8#z0KFptz=43)(hB6pCieh7!qU2Xo5?d-lVg3>Yb~)c4e}$BZ z8cipm`-do6JN_u_%X~?{rA)_~i(^T(k0<<1xlYHgIKggazK(Y%K9_NA2ps`tix~W8O2N(7b|{jeh=1{Zn=snHL`j8*%nu6G=VJ7%N& zm@~M~O`!#K5s+RxQLJx>!fxM+xIFeVxXg3JA)maUCPM&I&0nyfyjWtee1XoV{rKUu zFid`%fCIMZV_Ss`OZKkkL9?Ws>fzCtby}C}r#*pb#%1Er(y2J@iqzGZRxXb08iuJK zwCTNRD74N`x4cTb4BevV`zJo62}B*V93cG zLW#{lc1|4yUplWwlU0=J1UGf*5+Hbx^PtSr4UrBgssna zuy)9B_`NKiw3Uv**ro^^P<02t87Sej=@YqQzh1amx8APH>uL(=StKgwnbIaR16E0_ z6#HM$#7MidkkGmALS`fvp&2U7XctC*RN~~O>p0j%;Edw?(BpcpAUNy6Qs+>3eeMf|44#TA z3Y*YSD_?fta=!RSFy(@hRRpsohqg~1c*PHw_^|r;HNllkn?`VtY6ZN#Er=ee&x4MO zyfC}etjFtu2d|IK{z!1?(JF zLEfuJ(34FsXjOGKe>lDaThB&9&+=XJq1~hSMNlg4Svm@xM+~FYuF*nNLg=--m>9$bWM3D2ZnMnO%9=lL8?@?JCcptMVpHi`8f^Dd zcxm-hek?izFMSOX@~VvSLVXYF)#}GixnVF&;%Ay4A`H2o!yYYVI9+02epsT1*ZR0Y zj^zrvtacc?G~5(Uo>wQgfK%ucQZ6Q&8DZ0hozP`%F}7*P;n4Sa;Cm~aX4@|1?u*xO z$;H#+z42XW$|4_bGjiatfMh=OIf-YQ-IMrIL6kVpo868@2?5gCz%4u-wN0XMRc14G zD2juh=bk`%`9|~|8-_zRCu3I7IvjH?1s3X6(Rnv7nDA8c7>B9jrphqhwAl%l{Ch6E z{?*8TwVdGUt{&W5Es@((f00u~07ND&6TW&)AyrV}KFzwQt{^d@mJUMn8-S{bU2$6S z90(u#&(2o0R6KXEk)GfFDf5i#%qd+Id3tXpT%fU)&tIzJoN;^jziZmuTzQncE;R^ zr=X#WD)xS>jx?(W|LWI)4{8*Hi*G-gYCjiSugCL3RT;L`i<~fc3A)@So}d>4zg~W4 zvsgu(zdK&)s%|CY4tL=E{ZpWKV;#(p{LP;|v&mH60bjT&VW;hh;Agf&h~2&t3*)ZC zYb6_$WrG?k2opD7fH#> zv>$Zfes8{b-<&)jt)!OD1Eo2tH%$pHBt2KjO_);vTQ(o2!x|spV2L)mUaf^`eT!tC zXATQZ!H(GL<5KyMkuGSWlSzF~ORl|J`4H{c0`EfJ!lU#qLe9jkP|}a6tNsh9b5Id? zZM!GR1El%ErBCEP`M9uhVk9jcaEwe-SM!i#^C3m|8(j~Mq~$|~QE~8iafGKHK5nX& zSv9Gm`i<#0{KZ|ku(L*Jo{}u)FF6Ad5-U6W#TmgtZ8?^$TuRT&d*Q)>8nQ9|1K@7n zLRofL6a7=ZEKIwY1@CO8(+cw{^7CpGw)z**#sq(g?OC zXp4oB>)}h{Kk<;oQFwO!0PMFtD?EC92MiZ&hU9U}gaZv@g^jN@FvvYx95T8Ko*1wf zvgXA=__B}UA0LT3vD#9ck7s2MkNlBUsvift;bFoXoeiY=&;{Pj-9!n=)9H@sCE<+! zc5+Lf1}D#5BA@%}^j=*qzh=Dz@_IiKLkt>(?`8|cW4eOaUhX9ux%e<#j{Yk@xNsy* zZ#W=Km}UmjWR-r#9hFU)(U;;b_Jv+=;=rTqG8|nHCXXAE0azc!A^$!sLa4^3f@J4Jh{UaZ>HV8|KHqgw4 z0dVM*3lukIafH`-3SDc48Zve9*tr(ie76}MXHVhgm04VDkqyfitD#}y0!p`Cz$R7d zxN2rQ>6jmdna>i*adRx21pa`W?WGjjZ8t5PW=Xe)U80M`A%?xCeLR@zS$9;X{BRU*3<8?EeE%{%$n*qbimM*Fi-66jrznWbj}Ful-s8 z#|;9o-?ccwCci;|jw%hlr=s>AB-Z#F;OIr<&s_ z;#>!6+EXB_Fq({8=8nY9Q~QcnSGw}S*Gu@;wkCMv5e9!c4a6}fBVnDA7VoXyD)x2k zAbCk5<+1mlk&RN25ES`Qnx+oJ_1>e|Ov)6GfWz>(`&sb6mnK|P+Cuvm^~U=?6q9C7d}7rfDT4h7xVEMEJ!jlZ7=q>oxlD9<`&i`~|jk+H=EDC=>AE{~M% z>D}^WI&*SF$F3#RIDH^*pLq{H4B06vmY%2IpOhuGybU_xTi6}$D_VAO!y^MEcgI1Z z`@i(@P0~Y*yWNoEdi%75fQ`8fc&N?%EVNm8Z?VBG?@^ zf~NIDK+eQPB2+V@Ee@p&wo?^}inTYaS7fel8C>dpzLKGN0w zUudG#!8TuBNiXh?<3Tnj#i|+2tkThxA|2k|NAd|rP>yz+yu4~Pr%c>VzuS(}B_kIO z)|k#W_gQg^m;jMmTKM{zzcdt9vhKHJPI@ePL^dl^{l++9@$c_+;#POK9%O(OACvj) z=AE>$a}^A{`anoD>Vj_`n4!z~Rphh~A1m#X=#APe?g5j4X>^h_aW|qH$tzWjo z!m~Bv4TZNtU;@NxB9@TXuyw`K15o)E-W84zxRjDp?R+x{ybeGE0CGOEEmkPLcvMb;E z-9QgLZFtINC$=MfnY(u%9;6z<=d|C*GVN_x!@!(tN-cRy;0toB>B^H#N~kFRGeutw zqCW*~WHhZS|E}|aL3zo-iPI10q~booZPYxrKQ)Q9+>&5_)JLIeTeED~cW?Sfb~aPJ!U6q|6O#X9|JW8||78MA4xNbI4KN0V^)vrlk@` zws}ZA`7BfB=Iraj!(H9!#qB$rCeh1sX{ zAt1zqr{1(;_mW}={5Tei$9sxPpGmIEEnTr8V66BwbTxc6^sfDqsSly+Z;0<^TGI5H z8{u-^67KFb4b>M%iOtqaaUX;efP5=k0?D@Vq>mYLd$2Fz|?h?(8ag|D!wu2 zyhh0@-*Y8+6?I|NQK~e)-x0x{W{RKx?{9ZmDmyXd435sK7lWcM3en-6*mzY1#C?p$ zhDXRxGNS4B*?72jdmv^_>n}{pv?J}ShS04+>WymY;1mBiNKl%CPySoQu79klAT|MJ ztK5QP$z5@3Yi}&-0BEch#Y5WPi(_?@#6D8#Na0F6>%ZEI0p@OGc4rHV%Qd9jW({?r2*Y~aq=!7j`DHleHVI&%2&prWxr zYYEyZqlItj&e$g^f~_Q%d~Co)aJ})Dw00QraqSjb@Mag9OQK zjl|b0y}S9Q|!Blu<76Q36zprLKvpn3bYSU7nYs=W8-^uJ>`IJtdvSm%5$EPqGd|6;hV zbQ091{s&9POlO1PHRAoPESU5$L&YAiWYe9(gyWLK&PJNYS9_ESE=T3$Db4qP^{bOS zp4r$IXv!nJWAXf^Q1~%zCwDGsfxlCjjsAHE{tD86`}!){FfxZ``4rRoiKTExnoYjn z-kYCj-x6lNnajt%4Wu)HD0)xsyrEnTbGN(V@NQcqr{7h!SiBr9t7pj9>6|Ag zwNpG{*&d;DLpimSrh?1u82OBAT5#3nK9o&S;V4(hnIW-LnyRc&-^2<}=r^L(fB)d? z7G2DAR>76vigzl9V1vdPXxu2dUjr8M)S7T(cXYmwvRW4LxG@JNe?th+y- z7fVjA0VYPGy=^uR>iq}~E<48?$e4w>N^nhL%FVdn10#EQ!-lbyIBi!EtY74fn#$w2 z?PdceD&)Xdh{XIw3YcwCi7BOa5N@b~{WlZ{oooM)=DgvkzRZt~O^?AtS6TQxeFY7F z=>)GML#fAqYTS2MUw&TQi^tg%P@qN$U#jiF7Vc5pJw%0#1E+C6!x*;7dM4aoz8#{c zR?9Cu3>8jGZuazj+c4sx0(}eAK$V^KsJFll7L1QYi4KgpTa0jf^LM!EkqG$p6}}z?Cfktu;0hhw&o4dfx+`i!Z{Ov)^Qj|5%Xng>?11vN+lzjC6b-NgkK3 zTz$L|!bBymKRXd(ekaPi#dJjj)j05aH4lg1KMh`8d*kxx`CwN#2UEVgAf20nLp#oe zC)xU#Q(_?&kCc1_q{ngju{7qqE+10-Cfq$@#HkrQcuS`lFnX{X8=W~ry=?Z;V1p3S zj1S=6;ltQGKw{BW=z`J0e&WrbtDy3537(Ws#mzDSb3eTW@8SyR-Y-z%Q923Pc?tOX z`Bp4nX-bN_`{It&uv(YilH2)J7cA&)LdMRPwAwe9T-FtmLERgj&Pk}jy6Cgb3r0}}UQ#@lZ0@i%jrVR?d@at_h+_7*4$ zTz#;WXM@*<1A`h9%~Q#2pJq2LSf?0X$_ z%z6OIYtKO8ZzcTVV1_^KL&5fG9lW-gkClnFve7r&r4E}3x+PoSxE_|m*={XRHg`5Q z=I4P!ejWIIPlE2!{i%6;B6$AU3zi3TL0P;Y4;=jkmOGh%pM5kGVts9=u~Pd^%@AzikSI6%MXE zeEe1jozqWz;hq9|do6gL&?2)^Jq6|-ikP{xRW$3c3C=WbhneLg(KE6q$xG|t#h7#O z{6k;Vx-%P#=RARk>&h5Awj54Z7vUS9O|(@%$Z~2yN zmUd(R-%Le!r?F9MDD~az&hg78@mR@MJ$Y6FpFJX@#>-K>vgZy?y^_jVXUzC({wc}x zD{z+I0WQ#t;LpWg+`G90yKO3EyB_;_yxDOsd}=S~9+1u+j@CTsP-o6O9K}61+yejO z)_ku}4TgSircdV{!;iECXs=a+wk=vPtj!8<yd0Ps$CnV$P7t7H*xDpitf>Hm4DHiN5#Vr@?v8ucb>%wgC$Iuk` za?u>`Ufqco(;{%Gt%=ZQy_!^u7z4V$Rbb(LTR1moG!8$KBMZz3kk{Cxi}m;2!S7=` z-LX9gnn7#X`rjZP^`9DinR|qVgGM~d^cSUEU(c^JCUN1vd_FyR5BHm^!aG85a&ysK z7FrEB>%c+YeX^Nj|GPq^C3dW-SI;jMo%#Kq3^omNX_jEklqD0eUDqBPCVi(&fRp zVZ|zO$=X&lcCW?nBfar&bg}%(#CaIzHV+3DJP}sCE2fx2Rh-#cObxp2wVcGcD-8MV&rR~0d0Cv^J)a+#nXnpLOh%ZRb;V4_L7H-FQP z*M<=GD?zYW-~!_LP&hW&66VZwL~GgxFDH$|H%_haTA>ba8Ye*23s-Dku?~mk_QRwr zRXF#WD=yiVjQ(nA`0V&BD6(0CTH_eaa~*N>sw#A}u)%kKRzt~qFC1T)fR8@>hSU+= zVNY~#>^w3Tbjp)p`S)YA#QCel&+9~K7G-w5-&a$+M+%IuwdOfSz2JCa7?+s#VYTX; zcH_SMB!`+1c5Zj$w=AE9xeDlQI45>{qt z;_*apYJNQnS8kER{bvJceBW0>zHhVOYvPEW-pN8u)Q$=g1@fv1%g)EI4q_u&8xkC=v!_%C#vPz@fF z)}li10K5}B0e`+0uMo1>-Xn#79_!7KLr?JarxW;LWnUhrKApqrths|cmz%6d z@wMFpXB{kA*}Zrtl|Pm^LYaYJw{1r?hrn@nDMW(QAg_nNdQ|4jl&5x!q{xjBcF2 ze<$P?-XrjiRkJMWV`*k+RLiekC$1=V>yf3s9zwDYp=L_2) zZ0BDP^O2i5VECsXlp;3B@BnoWC(yy-*z7`VPM2i*%3g{qn)z82DvYg6}e zpN3j6?HLA}yzOwklrztcRg&`4qrhwIH;7sDg%iSTc~Qh#Oe?uZU!o1MM)f(ksy4Fz zqye<;*Ak5TFai&C1AeR;AvQXWuVv{?QWE^aMk%g_uw`qiDU)R&->eiV=QO~5AA-aK%ITy*Nx6IJG>k$-19-u}!A z-G1qDAI}N2er_C|ajFtyQ;K+ByEl&Ld_q1xTuJCPV+XhUZ^tSBNjXwqQ_RxzGcF}(Qez30dbD`GIh^H<|M6)AO zPA^AtC+f_^jb~eU&psK}Y*xViv39)ZY6T`=Bd)r$9)D=*a^KJrluI0J&)r?|rED|I z_X?r;-~Nfi!+i15m|#ArJQEe>F6Z61`r+5`6y9FC7ZL|IiC28oc-5{7`0bg89;<%} z*AmCE*(EExJ1h10S>;ta=%|lfCz{Z{%2diaWQBP>jW|+w4hkx7#kAzVlsoG&CgjyT5<^;o)*W?z!*x>-BuX+7>rFrLP*|r1HoL;hkYk z*hv2F{-x~qmnh0NP6Y28I#B;Eny^DrLce|r8Gb{SrHSLPT)7gtA2kYgg|(pbvp(@( z6}4Cze8Af@zeDZnS$2QD;z3dUceNuo3*1K^gH30I-N-3fIP_&bO}ZzK z;I+?QvyG>JJ&y`B};`V{FQdfXL%ElkV z0rq7giRZrnKur1U#vt!zX`DT(q7v3=svoz?G8%(HwxE3ibuzDA5pE$ zyQnoS8HX+$2V7G+nx2zJ7c{)0*A|A;<9kAI@uGzwbEu4;Yjy%<+Emb61>-2%(ah7b zO=xFsGz1S5cGf{A>`uHkwk;XT*{$xv7NOCs*kUKvSv(VO7Ix9IJ(sfynF24(SB@A6 znXA2P@8Pc;wfY z*fh$YNG=lO88;9 z1;Zst=6LfKUZJ{~sNX9FJN&wOK2AunLAG>;RE?l?X zP6A7XYxyq#-}&z}(-ZiJujdoetuMvw<94Cum#%zn_ffp#u8^@@asgM5uHj^xzQH?? zXBYf+@qwr$>^q5L{G7=+E2;#fLJR2exS?cg-EpEZNs@)od)UJ&9G9LShQC|6!P{3u zS?W|-VqP|i^aozWQ$EWJ?EL`rFKj!y&>qM77G8l93SUvHtQ)C$BSpqp-+)eyC~Q2v zm8dS+K*oGr#5^||(Ras8AuZUP3mW1_rY2;fc0J)8ur(J-7o@`bD#T{QnGsd}`w)97 zAFo9QKE`TT#_?~w4B2=#+)ioX0J zn-DS(kjECd1+(PTtE?<(A(<@vJY7o(8#wS16h`Wjn;$RY8y`~e(&2iryk5m#{*Ed! zoplwgC-HP2TFagdO@zYB(+SLxA^P7F*x0{m#3K-qggglm)n2lN+B8t~&^m|eso$4HXneg-o0tVmGZC2~ah4GEr|K@Oe1 zjOSlIMfN2zq6CYvTb46z+R{dTHW%PwM_X|2(f{D@DqW)G`ieB4l_&qTD3RaQy`*KL z3lYzYAuA94#z<@k0kcpqfibuy5lYLZ59X ztA5Ray{U?1#sYh?W1=%F{i9BPIgTc(F@rR7eF}+d3MW#FGKpsDJD9UcmV_S+hVa)r ziP60P7B*zHc*dq>aFlp}hQ@zHU$GZl6W0;5N?Z0+Vj=fdV;S-D{SG@`eB@H$a`Nf~{qES-hvcOYc=>Cozz9sN-Zmxx|R|PJFg9AK1<4gu0rht{> zGFaD5!8qBDNqJ|H?H70R6SB6DtYccxF={;7x@`y>QgaV2pPCE$cRlEv%$02C>lH^Nwc%OmU1U}OROdu%jdI8$BUs} zBOON0slqEBiI}ul$Zw^})8j%{eWK&=%0A4 z=@fYQ*MVd%87_XYp05_2c|z9;9lG87*OPvehxoFA5eZd|!IRV<_LJ8q9=r;oV` z?+Xg-i*S8q=ekh+9xd`PYY6MwV@kLbVRq#d2|Z*sJ}#C7l1jM ztu#Ki(i5+`HJc2(QjW);Q6RIc9tiHeu}J4hE1s}Sho+pvoJKw&F z%iuQls|&);@;%sil^yvtQ3>l#e9gIZ*^z?kChF_qhX0lg#YpB1K5Hn(20IQDnblRI zukEYIfG|@np0SVou{lhhj*ut0!h2SYfj!CD@)di>I*?%t74UGK%cy8o1HQlRH@D?e zD_%Wt3Ym?xpm86nq44Nh&^Yv*v!uy*FfLSV@F5M1mOO!dMQ-%K_*_)K{Ux38{R}op zN`(o|C(!%rkEzuCpVl(AT+F6z$ zCw~I(*kB0H9Fkzl$y@wz6T#=ABnv^4rJx{R#P5`Grq+8Xl-@1Ihx2~Z1tW6NIaNtE z^}tRn?&jdOgBMK`tC8gjdg$tEU$)cq6Q1RK2-XdV(dXe4*?L_wvg3!7uwy%d$4sb% zODnIztbJ2)*q$U@Daqrer5gnIiy2!Rk^}lWAMmxF5u`gK5xl2efczt}P^X@N3r{YC z2q{Zau}g<8`?(Yw$i}cqmVV@mZwDUpax(tu$FcUq4$Ltvp0qBoBRdW>3cKY6Y~?s9 za#3LXX2lF=Za2=OyCwDb@=L-V$^-ha(V8f`Nm5(O>FAXIRhqr=J;>}XfW*^z8?6*l%zS=0R5C2JKD@Co8_4hUHVAVU@0zz)c+w2@4b8WZ@u} zf2#z{rz+#W(PrcjJV#^BTm-eruh9`7fvIzH2734cQAgR4q@rvl=_)Q`@_ik+L73H@ ziF`|xU1K5mkTc7i8Aq~JJ(!$IFZict6OZB|n4LQeF5Xf>{UwoLvZa=OmfeK&l?rIv zlPJ9Y=vcPd_5!(h;VJCh`It$Z4#(vY$>c!hQ<(qP9b7WDlj9E=seE)1o)Ll1dVD1F zeN+k;`wx=79ec2_mlZl>N=(C|oUPtZXDFaJo` z$Gy7FG@Rw_9lva1O_#p2-W1EQ#63$e6KQo`^Y&LZ32$;p^g7y82q_4~s zp|R81+q;+Wv|oWNG1SBUbc1(I#y(HzZj&UMTGnK)ko%J~TT2|xpD=}aEimStkUbsY z$;|Ym>{rQ6q^9;`N%RnZcE$8IFZOc7+pcNCwDn`zpF8)-li)V^yJR)in%s*=yvSv> zb457p_eZ?jyp}Ah`brzFA7(*!YU#%<56Ff69zs9+Ig@&Kfv6eJ!u69<$k00mL{a%X z_vOnKR<-pwX4mH6nODo0)umKIKQATceByy9NfWa9DNER+M;=_$AUBmJ5O3)^x?5{E zd+gtb^Rp^2-pa%Gzuq`eLc3=7;t*2e8A{$?Rb%&*`|;BggZPHgWAL181m-2C?AF6T z*7DYXtT~rL&q@U2Hw6WxExMKY7VIK9*E7LEUqmJx+y)~zH8CgawYW&i3^upr6Mmmm z&8EVOP$KLtM{Qb9%;ZLJMUP8i{Y42X{X86#%Goe5REix=mLTbAe;_1oAKs_Amc-X5 zgHu2nD=^GQFHRPNb7hMFsO90(_RVZ$OFWsOp9LBA!@>XV7_!v=H6%GJv!!|dqvRO0b^R2cu^n`N{4a=o??JXy?1jeNio{a#I*hN2CP!zv z;|bGCSJ*N`vz7a(t}sJdbk~IyF3Tj9$`QivD;8e)=CdEi+Jw%y2^-x} zLd;c$u=^Puc(g5MPGJmJyAFctz9t;e^${7bFQYAs!>W8u6B1K7jYieFlF%G&lFoueul)SO6i!h%22Wf;M`&!`v?wdpO|($ecyXr zQ>2dbtuI1S!FV)yb_%P!caF#kvj)@kH_@~UJK#uCp0Zw-@xn=*DKQbUs z>c%`kiTyl#H{BhI?rtM%6)NCR^b+#hya<Hf_}+{vos3#LGv z5B2-MA-`KE@wnAP$eH6mVMyP2toE}Cs(kL?{Lk{BG1CB#UZ?}R>R%)Cy{2q-OedD> zae?RaT5;ij2chNhG_>{WRoWaSaOUpl;$S67@qw{LG)Sz5rKUN;l?%sUT=oa7b736I z+?tBf7e(xvVnwH>{Np579z`AKF?}+!l=e-lN4=+V!KOJK&)=iL_UnDZHs|VK!op(F z#FGg)y>~XOPej=3*A!GM@r|B$e}KlwVf;~VA&mR+#@5u)RqXNC9DDrig2CcatfV)d zee2D|RTi!Aao+=6dtxRMeK3GL9}SWmeFY{=yNzRZ6@d>^#ipHp^v^XLAqOa&O&bdY z?m#$xX0;yzgU&%pg(~Uu*#u`pf|1j(pT|s!9d&XeN6VbQ({Sc0&=$(ro^&9dO2B zDkCFWK~?<-65ZD#N~>;iakDPKl@ps_esm}tkW*twa|tP>UC=OS&B6k`v2BPXY0g{( zoq@N|P7+CGIF^9F)MY$5C5zi1C<}${+C*AUm>rIOK$?T>VW2~vT`QVEEdQ=0;?W=Q z%C_?)r#u+n+%=PId8cZhgc`A~;s*LrHHbJyIg=gzvE&KfKwLK%6Tg2gIOIqaZb;W4 z3bJ>}@Z53agi143_ckO=u@>a)BR67+4q(Hkg?MM13fpQv9JIz?<&XV5OTP;<*YK!H zVt>+#?W}QwRi1q`^sp=I7*HV&u1c(?Vj0^uZ3~OMO<84^HZwlC4HuDiRAYUDy^^$| z%iGJ@;Es*V(=VJ|i+qXScdus+)PU`;G$G%nX0d)8!Fm+e(j65K*u}k9(Z&v4$Z)d3 z;cl0}a<@9n|D?%XcFTr^pU1FJ-(1m-i`%H{?zdDcFP(@=x6rDWHSl$t2g@I!fwv+< zeDL2h9B^+mJuP2@EAD#`L!+UvOJGo1WgaH?O4p-ZS&AfO!wA+Oav|EU?&7e(S+unD z7~cJPFZNqHTC~qV8mDZn$921xldT8i;e?Q-yicZ*kFBM+fY-xkh5dcnsWOst`v9?i zI-4|PP9%DX8f=k&1w~l_gqNF{8jF~lybdxZ0Z0%H>)w+dNmlqJ5IyItpvWBeQ zQBRotHeShSCc&*Qu%q=&a?__0zwLO5dqnYM)$jpY))qs)OZLJI8bD@e7~-wFtV!&a zm+1HKP;$ZN7VJH*PXa7e*ce;DEBhmfr@;R?eX|qxJo0Dj-XFrF&tAY`Ka^NgRXMiF zc}A6L+JqecFSz+wlRf{^0NQJ>zya+WY_+~EPWcdmBQMuN@ZboxCMFTzwvs2wEw*f& zLKzSXIo4`#%KDX5xPmrW=92FWi61%Ua%w1Bd&dbr){J2ebUJd{RSK>fT)}N|B^VcO zWk+|Nfw|v;m|FR4s@y`rdEs>0sQe7>r$?~m2ENoxRtxmkT*6yW3q;KAA($uz%c-+?{1NVEt zaDE5P(20dY?aiQb`v4StJ5PsehOkDLjc_Jv7juYLV!M+Qp)zVIIc>58KULFV%Rv{# z)ENj}yhp(GpTpa?_o4T~xq3vp0v>ndEodfbu}{h;*}4{Qw!iiavxbvU`}`ocb{i)C z6+@U}q%*xc#Esm!{~Md$bR>Fp+QjUXH@;zPOP^K`iOM0 zH(Y|W`w6}7oqCOu$2DB5}Q< zfc^~Bp$((v5<~A8lDH-k&(qMuPj;74`(J&?wf#NbmUM-tYm{L1rI*myb8-9{-z8MO zWCglgeF{t4Sdy5Ljac^ZcJki42x-J7kWk$>T;@t8tT!rxBCG3I#(Wr6S6WVVvZmqi z)JzC+C<6Of8T@T@F*|dzkRHt_VTpcfOygN86PJWSZ$vB${H4m}Zkf&0hUL)S_2+SB zo2tlkBEw@neTeM_AMz$tmYhs|jMJrm;9+rzWb>@k_k@2kHi@-cgwVi!ChVzZAX9vL2hzzPY?QQQRV~Ye zyoNRo>Q`j#yWHu}V0&o3@B?ez83K|$1*qx#9h|-)0%hFlqhoWTaPi!EV0Nejmi|>h zQWCMi-Pp)uk17hIxARZ??9gU`S)FvZ0)Ea7!B-+9p{D3GTChX#CR;1vHH8OZq5mF! z{j^k2tKSZ@{uP4PuSS{}1K>6znMxmj1Z$TafyL5iA+d`5j;g;ER`cCdGedTwC|GP+$ zZkQ2+CR|;ItmKtJQEvqPIJX~b?MZ;udQZ?@31uAET#F?XMA-Goar!>=8m`(}jpN@O zLY+HmaMPw?IC_i(N&c9MeU{!qhmRSMWex&=OJMbGo>GbLgxBMumk)9Hw_i9wrWyyS z3GS>cInwr3me@Xdhj$ot;7hY&FqAJMwGXVY!j%ccry>E*qB>;L#`}1PkUPwXX~ADC zRY-iwb=)Ah0|y1Brp@8kI1UHl5q2*z{gI9>^snLP5`t-!=fbx$>;yCSgOTUjuXXnQX`c1NLslVwNN=$L?Me z_@UQyS-eRoYYdve!U}{8xAtnL-Y|(de2ryK^z@mDeKu=&=*H}Y9Nv$1VW+;rH9Qd^ySL%%*N2dhQ38YM)hoR8bv~bH z^$+Vh7vmQfCL$N3_ef{oCA8t-O{6vUFjAN?1k`sBu%5jYv7HZL-gpIkW$hE_A2I;W z!ZC2z&3EAW>;e4qj)E_#8muG56AsBauu=Nq;Cxk+wLRdNpW9V1RWV|Vg#Q;=m*v<* zy$Nj8L=zSvC&8{Bp3eIJ*1_A&J6UbXYgpSI$rRJ%Sh#RcJp8W!hQ;kE zRY{(040;OR`lhhFj+IPutrGKVoXLI`jbhD5W02Mz)78Pp6Odyr| zT1v80tEE}|tn)&jC>Cs19fp5=FR$|T2|Vq5DX^OMLdcav^zg@Ek;ykbko1kB3zih( zZ#yb!NM|g%s(%Jg)v(873~P~c?k6lUV*o$B?28A4{`?kyOeST&#F=}tu!hV6;-r|4 zYj4{SC0$i=Wt|3b88eTtwHLAXN=Gt8WeS;hYazKd?;^gRnLz57s1QR3e?nFXe$t?c zq^LfIT#*?=sxL+m{g7p3yGWUsnB)-myE91roUtVL(^4WMyOH>o+mUPc!bn`h6v3+! zL7D{S(3bN@$v9PYveJGoiRwq>@d0xpr>#hI6-~&Y?rG$v{&=$TRy|(!$A+ZVjvxd7 z^6*Ik;nYiFn#*>V)M?*JF=gKj^R9`uzFd zLm_hYXfXDSMQ5b7nUdURUMnIJtS6*`?E_7ge>M(2y_IFoCiUPIKZ-d9UW4eesVpM3 zAO3{TVz0g0;85!vwn|f%ZGW+pY28}LsFOWQ@6%;wxe+Ysy&fys8^-)BXR+Yu&1}aS z5gT`6osiq~VuPjY*em00ta{cK_P0ojJ)E$C9rh7C*`ov5Av*`=QRc!WYq0J5-4q-CU6PdwM2bLae%ABwslZ;ko1NUY##{@~X;I0h|_2>qjiR~a;WX=qK zUWM8n4?#52l+BWOO652Az<&l=@chYZ7+7%};;uRhY>->vf5!-h_?6NF&P~u4G91l? zFVr;C6CGC9hc&%rqGomgTm5R~mnll&dp;Ynp~WEHu6GjM56DFCUWl>Yt!x}fMEGRk zBpiC_1`h7Ig->jZwl z&MdIqvl+daq5*4SPvObB|3s@U2+QBy3S?wl3Etf&Mc(l{k$bc$(=OQ#x8k=lgP9`s zZ|H9bR{a6$g6E~D<0J}gXr*6eA7R6!daPuPu*{weEVthWKY7`K{Wg2DYx<$k=0AyX zXU*B>=xJ=D^K|Cvxt7(R2qrezp|zD`f}a zkfI5j`s^V*C+6(J*fdtNV;ZZUT*zt+6sf(BF3Vf@0Tj+{!xf7$m^lo=^X%i%Gr8vR`%mp4v>J!n)az9FA({|+sN-<_5@oVq1FvXtW)2CE%`hsc#RG* z;$u*=`|PNi-!~O%V5eOT_gk$du*st4S({4De&gS4lf6RCPVpx;%;f@;{9(y%luu*b zee>|SkCOO_%NrbO*o1Ee7LYH|fw;%>A}Q<@It=?q(bNH9_PzfYm9|u5|2?Y&OHF+^ z95$4dTg1TV^l8lJ_9PfI2|;DND)a6;43U;a;2N0-!xjl10pZ=~o6ky+e7uw;$P8!q zSCvqebx)x;zzo$V#KVjxM>=Lq9F&CEz}a$Fc72&Pwv6Zi#ZiY5J(U43$H(F=OaDNA zax3k7r2?x`FVKmy(KJS!h%GO+fbo^t0)y-^EV?ldWrieyxn3cP?bBf&$1=SC%SupH zi$Yt6NkaD|2HPcvil29x!Jvm#h1Z) z$s@WgJ`o0uj9~5;b!aZmhlsngVEH&z&RuadRY)npo3xj(Wt-0;nMqc#rSu4{TQVDz z{kqYyZEN`-_Qp8pMijkb`vfg()j_*PWI@{FY=QNbiH-4QQ0}^jzl#3xty6E|$vTqk zyuBQmW|@n}O9}t( z$Z6doY^q=hDPJ97t`oukSd=SR{T5(*(WCu$0KQA5%yHbie=FMG|cfa#YmK7q5!?4f@s(bRh(fK zk0(@L6kIV9WP^J!9rkw%uA3?3aoS^Wx5G=Z*5r-c^A01BbNz@H&E5r`+V^qAj|NV{;e2{)W1vO-p21x|MDr8 zf89Xwc~jV)=0r_4e?X%8qx@L93wgg3I2OMcUb_7UtUG)jORhQuY3hzB(d!TQT0Q~q z*j5Q6o(-b&#lfO=nMaV?VRa}}UkDL?=D7TcC5U!tqdC{(U{%`y7w~ro%-S#t8{XVa ztvAWwsLyef8uB9BGty9H_?lL_jKi;Q)}fh_5~A+zsZc3zjo&!5QnM~2Y~kZbyLUWA zf5IE-5sN7}Zeor|MR3bGeln!Gvyy1=mr^_B$xh&qm;h6v>}W++GVPXsL8Wq2sr(98 z-ty~mdOsx@jM94O!z&JOc<=gZzp4wcYqd1Yng19an>hurdp_iGf%L*}IWBSOR^aaX z@`~DaP|;sZo!lJwykGI)|0R}M8)@UIGk<|a=6QU>#~zelEXUn7PH?y*AKA{-;b&}W zMS8BI@amD~+@o3Lyj#*k{P4IfT-$p}=&GlIWzylQo>kZJ>HOdHJ7gp7xHqm2Z$)Q| z%b<$?BAj)qk=OTp>by{aM#Txck!~yG@;aZMdY6p#N1cLoZc2zfdxYZUics)6Eg@r| z0Xa|z1s7HEVYNt{rm4&gG(|(Y*#z8C-%O?5OOd?RXb?Fzp-bO~VUb)3JnpW;GvjVk zjX8gj(wS#8XjT{=`|&W0%~d6fwrzsC=Xu;+{S?%?XFu+9vxA=Y^LTl29__pBi|>qk z&s|*>jc?x~d|l^xJh5sYtriP;!IUGoJ3bu0FB{@HM?{oQVs>$%wp|4-skY zpryYA_hAo|rfH6;T_RrGa*{3kdYCSyHMo1iCy3Cj#2VA)lLybYfh2qX3+S4!sf)StbOnR)%rM{d@^>!gHxBYEt+Rwi(MRv@>-3> zLMFQ@%9_4d`3;Vlr9pp*EqkD_g}8rpA>WL0A-!Lh@tq+o=f^t|v3@csIeQ3Szn#pc z`lPc}8o7`s8%cJpJB8osFC^c+jYze^8@P6J2^;a%7MxYy(DCs~L}KzcIIBE{Al>({ zb9^WAx;z9ITIawu)024n*>qA_cmuvq*v4AEB!Xd78$3U4NCxFELC2*8yfZBwWeio1%|pw+he>k`7NGUol2OyBP)9=Pxc>CW&b`|F~gM2m}{&gj$0S9 zX{mbb@`_TjU{DpLt~=xXnHRut`UvJ*EI7$tEhbC$EP-c2dmRg`(sNOI;99OjVm|)^ zgLV&cZ|iMr%r`LeooC32b$R4lrv@9akgRch-b2ovi-q*$(>Nztoy1k;VX5OXH8EW! zobvF$LUu6#xw?yqg<%LwsX5D(gC*JUq62K_F)3oSxPdrFw;+e;+r;8g9hQjM#(IXT zuun2Y$YU4B(%RS1ng8~am6vi^Z}VxWrE$z9=Q-+Kv6S<0TL3F%CGk4Jp=GghHjAmR z!U}>1sdM&Zfj6bX%qQ<=yu}r^$x;fR&Ilz{w=b};j6L*#k)-{DrTR>GKESU##l)&K zhv|-}gKL8+%vUR(D4(8z<)&Px`(QHr=wd@GZ!UziqAASj$pR8JrxLP^YRg&$?wy*_TRk*iL z#*x3$Nu=VbFMI2Cgsq=+5w4vO5&Ca2Yg?L1ZiSRHRkMD)D(DG5l$=4tn?J(VKS#lf zk0U`x$FZvugYg3lO=BJSkCHl!5{Yr(L+vX>AOrWX3qmG84ypCEYE?cs0Q-~ z{({R+RTi)<2fl_Qa&UShJUMikP3^4*#J@nN*DEQ!RnL`u!#&^|`-mj;=CF4jo#1%bkzJ|q zV3#6fv3avAJ9_>gReCyMq=Huvb$}l8p zBsu-|9SWrx8wg2+3K;m!|DyZHaS;|F~RueHByb z&K?SetBGz+6Bu5X!>UX5NYz;b@~-X^Rh*}ax~7dMG21`EJo8$ZB1(lfpQn-Y{jT^< zwiP+5cNXOOYVq0mSpuI(n%&?}@~!!4B%u8EBKGyiN$5-0%_=kGzX+RL0>Cr;3 zEIW*Be5c{7D1aUDG9e$AZo-)k5$OGqMAGLHN;>P}h(iAqSnsIDJSK;+@qr!e&aFGB zpmPK~SZ9P)JtWEP>{#$E&7czV10nmb35oh~k2b%$N8gLO@ul;=xV%q^TsdRL9w;fZ z=8tCJsO5;)ZLJcR8$L!newk$qq8i=HGVWII93If6l|^GiHNM&2!FH zaC7=P7U2CpCqWg?;r6aFm?dk08uk}s$A4`&;A{)@{*XtTW%uKlWw)^6#bWr>CJCu) z4VlulxgKt8Rj2d}#w+jy1#G&2ywV`URG+vtFM1Pr&pr1CI<88)y^pmJg*IJ?@1Be&MkAF5{8Fb%LK{x0eP5n0n@twmH`A$E1-YzDW zs{Re&cUN8EW(3LMNzOrRjGIbL#SMQP{y3KpIPHQ}vQll|c1m;8-EZ*4+jgOta&P)w zUJLo!nd7O8H`WaDv-z^}99(2Ikw%+}xVYv++*XAQtU4r$Uv8}i;Ya2nMLUC=oy5#Trj~|9%XUDSO8sJNA@++J$+#%oa6G zI?H`&TFy<}U~gy9tY1?bRzfZEyZL{eSF!!|N3ftypW4n*pq3+d(?_j4_z(O1_)f!4 z+LCI3rJqisXOj=Guo_qDdQgH&CJe#9&gj!QVfr-2{}!#1)yMV5W9S!A2DO>nLR&8_ z;a8Q$BUOiSwBNY}AJ1ALULkh^9Td8JPPVZ~%iT zpcBE*oV<)CmLKCcA{Q{iuQ>J7W^}gac=Yb^H?;7o8-3Fwyu;J)@$CdvIxyOn z&h%EKwt^pK)0-Jk%o)(D``h{9vd?(sIsKw?OAB;J<_9vJaEUj6euSRbH=HiWTgZ>< zEXNnDBEyNh=*UN>a8}?= zUaD)4sKNZbI9+8O?X}*AKAPO(ua5A-KEI9WtC@se(b|N@SY*?)pCYKql&9$ULsi_H zxU|Om%oI_pT#V>LQXHS_tcjQC-$RR6>)^7z2kFt;r=qvRE5rj|DtOcH&Y};L;8jjC z^yl42Xw{xmczzsXU1OZ+ym6Y~741R8>@&Et$4~G(BG>cw1vhBJ*bPXbe<(eq7ltnN z?ZKniA2ipg8%;QI#b#z`4jxgcO!qX11;EN?eBDZ;=4h8Sx9aj*F$tMPZ){fOLNxlO zIxk)>(_8N1?$|I=m`*gcIk5X*M7T%}{^s&b~lm1kDZp57a zgDP;Gy^CvD8OOcK%SHMnOU0V1)9t8wtT^+4BD%C|GAH3LK_7(6+5Jd<$mhmt(UtNR zqLsIIO>^t9;I;2eU|UwVSI7T;EY`e#Llhl-liOJt$?;P>#PZYnL`gI1L}s7d#nrVz z-0D{=`2MH1^t@LyOwscbPkyWnv&MB*_jvbk-l3SUw7*q7{N@-gLN!@*e60-Ed8UZF zyY-eh@jq*NZs4V;ck8X1440$gGdu6_r4vI%BE<_0Pn6j7V3gR`3;wAYib8ii>kJN7sYyp)Woe?AR;$4g!6hB=iwJ7%Afp9tXLAk zTRFcHuU#Q8>YY$fy|R898}y11Rgh*bUCXzo@lp$4Q@54scV4nf)c#p@P2ZN=@f}1) zd%udjj1%~$=e~)Zr7=7G!pCmkjH{yfQ`5x$77n7EYi zhdvaKQI8g7lpGb^jS3WXJ)!i)o_;%>TZ{PulQcf$fEi7494Z=r$)?&VyIo{ykj}hH ze~KE;zTwvuZ|5f#@4-c<4e6qzIwFzrHg4my4!fSW>Ou$?2@K9 zBsEFo7{AyyIP|vYc31<{Oi|=lNXCg8wZb_Y6CF;z&yS0GTq_P%zA0)OJ6hxs1=Ut@ zGPEu5xhSK$ho;0D*rrr$5!*czS1&r}!5s)rpbu}ZjWP|w!n%ZI_3OYQl|-@BJC z8ecPn5Bov6v1eaWFZpCTI7J?AFSHd^Jsrd6j-5b-a=NJ0w2arxi5GXRc`cr9k}GOj zkSBKZljQ==N>#(SRovAJ?)=de60{|7o#@n?AEHimC(ht_iRf;aIWN&LSe0z2#m%~A zWEVfaMSS%4Fw~Kie$fiKZKzcIgy`(>daGPux7Q*BmrR?AB5h7ZZCxo7sZyR(ja~R6+b;9p#)zPv0bJ6D`VO*8XG<3vi zEdEbhntOUOa%8|+skpeZ&-xoaLq|wyOZ*K4-|fyQ@%tJsGS3_B z^KIhiBq`$WSC(*%!`rxq-?zEN1ySPN?Xl#~#13w=s;h_UF09$z)Nkb=T1s2;l6AdkMYl6Tu^%h_j0v9`W9W!MYWl5e!l3<2~1L z*y0p7asESY;_DkCF06?E9Non2e>NUBrhn%gp0#qx^HTU<_oC3{SPAZk$_#9vb)575 zU;(R~BKS)hqflN<6qn=hfwP(LhI>C!UA$!Za70i0RdvQ3fteA`$l~)I?#K^ytTFr= z=YD9eSTykw4xV2rmeSI|ICUB@b&HDHn|-(<-W2T0eJg{9=@6^H^+DeI2xL%(UCwT9msrA54X$Z}-o$Dt7oIov7v6wb9OgkQNNoBMbniaW*w7nX@R+5F*1?qV_b z$U_17-ucYc7(Yb44|zMAvLp`4z2XLQ3%UPt&yy{M_573WA?WEagx)WfMX7oY$bDNM z=f89qUNEVP>w5N{YyCSCbvikr0Kp6CVt0oTb3Y#%lu!&bL%S}@KzG1>19iKxc}$@ z|HX8nAW}UE8?z(qi@;Zy#Cn#A`0Kkdoun241k$7Y;LN6a=$V zn(gY(WBlzy1pT3CP3tRnRS!=cg#tgh(y;k|`CY?WDe-RPC#~{?+aKrA*K`W#6z+ze znB(HefW5T2uZw;d_{)BsvZU9CrqP^xr?~Y4&(Vo%S^W3h2>KMdIkSHn)Jrj%rn@Ua z(A$G}hs9@lwCEbkjZ3Dlj;#TodLz`gS`}x`q}0b|0*wn*L)$ZYklZA1TD|i%>d%f9 zFPh|lH_w;CjYdz{(7bj!1N3EmfQHgSvh2 zf|!lVMFqxnRL<)Z>OB4fihk!&z4Z&(+pm-8Y85ql_`fM|JR}#v>BU@`ryV~l?j&{F z8phD1*)+rP8(UbWi&p$@==$E!1a+8}+`~KTP&(4k#e^4*uFSIG3 zj`fYgGq#XTQ*Yy?tCmrR4LVfsoQn8a-ks`TziRQxT2<;EmW!AC9Z!=U9p=jSo;Fg5dH8en{c+q@5+X!s5oI^T=brcdSdmdiM$ypFkW@;c zfg*ck6fz=;mYMk6a}Je;hIXQqM0lk_Mcz^^y5Ui zlD3?((RxzhL9vPNKHjUn0>Ax7@urKzoaduc4DFZ(1HkXu(TnTFf`1x8(qSkunPXw!KD{Zw)0M63e(kfdgs&HkES!>KUthEzIKQo}&0( zEk4I#@Y%!xE+j7-o%9XHmJ|MVP ziSN5J*h4!D&$N9mX#XCD1&>eQr?z9sG6?~(k6%Vj)AC7W`4#H+_b{G1Wj1(-?`N&E z4fyV$BwRLKgbT(FkO#?Ha3f@d{c~zRZuO`}X}&e+td)l_;zBPs^kTN)kDoV5y*{Qn zD0~-g4s|BhAtA8xO%|h7b{-Ab%!f@hqncT~g zK&M9AAgwv~h?=D-dR==K#45#D?e1d&!AnEtsrF9b2IrEq<Bk!_qFz)8Iq$(o}-`%QA_gy=H1Vej-Z@UhWMbQQ%l$k@kc;>U6 zvIEZ5?`pc6=Z~+>m@oWxCJU|1smEH9lW1P^VmR(0OG_7(<6{rRNr*oswC5ZS%j4Op zhw}Jt>kM+lVnwqq&%{1erwswG78A$&`egBzsm$3w2H1jIL@IptL6ZF&6jNG>!>v}6 zSg%?F2FlBKw{W+~~gm`Tn|Y{fdKJ@9~30V%ea%<8eN_?${E$%%c6 zhsABEV?ZOBb}?VHw7r^Aj**fe6FU}DUGK-Jb9pYgRB#th9$EyA z;>$qwMibZJb^yH20{I@X2gjW`Pe$>alI&I^Vtw|B;N=So?4s#JI)-}~=1HZcYEB@8Tr>?3yUg(N&hY%}z26|!;tk;K+64`YXN6tinCjEb?gnwKxZ^qskm{T}L)sd1^K zufKup`Kd$ZIUf{#Su+iNS<3&0Z2Pf-Ek@D!JMLd|RFwYN9!f8-=KN~pk&a^_r}gf> zXu|qGR>x-R<8_JoWY+U>JQu%`T7P-UH0J*moeCUJCJ*Xyf1Ivzlg3}76CQP=s}9o5 zZbt`6@%tlOc99O}=$Y0i!NfEdhs$$EI(@jF)%{eK^=}@xU5A>XT+YvG9{pLW+q6qz z9@n|~AvIjGj2qjroEsf*or`jwK~;JrMZeNNh}vwbIivli-1ZwP%`d|wA>CFQk2N37 z34IcqA3gFGd48nKgVlB-g`p0Com7bEbx1W=ZZw*Ubp9-|56)M!*Ja1JzTO9%RQz;q zX7(tqf1*9mQisUzZN0SA|sl*p?G|tkpIppvb zM)yS>RdkAC9QJ#1`n_@Vv+fqr0rT)?v&etk=B@Hv`0j0u$z~0lX;n0%qiZN?bCBVX zLRxd-@HtV`_j^`LqO0j)sa`4-Dut_Vo#U2@s<}^}k8n-1ZMgLgbMW_M27L@UF3RuN z+MHRtkz0DTKr}Be8J%fr5Sc#H;whZK~zc1IhLj(F-)R0pBc-lmt z@pf8MQ+q zU;Z4`EX?MjhmyFlb@yqH+%ra}C!X`n>=Wq)>~7pJIExg|+{U?jw37$79XK&xX)fnO zAXnq%#~IpAL{Ijuvi9ylIj7pyC z<4xrr@RAa9HI5mjj8m@3(&eWbnDh6A+@B}=8U{Z_a{2t(aA*1j?(Or1XnwsOl=v9H zCD_*Ux4E4)3C4VojQTRETJFk~pLuTE?z8z6H%UyNpzhxFM6cu zy3V=*OInARGw&)H-Js1)S$XyBtY{Il-w-oLjlEb!UmNzWk0D!OUd`B4?*ZmtkCkn_ z3vxXp$Ntd!#+@m!Mxh3&d{+JdnLDSWcjs@wSBG$v5SNXEOYg$BwdOFe@) z*B+B)$7kq6$!8x_Ub-9d65e3<>Hf&gm@;LP%u%r~Fz zR(}()_A&RF@A<7TDX$RwDepuY=S{#w6wWXwlkwd2xvYTimd1Cw)7p)>jMbTE4BWhh zQjNQ~Ga9+xszKE}>-9yh-4)cA>#W>V;fRR#L z0ES&fg63sq%bf-!%PcKjA^nBCNGmk-3pmr3!U#A9bwpvWds9~i0 zw+e9&ni%1YRBlQ#|Nllm0re^Pu!l9Pk^j`W@VQji%Hq42(Ba5_#_wk*BOe(lm~_C6 zX*z9140DoUOjnI)y`2j6a>zuco`EQ%7m0|U6S`;Efz%Zj;csK4@kr!LQ}_(AGe6;vbQn1+?h$*v1LqgyqP7T=N;+Vl!=ph_$Aas3E>Hpv?79rECm zVJA1`!!XiI`oiQJ4~kkN3Q%e6WOq{}QA9Rg1`0p#bb=h-aDtm-IbNWbdL z^GSIx>Gf##v{p39joJ@|Z;D9ErS%Y_`VXDW(-vCY-T`}*!lCNo38>;bMH^=FC4G~_ za97hC%)3~~bqZl$T+oHYeKX zH-y!@#)FG{I$XYb1m+~Hg04F%%@ZDt7H)bJ%h+fDG%53p(m+4BF*O?OySKttyaNo; zMri3yg}c?t;2ipqR2+|lGjny=wg2&5vF#pEvDg=s=C5F92`?dZVICg%CjUVHp4h_8SJ~{_Ln^RQ5DLcU zZ?S)>wt-TvgK%Sz61%|V2N8yEfYgW6;f94YXsBAkGM*(oUcwcgy8DBL#}+tuQ3lec z&H}R_YZ%kI1>SVsf$VBy$c`|D@E_~Qtq~{M8{|dyJQRcfo&bCZUJqBl&HpZRy>b+lf0JxKmt26p1>$QTg7z8&+yiSTpND-$5YIRZio&Ee~S zAr)ksz`b^D`2A!V>~^*Rd)KSTF=7Gwx=<5bf6akc4Hl3V?+C~y6j>JlO#Gk$t=n~B z-YYfuzI=wz(=ioxPtAs5mldG@c`n3#J^^t&C%^KS16q>e1?1j#=yKWsx9{aacySS| zou&wzB&uLejt5-cVGQ52mctLTTJX9sj7Dn4!|$*>@E@xo++P$1?tes(ofr;Yau#6T ze;K`>9|IT4Vxi{d0`Tv10#jik{9B*}FOua*hSW50D+9Q#(F>X`(a`;TJ@oZ$wv4!J z$g}%&;Gdl{$bNH#SEH8m%pn(W-F_E#eL4tl56MFM1#PG~yp(;|(FBXL@59k~mN3yg zmbt&!kL(p2Vo!+AfP@GAmU?2#=!RfRE|pJQjz09jklunH|LPK@T6E#(u_9D9VHauS zXZO%J0|QX1gY=eFS83O}%{(7gHj z&^hkW>QQ)Y;a55c)%1Mxd0I6_#LsuzVL(NjbarXs>s|J=GA9Kqi@m`g51VqbuRC}a zTX)mC8F$IfTZ#BK?Qo2Q7+Li17^m%=hM7O+WSc}V`gFP-jQE^oapXS5MMOxq@!^q*1n_DJY%LQ zfjQgp=$M&|+K>78)`ii;Y46S^pP+Z#uhr9=_t`t(uQm@!1Nut0-aAMg-?flgZFac9 z=eWRJe?HR>W;iZ18pRg&60hL7ZmV&UuPL-BpY=|XnXE`KfRz%=75_sv!dx1Ax= zwyqMCxH4o&`5^XsU&u)gx8sA7=W#gSUjOpTkbfR|IAnuAJ}3CarKtOHb2{Yk>*yNJ z)oc`V?-+v*ZQsIimM(O~MqTbg&Nb}#U ztB-S$Z`^o4+8@qi-YTMTI-7jA6{kU?F5rEuS0nqX5_ZetSh6+Vlkxx5hS4Eo(YCv7 z#JMqvtaaE}rK-pb`?z-a$F&+_Qf)DanAgij9!*UOBe7{UU0wRb_p`E+E-6_4N6n&5Wwx6bf43 z$XyK{hn|j`jScqj@3N&lD@$FInIh>=+VkeJ?=nZ2?WG9584aUB$7xXPcnj41%TVF- zzhq%?B8Xb=kkgwvr1td}<2XgLdH0^1NRU`S{i2%ClAF^|=u0Cs*QOT*4^yz_W&3Xt>-RRlJ|U-2M9y)!dtiw`k6S`BN^S zhT(N&`kCuoiR&%q%1>oTRa${Oo&`2}nyp2-kuh-S%3^MsRxh)JGT`BSiCcYb85ny% zKr{M`g%5pJqpI^k%=4s`u<1h)N}a6-krga5xLJ+#y!X+pX$z36csG7lIvV7*-w|GS zLS*5@ChoGh3o={s5*^F>3eOwFg>^Gb7%zQScFz?vCc``oEn1yLH_qnJOQ{!vUp@mU zO2oizPMzNV<3QAB8;c&zsId6z zaESY0qlZ#*veC6w^{CkWGFols4K2ktkk}VTMCXl0!JRTFkQt9Y9rmDmRBs6)YGyL; zEn<=Bl13&l{~Nrp9}8C^F;dT-g3eCx6ucQC( z@@Owc-+3u^);=NZ5$WOKsrMLvzXROx!B1SDQUt1Tk!CLRajfs3P_qdRi%g{SJ; z(X%>4jt7Y0+g$;yif$P^o~FUhS{sWxBF3WUxhXI#eUH(4>c;(j(8_dqDIk@V4@3(N zF2?R|RU(J0ggZa)Ki0XskulJ`CLquJ(P3+46eyo0s5Wk39q~V|b&G?rJ$xOsJUY(| zE%im--9Ca{$K~N@eFAgjp_5?Ck63QHbtHU^{{W@3E0~^FKJ2hXjzHtf1a#ZG7on{s zT*1K_M%FtG`8wVN&F(224zR_~CP?CRGebsmYb`pPCC_{t=@T?1>LbtPds(C>-K3KK zkvXZK$xL3IU|BC{5j;$CXZCIEVJfN*Goxm|Wj#nm{x^r zfVPG(cCue!--Iwo+08Mr%Yb=(FhgNke9f#5i20IEwuh=Pj7;9YJcF1%f5+ zyBYSs*mxr+M%FqN zEs#G0i067qjeRDPw@9&!T(#9|q}&80pFS#zdwPS(o%)F>bnO=$FyJ8S`3FApHerTS zQ;@=^v-?b68;Y0*7FozXxb9Bojem=aUud) zUD0y?IvlLqhiCNCl>QN-UqqSF%ASg% z@@%0>?>JX9a+~w&%H|Bu&1W_{bP&Dm$C-$w^=M!DZ6;aokYHo*NzvlndW^K53)!tT z3C)V1Pjn?p7^k&a+{rj69G;>gY7^5%JvR}yf0mA2y|%Izc9+?)UMeWq^Ez{|^)SDe zqDguW+A*sAHv~~KwM>U&hoF4aT%4Y*g_krZDpvqu3*dol9MrIT1xT;p1+UyD}9wT<{>W(Y+|NCYGdisv)O4hFH4g zhv3MRa%TAODiT_B3jz;rMD6Z&_;Fn?mylI~c1vcU=uQNepN_@%omRoEs9osV-Z9AU zNF!nkIh;p7BTi6;u3MVZxU^G9CG{ZESXPCzgEjEfy@jN&ej_zFrH+i93%L- z8zYg`hukk1qvCW|w6a3W`dQF67{@9=>}?iI)Wsk&l%Uk144N&i0DEetamGvM6B@7s zPBytfPK`Y7N;rWICOu*<(1X@D9R6~>@`GGh)d>2t!HWG^c^Tz3+n|UoR~hapC)oR% z?}&(TXo}ue6f4aqK?!YwL%XjqNAj)VlU6c#6v?s@1CQw|{WbX4-`7Yxz#F;vNP*(| z2{1{vrAhf|9r~Jg3kgFox*9VY$|qkT+H0)gMAe5T_4S8P{)36=S8fU#+7S=4KZ=1U z#0klr*MOW;6RBA8KeVuUH~MhyJW`sPhm^fcK&tl$$}fG3#`Lh(z zyLLKNx^x~*>NIDr#mQQ?S06(r8=kNohsO{r`A#mmXe^9dc9i)~S|1th{ER}9C0VtP zv!E)V8Oe3>eN~^+D77IM^*@^pkKZMrX9-)`CN>V;`u-ScMOrWyU*+H?B`Gp+=py>W zjOS*Vj)7zY4KP@h!*Eik5KolCnZg+1r1dgPYk?R#G$aY{98%Hny>P_*yNCQMlhI;_ z*(klaiQfrdh%TL+3&lhYQs%tj%x0W|`7% zOoid2r_s^RzRb9YMLf$i8oJDvpeIr%P~qhOl=OZP>`CL@du9yO+N*I9D?f0~szg+E zET7Y|P+`4ay+PG_qhWW|3Ou&G6Uo$`!ewKMP>)a(d+WWUA=wE6KORSQ zmp0?7>4(v9RS-I~PK(@#{)o$tjo|9+MO@O=yL`l21Y1H0z6p1~Z2L;Q;fbZ@KPG*mSC$AJ?(#$aAkUs_PzwNy5Z2PlfSgqy+H=ER{XnQ7n zj2qA1c{D+2INucp2izg?+DZ7Ne2#THF%K#}>;?J#`s~q3GlZW}5nD_yLD0i%?3DEl z@O^eM4DVSW{CGAALN_FUY9H@|p5M&=XRQr8%AP{I%M);EyaU$Cql6(RwOIL!xllaD z5Pp|g3;9tC^WsS^_I*h(gP$K5-!l^ijynr>VhF^s1197W%vh6WhFi~5RJ@#J)%;arePeYSIU(u2c zpUwAkkOn)j_6oe3`wzyE5oGOO4-3oX!07GsP1P$KKA6 z`C=RxO53o}o3KhU2Js!DmJnu z2h7WxVOil4kkIC!ZgLZ%@)APL9&uPUTY~k|xGNmf{)TPQNP>jh7VuA6ohDiuvxSE= z`99rwsMSt~P5<_=A9%)Y&R;E7Rb#ZU`auR;b>}2WTv`LSCzXIle=yX|Ok_)91VrVt zz0h*xEfl3W!0j`<$E`IRns+T^PRH89L9@d!-ys;BfpNH$|`6Yu<_QahVNFYmflLa(yAwb=M z6`&vdn7Q?8kb61!nVn`3jDk;G0xf$#WbNk0?BjV_r#haHQ#&1S+3{zvd&mpDYKdd7 zGB2R~RT})|vwCd3pUh3Kfvp_2gMrPbq3GgQRI2|F zMd$qFly2~M={z4Wkbc9iF8hhbv=yQ~Ch7cc&PKNC!gSX9U^`Agx+mYHzqCxHSN5A+|O zdu#e$K_^}efkmJ%Rk#p@?Eia&Osgj2$;1wI>A&Z;PFENGy&lCouJ}yfpbB2r?O>EN z$D(cxbGmf;R&MQ^{Se*o5B=Cu3*~oTV7l4_KP=CLf(~C2e_|aR+LsJ!64%+#eh!pQ z@r<-t2cX@zjtd`QAf<2?89JW}r!Ogi@a1UY=5rk~<^5?~PXNRZ7!tp{2k4HPIusu` z3geScK)IM1TYTgIjB@B;6w_n)xx{-b=F)nyb+R*l*|LPG=_-cfH*#Rc`Uv>eIGxU# z&gU4rcf%K#5RhzI3TLjSvL$&Rot0&yig>U)9|vRvWom3o+DmQCk9&jim?**s^U z6n+YNU~H@aKl(8P%>MY{#O;aDa{|K*&r~#;zx(k;e-L;F;Y)`9A>H;8f}_QlLMDh^xTV{H6JAF zEcVb72XpBXelKWsu>u`yxu43ptf4x_A4&b35xV!09u1paN>{|l(xcJ`sO$Y@)a61Z z-C;aTMj9qlmj(0b#&`E=;ahEbW?nqqmM2ehHl0nNBUC+7fhJlkr90aK=)pI;sK$S@=;|#8s*J z4_!K%n@A@N3FxW)ku=XXl-@h3LBmTWsjlcRsd~49PAWc24;vKGKYxqpqCqQa+q#PW zi!Y@UnIlwn^(opNP(sxM=2Dz?h<;k_PQxBs)1bC*M0eFPDr5MCJS{e(^guSPsdb~Z z-Fj5SvjzUl&$rv?;~D7X|NZ)-v5nEy|G|^c&B>lfb5U{9-GoyhlOHdQn=gicq*h!@t}>5h)Mhm2)U;oLmip{gNQNsRVMuICOnN8vFm5#glx*MoKHNGee76lS2<# zi4_u{=ZDd8xA$mL%RyEx&p;SeXbSuP^|O8#%Fwnz1vnSISs3uW5Uo4?5~c6SMO&VG z!wj1g;pNy8D4DkvdB#e!a$yE6c_j_;adU;O#amG5-gLG-dKmo>e}ux-PNM1cFVTwx zO`*y1Mx?uz?;g3ygA0Gg41KT-RLeRcHZzQAS@al<8*D(6Jm8J@(p?; zK9PMbeS@F1ykRwG7NUwJB`Eq%J^T@vLDb2O@OYIYtD`|#zxpiU+2=Gk&A> zK|9zuo?ow6C(F)qyocD^>QKGt8G3Krj@qUxpr!GLg$}O8NOF!Cn=~s;xPPTkSnDbV z-y#j5vU0KT5rfgV;ti-z<34(_vjQ1txr1xsI+&<4o3*meXS32W*w@1#)S7PzLzf=| zdr=L7cN>6a<}_hf5F{F*vQ&n%x0#6*)c3Y z4f?@?EmkE0l(&Q&Wy4pp}; zLCPUgI0OO|b|31s%GJz^@fPBG_2V$7mBC8FXb%?wG%XT%+wpm5(Pq-nhaU7e|EovGG| z%dP>Le^i10&HQBa#Z#DZ|J5>*GK!1Ttd~X&$)E70^lRAH`AF%ISteueU^h4=sY9ZPB)iI^itUXuVdrjG!@i09 z34%}CVPQ@lOcIX={q6n8ZJ6iMj#6YTpC}5kr7m09>H=mn_5$0q51d}h38(Gx0e_x> zh07E1jn|#5+w{4@`w|b(2!t3_GxK1uWIi1bHc2P>_}uUQ;%Ks_0d$ zZ0Th9a&H@49@WD86DP3YvK4TB#b#LVCMmSkzm0CD{e}G6Y2ftW0w^qx1?066ZqN`o z+IR)|y!Hi;KyTRiz6NwQsIbQy1uW`LWKX;ku<%(0Mt69zC;i^>odQkxz04VM?KIh| zdl$kkjhmoz?+g5x`I}wynRnxezeWZ+^PnU9Ff9C33fBUku?Ark(0)x^Xs;dvDJNxw z@(XJq(R?MyduqYb6+7Yb>0nT9mu0OpD`COI(O@^+&W_>ldY#eg@Z4LSU4K0bRv1{Z zrP{fyO5P{n6rh|T;&R@q*p=QR4q0*It#M~}!P2Y(h@IBS>I%JJ{Bb>ycO8LF*=Yc-A5oY=67M!0!)6ufz&ZI9FiznQJe#Wp zDu=|_$r%elG+~PHZLkrH|NRmit{nz512tCM>L5J)p}-!D34j|;LUwhE9hgjQhT&p{ z9Ut+X^;LYr#`c@Q?w>v|tMCTMjb6bP28sz^r7s3+c+M^?(uOtlap2jhC@iaRhOLnS z@ZS0$j5(hQ=SN*Rx8;RzYD}Pi?qpVF`v$naHWnuS zw+tMzGeH}kLkyqeS_Wjp)A>^#MR=!@mxu=N4FwZ8(uYBZ`gj zHs^9|`t~&TxP)8f&q7-4p5*pNmHdjA&i$b^Lo^CAKQFp)=MMlA&3bad-4Ith;C&rOO5Q)#mqD z=J$9U-=jn>yLn?-zh$_|_9&)05?Cm^9V=ZDV7);nI=k==F*A{Eeq7{Cg6FHyeQ#s% zm05`1UX+g|U&do}<{+MQeF9lmm5s0UvP8BtkK8$B3rf0g@bXjDGOj6cQ)B$+JQ?Wa`8BP0y}(}f=>pSkii*hRQFveHkzeJ zlLsPc@R2lpZ&D>$Ze&Q*!%E4~B21)R#}lIhQ`kINohr6UG~Wp-!P%2|#$`hZbqm>r z4-L(ro3|wst;%FvKRX%Ed^VA|1elT#%_Lk=tA-zYgcG|}(Rkpx6}fz;pL&FuHTS0$ z(fc$VuU0#WhoKB_);xq`IEr7qK1+VSzl~>b)94%jUT*ud?YP$0gGBYk<7Ky#aEtF2 z+*c4vmPPb&7t;BDKwBOW3)_ziNADzezaPP?uMN=b`dsYw+mZN4n-kX!%gGUMSz>7C zN`};uam1TR^n2A&{1RU$zwK_2RZ3#bGH`@?FWimar5wV)^Uq^Rj|p@pWYbY!R^Yv= zuc_+r5}cD5f`6csAa*Gp@A*}RFO`O4i#T=SP^aJAn!kuV7Tbi69^;vHeZ@p3)*Js! zbEg>&R`|2^b~0Dv2=;jyhb>=OV)fId*tZ~-mc`^?Jyl&|#T>y)75-pf-b&@B=s+yi zmSM|+ay+;<3zwhUiSIp6r8|#J$E*JaaVMGuN#Xqm-f*7uW{Jqy)O=N+KOkk+u+>&-^n{vg`IQN=vP-STs{zsT`yJ=jh@^1 zyS_A0*<3@$y;P@9TQ73c_FSPGRYUPXDQ8@AH;ufq+s=DhjL22R9DKel1y6Zej@QHi z{_;8<4=*%tR_=4b6VHy|*zXk`8=olQ-_j zn2W#iPP#Vnc|`5+9k{vnFUi;2Kt_(Gk%`jwJh^Tb88>Slc@*=5w1+kk@9RhLChf80 znZA%*DXweo7qpOz8LxmWh{RoXwxs>NA-S~UEC22{B%7qP*@fzfq@&TC%~&`AIiH$N zWL9_Me=V})@CpT@@_G-kj#g()x0w>#ReHkGp!Fnd(3W(^5}G~v0J(Or3~zidBiv}b zfM=D)H!B4%q5B&?i%g!ZB4e~?5xnRU)JxAHxBvP!*ZXhgrjL>!lRc71P_+bEWgjls zwBMI3l5!-0?uVN5I!(y&$*m;JUWOj64`Cb9)M4xCIvm1tVBEA9kSpOg@#=GpD42hy zn$zpC*$p+}w>P{`{x{#p7+sDvu{>#dJe53%&n0s18}Q|cu4I;dD)}!ej8Vy4Ku$Hr z;&tzK5o3Sr=8i^x5=et^%ZD%gbNEWeo6C{g6=O-xQXx6x-GIgCSdc~+Q*wCUWt@9S zw)wKO2hCaKO?1@qNWqowT!7tXSR?O4bj3o+t=8-G=*LB*Wq3VN{`ai;N5gp>)HbDQ=Y3Yu>q1le8TxCxsvOa=&s%@w~!% zlB7R}NJuPat4xI?VNw7|vr{7e2ck&dCW5zQ+K~MUtB7~Q6E5)6QgS@pg}BM+k|#B5 z$-yIzB&AWGMEAYrKDCW)?(X))K8NdZ*jf+rbx9eiUN)1y`R+}uS8Rfi4}9-;z9P9k zHW9nr5D~QyS5nlyfryQY<&ssC$n9myXyl4FX^GDvZjW?`&h$F`N97Lr#MV>Y=CRaw zU=3Mij)~4w8&X=*3MymcNb&bn^1i;9_>H;46=@X{C7wqurK{e25`qIG%U{6LQ$qFS*g8}ydM=nQj{kX)ln0OK~pSok)4dKUsRBpie*H21T8>TK}Y^AdN~ zx$u+f>lu_530jU0GP@O#jhgWtmmiM5s|d4X4$@XVRz2XDp@bK{4gqGpnr+rha4=L|7O_b)Sh-DBqE^5@L;4HZnK zVi%(w;0SCk_>s@WF}p_X$+fJR zjQ-8%%-WT2nGt>|(_cBr@W*EWr6zTzb@u@0>+>S-2OLQ3IRS92-;{LJKV)V-A7D1@ z?IepMM1XqL?DEheb>?BqW#D4a#qHj|X5MU#1CarRjAvI7zEHoJnV%1u969gn$6{R z>Y1{Y=}dQ4A#*ez@ZY@|;rhal7;-y@iT?J0S@Po_;}=-X1l;0k=8w&wZR{y?8k*0Id>WRl^u*$=|g7at6hN1N@Qk;2?B*>5U>lsFlPoHF*(npNE(;X z)coGUY}nGnIM0k_5`>+IXWv4Q`13t;sR}VL_AO&e>KR>+SpLFK!_0{pdw^_O7qheT z0W*H=HIrIFn0GC~Wa`jk=I>L^1^%;u$?5E826k|{#Us5QO78}pQN)OG1jBbCwhOrPy6{?4mo zjGxC(=IYoN=5S00W2jcZ{5w>|jN69+jj2}Vz|R-V`4A~^w!4jq!7)r_=Sr}?TZNXC zmM9lN07lhgk8`@<#%?1QyK-%-^;F?!)u zPlESKl<$F{5%GQlv!KnDr*}@U7vJPS^5Y9{ zjzR@F)T)EmeppA(T5H1^%{@hvb0*L&(2(b}(3JgovVxaXuMdx@6_kbPjZxOF)5@0! zDX{eD16Z3D!Md!eVGqpS&t|qxW3(23Da~5bj0~h?V5!d*vM0HP9o+SWn+|B*eNqN)a);+_!4Ti2mc&VKgT@mc6}=`(ghY7upz?Go-ddx5uK^aaju zHGn&6OzG~b$1FAVgI$wvOwSoPz@6JtFGY8-jS1#f3c^oV zr^Ry2SnwFTJT<@UWV9$$vtJ68-!!ouw!YMtl1uE$RjIJh*qaptEcw??HW-PszYm76&f-4o zyiK`fI+h0Hrr{QbfS1J2#O%*jQO&Xr^5QRPgiENzM zSJucu35`jb*tbEP`YpI+E?8UrM>z1^(vgh7%FlYG@Rx0fb`ziekeUi_x zq0M4&_L7$@*{%maYi}lR>+bSCX+ictgBI_2!A$mLdVZe|NMN-!thl(ThU4?DP} zjrCBiVQ-4upf*OWDXZ%Aqi0DBu&V3cvCj^^Vs*e)Xs$cPDk2?v@_0Vgw#|%)?rg?0 z?VWg=vs0|!COoAKxJ>av+ZLYbyjyI8%w6j>ytxnvX0pRuD$9}3SO?P8 zn}EluF0j5sfN$|;AAja!5&n$nLVRWKT5vD+6IdUW0G5akfl#kUz=NA#{Ac+N^i94+ zvxBD?;l16!F0Ks}a`}Rv4PNDUqC3Er>q9`-MvN~v{UM2#hy(AkyTGP^cHp_Z1aK)x z(7UK=m7z zjIQ_rp7jj^-t=KGWB4UlQd$L~Q+fcl73KTx_yP_^8=sdWl`$e9Aep)bHTo+_E+@ds!csqlLi zHvoZ}&%ndj1- z8g+tA_uqj9J@q73jL#p<9{?-3oK|GeD<8R>UqaVPBa}o`Bz6Lo(7s>6^L6Cao z8Q@nd@zri7f|PX>KT)Lu9QgDSgzE_LYaCvKdeL_9zDJaw|7ZY6ss@qCH&4OOp(aq3 zUJu;P74iNAv3q+fL02y7Cpw^&+yH^x~UDt*Iy!HW%@SB0tEaVB7cyaB2M;;BileFMeE_|M2Kbkh{AMy!qV- zC`N+6R^l!A=1>XVuhQlpEdB#_Nc4dIK_x;t_khAZV*JU7*C1YBi2pgR1{_v?4)*SU z2Hta7N;@49{->2)K%-(9%-h5{nc^yd=f5XF#i0f0*!2LapbOa4i12s6?FK&MKL9J) z12oY)aO?7OAl=*y%Iyb$pKl}3m}mk^s$0Q^cw_##UtdA7cOwuH`T(-mjRV7xVPtOA z42JPjAiTL5>}%--mnNz~P+vXhNgDy{6Fb0)v2G9*)(3X)?grvQU%t~0}G@2fRsip(7E3M z-i5sYyG=&H=BI<;WRx&p>*HIHaIpt;eiG#`Q>_38)!qTSnEPO*mkeKOa0pZky$3JM zo`R%{A$V}_3vg%RKa@ST5<7+#!EmQsl=ovVy43R>mRTNy(+Z!%G_Hs2bLBbIc;Ajp z%wyTn3rSGyY5}{VMhC^EezM*;l*?%qm5}_%V-z-jm~&V^ux^#mLaQ$vMg7@sv|>v& zyjLg*U;R8yR~fBlB;{hTMuRkZckDM4>w?gSWGnPZLXVLz%7UvWAyjxgk8RpV&~MLF zxF$@6`lFl49*|c@X8q1gx5G&!sG*KP#)c162`HB&52z595khcP>OI53Jbr0mhK#YfBbWx+0hhM_s=!x zB@&G6GiULtIQP?ytv&33Ul?VqbQ;=vO+c%{OUO0F5BenKz?2m($ZK{tv^GD$TP-{j z`THs%f$df7eyao|TXG5R__~Xc%@joML~p|36e|?C;VAp+{82RInt*Cv38DNI52(=8 z8u-K662WvO~ghgo^5y)$T#?Bj&N3IF-Igtm=2X%NYbw;Ri zr7-&Y2(eDq`Rqf-Zg`Gw3ah1`!ncQbaP}h)O7l6_qP8Y5&_NLm9(TevgDvosULP{F z%Y&gMH85KFGn9+cfeCGgP-uWL`jp3_(Z6+YtUHyrLg*qAEV_zH?d4IlpduEX_Zmvx z%H#UJk#yLYG`jO|HO%L%l4yejw$VR8Jvi+F9hQ4jZIlaShN_|JT`#0N)JqHA=A)LV zU__~Q(&1(AtqwdJh5YVbUP;wu`bu6C%8j1~H%40_Z}~`QonlIB^(LYa-)eYdzBkH}2qyoA|6tpHzBhVX9lt$1N-HM##|D~k8s zg}-jqWFF**VU12PtXsk0k+=FJewhPK{P-TL6t@6;796om+X}qDVi*axnc!T`vD`d63Ehq)r061pGu79CT^k;w zx`l_ae;k7)Eh6w5j^h_1_W`8;cjWNF z=_5=5RK&%`n#|g>`J8j)0jhO=iR4Wzk^Z!`xXPj$x&KqaGX5-_KOFp zei4js&eI`h$NEsVX(b#8w8rHcF=(mkCTyy|AJ2Yw7C$q&!K^C(h~%A(u)CKkJRGft zdwANokXeZGVqEa*wn-%WHUzDy^TWHMb?`62i#Rz`3BTNZi=55egb#B)&Pey=Snpjk zGTXNRyC?&$^I9MhITsJBAKMh2SO07eH>34Zf3Xj6Xa$jf-wz#F&7{>8GUlDPed7S8+VhHJdHVV#fx z)FeGb2dd4(iz}tEhWs?Vvp@~c+S`Jk+I!$>BR%M1gA~qJvgI}3j=L=IzVKpD$Y}CiGoI38fpfHhCu1ALTvvI42End1s z7^j{W#9mg*@L})iU@F%T*J>+St=|^UOcxr((+UMJx-5xnQ$CU%ieacIc@CB`U4U;` z?ZO2?mvPOaO;}k-4S)MpjTLm)VXY69*x>0pTxc_fKfBuDJ-i-h()bA$*jPt`gZ|6G zq}M;h??;UMiwAgJjM0TZq#!FAVbAe{60=F~0+JBqh*o*f>r);b4r z9z=svUv7ghpQXXcg{eUNi!@kJcnzHNJqOav&w)#^8DMW>Ecos(2h6zagI@eO@VVI- z?Eak%-UnR(?XAh+nEz5RBQ6f?Ij|jkTO0?%yz{~OP%ChT%O+$*Z3O$4C4+6d&Vvh% zDWKxiQSfrm4@~@v1|P$Nz^!|0!70o0pnhN)82OX}4!^y`x$AC$eSciJ-qQqgaLf?| zud@K1g-M`y{S07Gc@fMwu@b}@?*psMKQTJIHNbUm2JrG%0}~A|K*(wr?)fPc2%azk z0++b_CsYBMn!OB9M*yhKS`5C=4glurTHwX9b)Y5H5$yaC2MnX~z{E8}kTxO(9(rB@ zGWX+w+uC5T(q=hW*6#(bd!N%VZs> zR|o^_JwLE~x(j%oa|297h=C;WXpp#781NV5fWp!*Oy;`9oF{4n*k80AWSx)(k1J`= zogM|=@8+(xqVLSM_A}sQ>lI+MVlR+=5(cK4&I50gtKgZ+IHT1$7rmzf8T#K=Hv39g3kL->ePAJ2X90Cc>lfufuqL5oGV z$}Ez&o!PQL5qa4>;{6@Ik6-29LDANBM=J81TTpA^q&mtjUB{YBj0_M?)xU;+vsfiZA)px|HIZJEt-z+nv-nt3x?3P07 zCZ&+Dk}r(49D^fQNZIA7TTsAc72ChW7)d$pLyFRtc*VT8%!M1@G4AlfpGAJcnb}r& zxM~))W1bS66Rd{fhC|U0Rjvl!9nHQdNiQ`$97>|^=p*CueEi9SLCONpVb{G+P~No& z%k6m&)%D`=_W$on@2xGDymbqXzITV2b$_6b2ZpYjo|KixzT~y@O|Vh;IIEMRi5i7I zt%1MREJjX>qKLM;0fC$(c@t^`S?}p+&-E^>^DmNFwATQ= z-#Ew~ELTMqYi1z#xt2(3(vrG;<0Gs(Edq}ovcywItD*i!F(fzdKGZrUhI-va(4woV z2*#Eov8;b=%MoMr@pA{QyMd1`nFOFyax&=V&p2#*TL;xz>v8klYm}~MD)Kaxh4TOX z#z&))*pQXyiObs0P_xMoA71qp_FmN{`5u>XS;lKPxa$?HxMq#a?!+SnB^z9H@drH2 zoJP{mf5E7JN4U4pfqd^9gYM%;Q1^wmFm=NWx;k$GlN}NTWnz=zssvADu>1`AzH>d? z?fV!TuaQCt@nvxT)(g;Kz``0>2t$SLdN_Vg8HHIzz^czVkU@njwx+%ycU|Fm&ywDvInUJt~PBFN;+I83;bh>oFro=-BT>&f@MfiqQ~L0!)$c$c0C z`ddMx9W{37hm{q1GL?;6-4Ebxr`>4r;&Irl?}1M7_u<-MSNu0P5U#tv5Soh$qm*mz z=>3&HQ1@Cc-uYMnfsfXxpPs^hIKIXcQvq}u?%_F>bU;fxAyobD8}x}0M!C~CKkqg* zye#A{ZP~Dh>MR_?L7LNXZ>$J8AuWr_jx-bZZ?oY+Z4K;mIt;g+wS#9Xr=izRb8w~5 zU7Y1ohw45=6I)4bJc^g%efW-AIjYf4T%iAAx# zPUzpY^W^)l7ED2Jzu(q}t4^rkgB(*%TxSw_eOZmWHIs3)@S^gn{7XnZqneDE`{AYoG4%1| zB=Xd;8Sl&div4|y(XIcq$~9NaVY6>(Vo}9ZR6B$7HVS28t%)S`^7veQc~%FUF9opm zabX-&IE5ejE0O|FB~q+(o)q6RBPQDxAQ81x^1QYP564`f6VyMkX|hkSPSlCh)S(-oSTEEQs-=JmR%$CN3}cA$s!Z_={y+x!KBgtXdq2 zJE9}eygxUPW@88zvqi+>*bIE><9+fmGlG0P_5@iy&LaC$F&6iqL1YBplIrnt ztp9Hb`I%QsT(2mWPrA&;?$xvL4TncW%6JN$uv9HyBYuWhh(;nQVKdr#R2pyjkc)kX zHlcTqzr#z%XO@qYJVUWo=0tYmL*o8%A)_qSg7@C!;cGe~usi%7T6k6)M+ff2Q}?{d z(~4vqZ)a34)BGIWeSa8LoIQvW603N}rqj@teT5C~YLnphAG}oiVN`U^hMeF$%>`|y z#JoHgJ(YR_~B;%}abtsQ@%_?0gR5o$uR3inY;V+HE>kjBM3RPnhx zLD;G`kVLem!vu9hY#xw+>%zFq2FIXN-W`r}-zZW-(-lz4?Eg@L$RV_5K?XkiZzf_Y zxh%JS6)w#Z#QVDKh^qDgR;m@n(c4B*%!EBkz3z_%9L9O+!dJ-QWJh%SZyP+&!$Gtk zDPSx6D6-(qGu-oUG0E6|o$A@!0qaz|$WC)9q7+4NprHmiXq-=euM8(~>#9lkS`BhC zU?!fU*M~i(dPr442=Qs)&f`1#aM@iEOi9Jz{Y6)yt40}7l2<}8oHO;~lMiH}F1IVz zu?@#9HO3E$qnL)TCCJ308wHkm;JjQ7AEMHqE+^ z6<;|KDK|6H)SE?qH}55f@*GLtu8pK`x-O|5P9?d=mJ#ul>q+Ov2x1?cLN>Y{CK{{D z@d8?dlzcl!GBcye?6Q00!+|6Me4dj93cE<^_lu! zaBi2e>?ogTdL)y`BemG!Oad`EewI`*j zW-}nMM+Hd9_jq#J)S1K$n3568qhx9JGIEWrBVsXmWOeIuGG(-e>}-BO++}Ua+eK%{ zfxU%9`_5L9ha|`=xmcnfkVKXiY$8*l@5t8ABIL0M$KOkuN7f&aAy+Cai1xkn#Nm)X zaaSrNF56F#lV(PwUr3Ae-qj(o@0>~bt!o5-i6)hvE~Kj~lr)~wB&+6g`C?SMgZCOD?f2WXdR+^;j zdNG--*+Fh&3z9Z}J2_=`p6p!}LgX4Uh~(exXV)*O~*~5MQGqKA=+8~@r=4O(m zrir9GE`YpSW`ETH(q#RVKDkt&PIzYnh|fqcNtfl5yxeKTp=u0IYsw<^lRTnreTdl3 z(2?I~?DfOBlf=gxn}5R$9X3j87p^Nj*(0mWV*{^_k2{@nR}P zQjNX1Z8xv=R0kE~A%OmD%Ag+3;q$X^=Tm*}4q)}^BUGPj8|2$c`|MpJ+zC;U;6Sf0+6+rA`Y+gwX+3V(`?*Yy2hY z$(v|qc?WgcIg8@0ScQ%+Qf2%G)=|E)`vH76MIAhmMhS$7&{2ZfRGw58@1JuFH|_}VYH?EM@{W0;|uzx7kMrA&a~nYp~Die=QlBd;jEwJp?$Y6hix z7E>C7eN@CJC7@F;KnvE6QvcnQvFRBc#LO!L#Y0DE^O`}E9%{iM&9#CIh&{vdAjjPGt{*= zM7m`{U_v+uUR}SJe*Tf8XUaR#cQ&u5zHNSsK4~1J&+WL&my-#nq+S_OOD8u0YkmsV zayXuv{?(GbH)%xaPAJ%%l2C!{RXaL*qK#S?nMAp7RG~M_lK@J$x+#O5L+rHDRJt_7 zoPO6KO{b>{(SDD6X*;za)ZzzxI&j-gs$ly~Ub;#?+~4?w8r&&Li}PPm5sGcp)s8eO zF=7!D>ZeStpBkYCR*J#j-ZSYVMjeQz`BdZPdlXj8=WUKn;T@or!1b(h>fLJ{pH{unmL+I7jNd-w1!nOx`MJOdCOgRxbP9Ww^WwZ+opt~ z_C&%6!(Y6RtRHae^ZT&5<|%VA_92SfQUvJgejX@GvtGU-nKIb$5T<4yXGbMcfumy# z8lU+DY>K?eNc##ykwfvkpU{oEc3y(-XjKIl-98N~j;8}p)jB{Q+Q}GaJ%CL?T0ryj zxw0iL$rIRKM=`*asag^VB}b-E&dfPfVRAR54@TfSrLl1PAvf5w zKos7%{Rx=m&V~Wf@8O(R5$xWzL6kw!CN`*OC%my$1u83YzKar?CtYG_xg}knb_&60 zvC|uHP4+fUzWW*-3Uz~hlhbLP@elB5pFQ;D`NPvh09hEMaCPHfxYtw$xd^6#^q6M& zCM+Bj87znD+1sIPu`C)o;m4%fizBCB&%g@adT3D80Z#YLK-<-fq2!a{GO;6%c+Y3Q zg>!lw;E2{mxXLLRE-JbTj@H@IpYzv3(L>GjUZYoVJ|%)w9FCC|SaG*Y!VxQUS8){-r=Tv$%+DlAQpz z7uB#Uc41!B&Yhsg>H#<+|AK}!1n%HvK)>95@CjCjrSng*OC@>eQhO&)*su@?*UG`b zm7`_KzAMn%=t=hR&#P?EwLG{ra~hhFDFYV~%sL0j(S}}*aAt5gmupUhwHqs#J&V%d z`fPU;{WlhMB-^w1{qC0(c!i@Su`SRx_6!L6rwqOQve1znH<I@3e+Jc}!Z14~*xrQqd4>jvZK!2c7u|uc4xb12m1wv&bAV^vN!W8?YRK46h;OQQ z5dEs0!?x61g6Fweu6*4ds93!TH~ttY(^AVuv~~y^INcPwSz5r4gNV}nlZ%v|s=#}J zxn+M{tKi6vlW^Me+o;jq8x8rzTC*z`Lfu*;Xdtu;E_$yDR4>W%8Z*UVy7_wQ%k{(X zyGJ}Q{sYh|$9NcZ_a?k+stTVsvW#cgCAM^v7rpT3e7OEeGVGhkfo^R^P-KZbGCp~e zePGDts*h+xw;Ow5!bW-CmElm{naSzY&O0)wE~f{Y{-OXXuLp5Il#qAbao%2AZM6NN z0426=J_5NyY=Vm>eERDZoOUan>oX|9(5aK)fPpadu{K9H&|P5iD3xx~6XrF#cUb>; zr2^mt3tvrp$evibjeUHd^SAD`WmO^P@EhBSx+BgY5#!ge;3)OfZu-fY@TMHP=wU%4Jw#-WAioS{ir151z3am}xUKgNP(7?fF20hW!Un`~W7~Qv`&(QL=Q&vr<^m zIhVZCnxs|~EaI4gYP3O$DH1O|Nqa^s;5N4nbk(7)lxc-V`Btqs=tb2bJ!276{yYo4 zQ2j-nuqObGq=tOsfoSyH&^W-+B5kbxkt<4W7m_>iCF?z z^A%Tn)qO$Bg1%6*>uvB-X9ko1L`Xp94Xpm}J|%QfiR#eXPPcM%vc!}`tk6B1PIJDD z4@l(ko+uok3t!wVZ;3LdzO6Q-C0eZE+6fWVGHoW1+_4n<3T%ObVoLb2zXL9dn?>6y zixP{~M$~|T2d0nCfS&Q`uw;KdoJ{*d#oOlKngS!b#$^_j<)=!2mD-3{LlHb)CWm?9 zo$T%xuJox0QJOzz>m#}_=~8aHydBzf8bqkh{svalHl%y$JFrwLsZYE zQkl+0Fx`!h_cR6Il#z^bhvSc#SwD8770WL{$Ko~ksHfYo4K*V49#JU%4LGz!u!Sxe;a9-XCvZ}2U{s|Dlzm?V_J(WCk=BYVWe!daC ztSUv>50e?Kqs3@WgenRy0eL`0Zm z{1HVPi$uxdH7e*<&_+}eqefgMl3|64Kiae<9mRfpiH9D{h2kBB$mhZ|^iW+D-O4p5 z!wYN?b&f?EuCCnKa)GE>Y(?hL4Eo*ffK=K=kf~Dzlzzau*DC;ebHxIQN+h5iBSA=X zycx~fxdJ&an@#?f{K3v9j`+@h#wg%t1Ue|5$Xb}GB7gi1J}%dWKMiA1DKi0$nK)#h zbA!A*y{+_{QVA~G6p7x}DIww*1~*%`;1^pi!iaW7d~}5;lzCE5F878biy0PZgM1R& zV{!(aSfLE{#01fgIePfAH$d4+XNcQF8Qg!u0F78g;#Ysl;N1JEXiJVDxgy+Wy}`(z z^e=~~v}Ge+kEKx94r%o0{w?gpMxuorbLq$MMpSWkKN=6xKnr%bpfo2bbaBo&>^d2S zMt0rhdJ7lP?sLY-*{}ymp4`gyji*rbMjGV=?MAEJmLt)nocq1E9NqaKfHsM8e5sRx z#O782`s}>~6>+tU_18V9W@!l87>VHJI)LM{$9n4U#1zWjiTo<&oK)RS?8|4z)C zcMnMlZ9+EwJJFW$LUeD!7mF{rg%ThS&&WSXs(eJ+U9D5s#~X_0mWEUqrD2vEX^gAH@4tOfZ@y9t>{i^ zDVkgt0V8eqpkUY4=z)O>I{s%I?p|5}HHBxP@6jf>qLGJwf+9GY(Z*=7l|HyYl1F?^i-9#C{KbhFwIBT@0ivj$(TC zav8XPjSJD}mq3Cy^LhTSoam0h8T9;sYU%=;jw+@;A(PxJBK<^x_EdO6jrdB?J97J| z{-xKLn#p2n+HYej%36iYUL?Tu=dT9apPZp3&ppOYTDPnZf;6gaS37kgxshsaaEArk zt?8V9dYmuf8})Mg8npWCCc0<-dU}zgC^H?WLYsyuUJof|tFD!R!uzqj!Lr{}r?M`6 zqTim@mokHS(L!|Vi70B0WDQl>eVRVJ!G&^{*+O?NF`@tK*np)SjOc#pcX&+ct1lyu);YoW*j^y>L6ugmFsJYC80X#UoX@d`?P+Buh&Nbi%pC6H*1bP%V)6@BA5$Qz2Hl!}D%vqlvd}2snXAaW&bwSiB^`n&SZhKltU5?t=TLgqmW2p2w&6H?Y8!dQqGy86u zHYMr4lr9~YVa{K^ZY}6sN|{LeP^r!xRO2xTI!3>bnwQ0KOW)6>s--)jR`+je!?6c+ zU4jQy)Gmsi*qc&Yt?W6D+6#)@Z>0S_{gAJ%DKPdnpl{<&~Aj@;c#l^UqfACDdfGD^FsZ?$^V`(RbN_PI6ftf5TmL`vy5k7}r;<+PUKZ!5iw@r{7Hd21FTXmcsawY-+I0kv3N@F1tCtl6HP)&gh8Q(5-ECRL194%K7n5 z>W|a_y;JH4b?$66#n^;V&xhYoXXR_C_v_=CtSTdV*_}daV}TOg{_&0V{6HS+ZCy*( zl`C7@1Ycu4ldY*AV<+(9&o`+qFb4?suA#y_=hAumK2q@@pZd)$S*YtTq3o6b^jYQx z^<~)&UW?BaxWnueZPOi1i}tQU4TATP(xPJKgMt$YjGjS%nyy4Ut`(v0_dnx38_7da zFP>4A&N0-G&0IQrs~Vl2^^t1bcLFCF=J0r?N%Y;_&++g0PI`fyI<+XYidwVQ6tA=V zN6Fv&O0BsyhYsgl@3XdFq7SW=q1dl)A@86O{@cmY>zR0D2{;D17mGfp-YGM06{ZZE z1@Yv0Npz$&jCV8L0>e#5FtdCIEDhR&55{Dmi|g9q*?HHgvddTSRp%~_hk6!Y+-br0 zu?)kzuPEUCoaZl8HV;K?ISU;{HjuB2Phgk2e4P659teD~0hJgkU_CaEt`fKbtn`1O zSaJ$&?1}_kuo)ZJ6_El8qG<`|fYXCkdbFU0c73QwyfQA(+3LN}@8vuu#?uTB1k2zI z@B27unl}EhRUSJSJjQa{=i;-MI?&9YS7>#a`^4^>3DmcJgvGP9ut5JeG_JH7g*D`$ z!#ADTuyR-U{iy>!`Dqd|i!S5;tTo8iX&G3<(h6JT#N#7LiRhlwW~SuW5S^}IkM@im zK^L(gUU1L=cju^MorEY{dnk@@d;(}-@ESc95W+JD*CO{dsq|}62T<@q8`DbfaktM| zJiY%n5g)yUJigyVYwBMj-Ig`@o$hL6xqKrIEr=k0<4z&{?0Bro7bTPWF7T6X2I##Y zib|w}(LTumY*MU+dmis1r{!vK$s&8O<}HOYX5Z(@Z_{SiuXrE@D<%a}wgbs%Z@n1vEMyH{nUzAXTa4k6 zJSXhkP>cRuc#mXLexS_IwR{2ZA(ZYRi`#XQDC43y{7FO=4|v`{^;4VCDT!ZdtR$O)glE`|M4|Dl%~1hJ2! z9$LAo3iI7#XtkJqsHjsDd&erHihEaqVaG6P*xkw9|D%xl=sAiRIS5YIg`rccRmj6x zfhb2f9fj8>A=`niXhc^MU)uN!`ATiY{~5kV(JuxOhjOP@XUpN)t^|3DL}TUj7sxPT zJ}FrI6c7E<#p|2@!rIEG2Q5q+{w*2zS);C-Y2>d?Uv6JZIxln!o281XSA?y_+@z6 zMU`28=Odj5444LNPeQzV+|#h%fRk(i3*`$ZjsL88$r|?MyR9u|;w8;d|fVv=%w) z{fH9iI#xnGDX}g0Pg7C>GW%9DM+M{iJTXIE1$0rAXWcrqO?EzHb#&`n4Gm_xm`6=F)*P#oVA#LfN( z=v@_uXrY!JjOHB!cG*2gyu08jvmkFR+E!i1oH%ysN>}5*FY=WNu>ji?-jj0@+yJ&Vn1}dp^#~E50%p#ePfv}r`1f};kh8W|2(5U$T=E? zJJF9+LEOPtz;Buz_#b?t=wpqu8Q1-z@cT)Qw^Q{KUMsCe`T2h-HK&b?&nrPDargr0 z8-7Kf`*96lHC;o$8P#W&4cy0Zv^#1wf_UMZtF&zDZD#QF9pc5!S&NpT;I}$GOw^cQmUIU-MQ7p%|El=t@LFo^Pkq`d za16h-ETk=8m(x=!nRN0Uf}Ra*rqvsE@PDthp@W@opny6ll+l#N3~w_|=>W)lI!2#I7US-IH@t-7$#{_{TH4Zuv6O8k=29vA7%w3@Z8R5YeiTMC zT_IXwtIhCECNOJF+v)YQgz!YpOM3esZKl1y61!ZMV=NCMW^BoCvt3*FV#zt5pHQ_{D{6C9qunCsf#Wj)P8|pV{H?*v zle%`4{Q45nKP`q-%aTyyU@Cu=%o-FU_!7Ki#OUl@fDCgpzONaXjg?Q_!}EQlX@Sjbh8G1Wb0*6o_7!}TdIJkIUHgBN70$b#q{-Y zycHE%D3uT;sVIe-d(OSqi1yWzC?ZAH5YfJ`T10y)6_HX@_bgLMNraLmTiJe8h{zT_ z^ZY%3%)IWLd*{sgem?K_LAjUg-xsp@&c>@i>}CNwP5vC-Xp_O5S*S%13f4pUAsHN) z^BpdWaVPh6T%dfN24gz62fLU*!c+CtVR6Mb=o~Vc=q_%7vMv9x)UgmeA~7HL@Rsa9 z%SP5plt+xYR0DLg6Ij)Qr;+ z`m|!QxB3M1+m^;&IVxtRVp*{|0zbFyW=BR$!3skI z*qncz-L3r>UbhM(N6+79R!v%_>|_Nkdz|uE{}rucBip(Ap%5y;Ld7osK?_O=SjA%lRri z7|;!mYaL_%e0FD#Ev`nzvQqd~RS3V&kb`fI?!*u8NpcrjzhP6|JUF$~nA>T{)3WWk zIILco;t8wR?=2$l@0zK^>S6_M{gg}QYDTb^Wi5fp^Bfo?a!AaapU?l;Fou2b>JWQJ zOoct=-iP(Me6q7Tp6!WjUXng96%EPQFp8ncJdGO=ENM+LD{+xwZdVjDTUF}?RX;cLn<|*`v!+^#Eqw1{Pf#n7NjI zf?3=+fQH8s#epCuS*wUKEw4b~4}Gv&;&S{bGgENt2*VdB-DGlPc*gJcO8n^f9_H=# z!n)*~VP-+QK9=#D%=6monZljH{3mlG!Qq4#c&3?LU4i*iL7ZDmZSB5S_}8=xOs7v2 zUl(zSagwfO>_5ox7hGR3%F8<#T>ph>dvu)XWP%0Lk4XS{jxY`lHSq4ml{moAAC24p zk@;{-$OLWO!C1buVTSXsF=h*{*S_A~%rpyxi+9?M1rJ~N;wy>Y1Ss${`YPqdd`YyZ zy|%iL=~`(I9$z+Kl-sA_yf={1G+n}SiU#1ap*-_3#-8!}5z0*2JcqGpG-Jj@TJZn2 z$1>Tcny}YNKak(k$c!kT1H0D0Vw!8lG8g&p*xFmSE;ejByl_g$t1+AT)f+bO+q@E) z-IjSscKuH?{3f4F>CysgvTKFlJZEN3*crw>L;#E0y_ow-V&Jx# z2GV#R3Y}-9182Q(0h2IGpxqMAxSqCRw#zIA$y=t@{q3ygBl37$8d$^l*8VVy8Ww$u zLwU^B%{v%hO$#$nw4~PM%Q@z&bQCipWcULO`l29VEMvK;RZxCY8$T$yAkg2I&P?um z!mRJE;6J!X^EKil%;m5bW|DU%f=x2r%>G-6{IgrmOvUUvwAna@CzHg%ca8bX<-q*9 zQ(GAR%j*w(Q=b&LBk>EQn6-&=1P^PUiF_Ul;$sEl@6M_FP+Y>iGI}mpYTO9RGs@~b zes?nl`t8hM#cU=7+-537XOfGimNTPw1W0LLyqQ{xNNO;C4B%YD1V7t;gUXI8%yO8` zSca4dxK1C&V9iLKt>;F51((H;%BupElcvmqT@8%pwH0Vdb|_P|)|OdT_OHfr(*P6e zq{%w)F11IMq?zR7+nDE}nDOpW!~+R2OiFh%TA7@~xCZDmPaa#;Hr6~~ytgFtCmjly zhr+RF{eSIDz^-gYT-adt>}-kvzlDrK&LrkucN4Qgav?0+vI5RJl~&t7XEkG=)WUyI z+6vk-8NqqSgN%|bWim@-nKS!R8Pyw0nB}@+IFFeGgdOYo1tELTN!0=HTae5EBn^(j z0)awD9AhfXXAVrDOmNpFW>3%zHG32OUS1IH9~ z)?jR#U5B5v=)v%qWj zFLu=!2O!lZJvUNm*T~UnU%l^YfppF zrg6YA-2tsC4~9-3#DGCvC=f=4K|lMQ+`eF0D4UbU8h330cawL(%xyn_q;EGX6KxG2 z-A^E|kreRi?dBc_TmiQuelkBMzXzM{XETktE9*{lyTDqPZm95hfYEX|$_jp7f;d$g zDA(EPN;l`5_^&JoFSl9$5#K4cZuq)nnk=AG=`R;D5}MHy_|O z*#V}tRKzb=P6oQw4)B1298m@k`Bx+1)*_KdrXUa4 zOmbp8O|LO4-)k}lXOyw`>Huh}5rgV-MPMmN0~=Nk;HA?!cxRCuN*3$q8};15(B}yB z-f%To7eAMEj~Hgw$%weSt((9zHV@pLvjpE9zaF^iK7=6+!?iojgV4oS7eV8lQ~Z(S z&!FO$F37@AIp00O)}k51rtnOOpYN&ju$C zYzGBL-H1iBBoo+k1@7`FXT5Sy^M@bvV6R#W*ziu|%27yyUP)yj^VJvN?<)g;WyfIU zS(kwOs21~YqzoSaHiory-ii0e&4aPc{%qaHE5N`lpRF;twPd9L!TBH(9yub(Hco7kwSDU3VD-#Rby^%rb0a_JI-eUX8?VG}l!s`r;_HMf{e0 zaUM+95aru-@bi5KS^M4?;=VKpjQ*NKkMb+;g7*Jfp?Ho3AgxoWuQQeBxfd zvEWsmQl%(I`*SbT-F=63(Vm0~e?12=HN9Z$joaihRsh}>cflbjfh3OU((nV7{6xQC z@T|)h)zy3wNR#ZkNe8YXgF+*)@{ATpd>%{^+WOGs@EBgX*?<^5DP`1`eFT|BJo%_@ z4bIuOlRu*SUR7-z+k3qMwf&7^j+?hLi?frMGkfQ<=XR%o#rt0i3Tis}hL6`o-F{^> zU3vp*Q&a-)`$I{@+OK@o?Ga`=(B~Eg=;3M`DKf?>08}@6^4cD1fK?Df+kgK>n$lwE z06!5v{L@4J98Bh0*WY3E{+>k+oAY=BpZMC)D_JObd^#F>8!7o(a(h+i;! zCHnC|4f!rBfaNc$m?pUj@OJe{H2r5ia&5bX(g#GMo@4DG_3mNe2BY1ef42nseEtFY zF@*8&<>RqukR?r9Di_#fl~yCbEB@n0XvhumwS%X?p;?5sto z+$FyDS=E!e|1LVB-0K*O9W7%ng!{rd0q=m=%9}Mdk6ZXAPj&v>H94S?ewPt1y$1mI@QHkW-D2_W{O*^E zBG%q6X4TCMzAC4nu2k0zEfr0`=U9({C2wv4x4S?1Eho>Kns|#GJ7HgV#oqZOBd1z$ zuBW!{>B8-TB}pInLINcG=+(SZV%wu9TF0*R z-xm)$f7&uSs4B{&kISGoFT&{8Z2{E2dJ9dRYD+6aE~#^jy3%%%5ZOA zP)84i6jH&@KsxK-EPB}d0R3})9kqO+Mc0~6royv6)ICqsLD(rxSGYf;Hz%E@jh`}T z%dE-t!{|=hv>}a(6`!KvnT9mH`wKa6-;P$CGpC zw|03@y|Wu=+2aiAd)b%1bTOm{Zr&yP1Lsl3T$&~)votaY(52O;bXTJvO*^Da`wU-^ zF(3C*k3wfUQtL{k+$`v`JDF57Gl%L+PNY{SO`>99pU7B23iS^>O|3!_Xs=on_0bKW z=k_h3_ODOS|0Y_}ufyT=>5dKbrrt&>sMe;LpHk?qx5sH*d=i~A_X7R#Zv|~|dQA4L z6ZOR$okcT^rv$G^qk4wZX(JHzM_i?8+9pN1A=-et#YEC>r)pYm6-;GXZRwJPFlyc^ zP0ONfsCw^wde~NQcS)s>KnIm#^2p3Y`)Q8%u8qwd(N&5cG6Ow!S2%X#DPwTta zQ~PC$=$?2H&&kV*-WkxQfv#Z`=#3D$GyCa6N6~t$6i_|US~2z1r$=TUp$2G79N zwC3az>UnPq^^mfsuIIyPu3$TzGj%^rZ9YWJI-}^8v8mMjq#Ko58ck2+`OvS8LsegjSJK28<# zlIU>gcQT0@&?WvMwDP4H9eEW*Enmjd;pflDhWCf4>P>f=tsYGW_uJ8@8hh#5?g29H z&PuvS%8n*YNuYhdvT6LfKJt9sEUK*#Mbpql`td?6jbNNrJ6Q-LDhJIS=UV zQ+9OM972o(yr}EC#pG-A1R7hMMH;_2kQt_~O@}m{7=+lstmRBkr3jyS1>k+BG;$;kvPFpa(l3XzV-|tEm{Xjb)1>d z-s~G`PPP}CcRCOcR!7+6En-{9%qMqQOQL0TfDAlYNY4DXoun-f7LHx4LrBXNyG zYR^$5;tmIi zg=rDl9=Mwyrkz}zagu|CJTFO5Xn`_q0Rde zh^~$~-tt+OgsqGuenFrnNt$fP4Z#asib-h5G}`9Y z2k+<&l5q!S;EjbGJvwYF?EJHV+z&4#zvDUK7TqYq&q|^jBkqyp%V8v9;W-j^qJyka zyh4N{ab)Y0XmY+U56j$DBDJ&fh*8rsdjAih&U$yqVdHXgvvLR7FXu-NZ8}PZ$JmnB z2OpEY6JiPMvZg;3U5RGS9@0oB)0uezWH{Q9Sj|``dQFqI_s0{jRXa$@x(=fH(Sf*q zdqbRaw~#oS2;u&Pm&t~L9^w&thg^&7Cs(8Dh%zfqy%NrlM_T_VJbsw4Q?;pE=qT}g z`iSCXc9a=3CYslRiO!{9qPSj%`qUA+2P`9-_KOMS1xLuf=jr4}W}=AOY)1n6|KYc9 z%*asBUtG0WT^M~(oQ_NOBZx(s1|$ z@g<8%LHKy;?jyQx`%-D%_w_{Y(`IsQJH$OvAFiva<3I>7k>y z$gC&Vh{76{?mp5(J{PFc2ga+(FDXo@Y85&0=M(AqHHV0GOcTBucbo)Q%%oSxeWhwY zq==2=6MXguB>JKr-_PrXMCIQJGIsYe^1W#*w!I@x6Z(otqqzp1VXH$|vCHU~21%j9 z^iZPz+k;9UO(e6EqsUdUyY$iK$<$BpH#u>8K230t5`NlTiU)!dsC)et+?Fdvax<#1 zj=+TOopOg#hca50R6-vf-b3ZPQ}D={8q$zqM`ZS&!fwALsFB7uIeVIBn_tVPc z7@WU#tT5RtkN6sUV-=-*{7qZbnOlAh`-O>oZf7x#(=irK8rezS-8xCtPfQ?XvNF^< z=?4+7^%g$cJe4}G$e|fK`^jGoE$YzLN*;>VpRGhOR!N;oG_)m1`3g}_IBvbLDJ>fh zTv{sB8u>_%7M#J$xGS{xcMb9A?4VbMyy#hG7tQ8WgnO5Z4!1Mpsm7FxM0}fQp0VO0 zHIlR=DOY3Zi37&8!|fd&NE71C&p(l7A8Xn)<{@_f?u+-PD+(XVwc;aJM+yHn2JbZd zKtINPrRsDX+3ik*We2l-^#R<20v1Crh5vt*3MiZ`!emma$ zAf7(Q=`E8id?+i1eP&&XFFD?#2IDhC@9~4y9zQOezEhUyRL&=Fo=zeYHmC}f^sWep z_rJpV5&Z;4l;CA!-r&x)M#9S7*Dzbrga1r2Bo_U{gg-sNlJ!o8L~^4sU2dyM z8*Fya#VaP0ayx>DmpsGH+Y+&X+?`2XH~+hn0Z+zz~S8IN~P zh@y}=L23#lXhYg>T5{$xm8e}PyqW%;j(=lHcTAKe!y}LAy9a&r(6ka-`*A$+lYB|? zqh8=D+X8Ye?Jkx{vK8Jxnu@1drO~-##t98FCJ4tmtRm|ID)74rxkA@GMS7!P7v5?o z;%7{+!z)8d=(64k^fP%x{Y&psC5P8^{eX(_j$aMQ(2JnAF8{=PuJzNDyAfnn{||a` zXa$uWIYv%6oFrF7>+;C6F!JS(B(d*n!f7IhhwzLZXd&}_94Og=0^htbGaWc7`_!~PUeZ)z|ukmV) zTew%{IsSd#6JJvqOYM#0h;8Id>Q!S-gRFze*v>@!`J5E)_4tZ^ggn8|XZPUqE>7fW z`d*y=M1j~?XOQyaGSvR)O!D`aAAPj)1->;|#By)!!}$R>NPdqUxhDFr@~(X&ktn{% z1{D9r$K*r|#D#_U(E0l~%WNLD2n-@43nNMBn|qia+D$*L|4TA%+`*j_Yf1Ef%A|Pe zYLc{nCxvO&WbDcoYOZEM{J(0jS((yAGx8{|XqicO-F}U=by`VdHT2M##;6nCU-rN0iI#mfZKiT#!5>>JCg^z;uIdO7a})*Js9-`{)z+uY8_ zr&U+s!AUXX`JP^U%VHzdtT}_5F7#rF#9-2GT8N81v+*qB4_IBqfSX?5hNsInik`vd z;AFCRa|kh?Dn&NlozGo7-H4ZE zmysYbjZ}k-Hg#UaHvPA8&e}d)zvCR~J$j7DNd{tXTVqbjU5I5*kKz?#Iy88K z1W7F$Pp-c%!kUIP_^n?qJ+P>h%qmO9ujR7I=FAjgq9uA(#ESOrRu6F0$Wd}vU5Uho zst^n9pLmI6F_szlLjn$L$94Yeu!QyiP6#$4eyT2HtGG3pxV{+kGg`6s!z);O!!>;R zi5J~t|5TK}ZNd%bo?)wlWq9#9BkGcuNW^|jAhY&-!hXl!;J}3jRJx)LTU@wKb_~45 zSCjAHITE$FcUmm2d;bq_7xCmyWLl7o>rdjHZy)26zfO|9FQiGU&UYg+VsNGPEu#?iJ7Ehxb*fkx`GE}+O+4`#JLcU*^!AI)wYq!_ftvfj#cFMhtHI!=<0(+dzmY__ z%!HDP&CJ4T!u*!VVKN>apt$D?Q}J4i`PCl@hjxj)5&vr0gzR=kYs*<^b>;$-erxR2 z>2IA`^CVGM%A=C8ogoKKxn;7`Y{bCI6H}niiU-WSKXL$qEY|)Y1O4V*W#0IjFpG-T z(x$W17|%Qf2&Lq~nFJp&bTg8fYJ7-U_ri$zncvO)&9r8!b}N9e52wKG)nCADgAH`o ziD|4;{8wnVyPrA!-y9h5Ru0s@i)4heG=Ug{fZVADCX)>ZAw%}?&!8AT)E?4>oAFk@IIiG0Dc&%z_uSjGxC5!IIQ)roW>f z+?MKs5_2@`%07$Jq#Lh5th+4ux;X(vL^m?KiVa1%(;slWdJ{+|yV#|xL!j7$I(YZY z43JPbRxnBK0lX&pl{qKrK~I=&0YoX4kw5Z+X$v(6J6=8j(`r7Neh!Lf-Vb}xS(93s zqx>mG*ZcxpE7ne8HFUw{wGE8+tjS>0($9kUuqmL!#ETD!7lTuP1u*;ysDfiP!-INe zA6W`ZGX z)l9!d3ZBT2fCK0=^HM2?E%CoASW*=aJrcFSJ-O{>u4C$%ZZX6(+UJ4NvVF|Vz|(@% zN)w>*+XFQ9J^=kl8g9O5$`n7AXI}S3GW|0181*GAyCk%^R;{Cu$r}E`+_gE)RJX{J zIewKu%1??}a7~`k7keRKTNi@TK_8~)QV=_KUm3ICs+QRvG{{6=Z)U!4)P}*5c8qT_ zh2N&6Gs7~DOz^fT@V=HVSo3)`qpi9HdV1}Gn%D0^eSs3umDL7E<^N;AM0Ys#)i+Rh z>>FsQj$`(zhy#9h2xCzBf{AI0to`gC4%bF;jQs6IaF$0rED`&J2G^`%Zivof$DH?q z#{Hkb(R-oHMwSYEVq@y$3}(Y;%a(vU@l)aR6JhY!%~aT7t;V*RD1tT8@lffqnjj%0 z71qw$3bArFS+m6sSh*~Q2a~d(`$8w+y>%1xHMeCR^jm{Pg?AatXAD$~z77v(M8JfW zFjD)}80L2@2GW}Fy24NR9cLs@@@NoWMUe4{%R?hq`Zx- zJ9i8;?M#KI%Z~vPq6i-jgwr$;Gk)Q=BEcnZBk=p%8zK3xmvM>ogBQn@fZ~X6pkSjk z`0xEmutL`bWQg(#Z$mOba9$&5R5%WMwgjL(J{0U&3`8+`;Nr6F4Fqf#d3L(uh_9CjUMQ*qyV1 zpH&uk)VLfN&l_Znbynx-%y|GCiOusK*|CS=rN7DKVk8o;~F0{l7d2s9^h&;=>i z7EL+HT=92-rT>D#??gBFpN={h^iN}t%XR`k$;Gg7atx$t<}lC54u-UBgqwd^0f}Xa z@T8;~u-LK{-n3MP<)bd_pi38AZ7~B(xfuaUCL4k7g{NUwt1HYexX9ptX>d)>Opz}% z5oj1c6Fyb71SgvTI1qRlXqcNrnbp}eA;TH=4{ZmwZl! z)1YAOEg)I=2!!=_FuT54fcfvN;QkvonSyjF$nJg!HauAm^Tj#1V~q#&-B=0S+asB6 zO%SBN{mLj*FJL>CF9%YW3&7vB9`JNkInyFON<#uH;PgC6aIjVlTvU3>NUtfu|Clbu zQN*FXOB6x0+DovcDG%ISEDf>-+@R&JGElOZz?E;MKyl(LWY zgJES#0L(Q}gK=TUp+uqr97w2z$Bunqk`9Snl=j&$McQ3xwFSa0?u*H;8UeX#v=Ph9 z(IPt}U9ffKUOYzj4Q?x*LC*R{kcX>1N$2uJ;;O%xnCctg^Iwvo#wU?8Dsc|xvqy2^ zxL9)H{c2M1VkPUA6NiH&BJkr6e)wF;2%Em}3buS@kEc4b#I0kE(BWk`)b+fKV}FWf zR*!A)g3Sl}2Uczy|b`dG=6|R)MgqPS%7p64{NYRx$ z_|?5bSddmt;fS5AuWfit;Tq5jSSWdpbTaG;wwm{=VITE&0o3tFZr^l9vlb3P#@z@K= z#Op~nT9Y}1^`CFY6ZbyCno8MNt!4^!E1gZWUyFK$4>EC;gACD`I)+$`KZw8fq~X=7A(HP@Bll4NIeNiu|VAF0QQLn23x z!8SaDMq{vkBU$Ua6Q56=kLOv{U~Csidf%AfyFC*4sq0Fzd+&K%Jl&rx6S)h!WF_DS zYmt-6QJd_D^d{Z;kQ`q24bPc9mCQe!g+s1IlB(v7_><6?EGpeB+W#lwz6YZJD&7aN z^juk+S2-nI)C5 ze`Gf4oi`o-QPUy*{}~g>a5r3*vjMh!12`?%gxvM;#_JQ;VGHqHc=FcE_>Ih4a-nz& z_U`V$vU;KcG3}eO`*ieoNr@ORcfoa%1c|CIoLyIf=tE^$7d1 z2S0Ooj$hANjpP30;^=jXWV?kl8GhG{cns`n!mM zRqA3w=izvEMCk|oa#oo==hg*xL3NOA_6fVZwIJfNt`n;_wp`EUY`9S_h0l5M07e)r z=Emj#8eMEn&c_XXHmNt3-Oa${B=jp1tfKxiiwhJ8Jyi1f*g+}B(!Xx|^kTD?jav4-xl zCVOYH{2@m$t4|jH@SBDwUFd|q)=xpRh@pD4;x2oCp#i(eJ)6}>y=?m5ex!4AEBmfS z9E(>ij3kbIo888~Hgx11m0MU->4RM1xmvm*#9Vl-Y$-e0J{{jUKT{YOcpLs) z{Da#O8^?~Os-UK&C2)Z0#G3h%)0Yyt zu*6^#S3Cv&GweWy-9JH-^jnamyq)w5g0rvQ=Epi1 z;X!q2ZnO3a9I#xRRM{DS?uZ2kV{wd|ozHK2We8TTtukJAal z$aL-lq3zjt_&sVY_dFy5F4W$QN_;k>O^!ymCVo3tJ$MzoIuZ+)PQM8K?nSd3Lu=UQ zc0R0MRU=lBBk~F`uGB&}pkfQF#^r$W^O-o*cmle%YXV-|RssJVuf**_U)bnW9?V_woYY!~WpGHO&7h`VjV<`7< zEl@tFg>T-ofg#)M>c?$Q6-fE*hJ&}4(x*1vY}2$zENBSDI)C!H@ z?NbxH=bOwv{P6JM5 z*VyfMma}(XCZpB|_AFS_g=}Y8ig>@0u;_3m>b-1@w%n6My~PVyU*BcOpeGm}N!`wy zEbQl3sGMNiB7QOPlT*>1&JzB@j7Kon)d^j^SIJCMiAR@DYoI-cSE89Qq39L62icp3 zp)g{Nlqz-jzE}Eu%93#~c+7QTuyqZxP4fpumKn%-Y!!3*Qz`l^zYJ9^Ig4H@cGaD- zPeXEP7x>aNePAMY9t#RKpd9O7bhP3Pe|Ant%|z!P2>h7AW+<2d?)Z9~ye^9Gm6!;x zvG4dk*%Tx!l4jm6@!D^kyLg5*ID5mE0@` zmd}{L7S3`8h2wkhgpNGmwDm3uxVZ?uOUp$gduKq$v~4Kt=WN#h={e@``fcdyY8Hvl za;}rLkphK!MMwwaBL$g2{3OZ<=_ZdseilKf&f_wM*Y~I_-A5`=KmZYA4EjX)$e>D5Z0o1$~Fs523z@!@k;Ote*5AR9Bi&rHg>FuYG+&__HcDoOH zrdQ4H3YJHAmW}WMzvIY7`^kKai!T}qJIAP{z5v+~UZ^H}HY>AsKI#~G&wGBZuz*CUV#ny9FFp3 zH=_(o7gUqJ5`CyP2Jz?9(SqGE%<-kc=syWtWV4_N+!&vQG)x#Y6NjSm3i6 z{#CN@kw*}U2&fV46K+H`Lq@i+Dik|&kfvWm2x})_4-4n_qmFgdY zfp3YR)XW@D{P&F%+GyaY-96ybI3+Cnu?07UpU1uPdXTD65o@X`3cXE2pk{Id-Eis; z{uBHcas36n#kYMZZgC5J9Qc6wnN^1NZcBp-uk+ZO>a)m#HRgESwFgvF)0r{;Yl0=F zO0$ViBEiu=%hBbPlkl1>L;U?!2C_o}{AIHza#($=ZZ0puEt)NI%xuJ{z{VI{d$tWX z%)Q2!6FaPOWG2@6ItwolB6LI62i5P>L~GV=K)%vfHBSE;<@;c01LWq6vs3O|F` zK9RHHWhb)#Fcue{9|FzQQB3WVj{?2uCn#1$63@E55AU0|4=cTl=8lBrqJPm^taa)l zoRM@DdD*^TI;Tu!!%_oK%=?oxV9=f4p81UGo<4v(m**ja;!bqXe+DiuOGcXIzS!)d z7tqq^LJnr_aObW2P%dX8{xv*-7)8y%C7pXv#ex!8mg$U#!sR$)?{S2c-^}h)?Ly1n z)$p2zSMbmm3G9+%MaO)Q!tCU5l&w1s&3&XLVnFNS2{*^%twX0#LV`1A?>E5f=2>Fy zc^6q7DNo;ooCiB{B=GLK3(Qu-TWG?)8%QNgNCjfSJ>()SiLvBn3CIdl$P{$ha!E8|g&r8>@!DMgA}?r7HEL}0Qd2Yo6{L|0yx zfsb=e3LXF%>}_?FSruRgiZ3Y$D+PB%oHSW9W%nAZU2=lo;^c9x>J2nBDcrPM z;s;Y`Rw(K%se8R}N)#gg+9kXXndGE~`u#%(!|^nG)1Q=lJ8Oo+n< zM`Mt4lE{NJp^)#Ho`dq1jmNd^^5}hgH~KemC8ypyo?CeC62da(XoZfD_ia-V%@2F< zq&c%e35Z6j+DG~Rst8ohnc_3&UgPWcPobZ0exXU9JMkO6Mr;-K4H?=b;N{(qK(Xvu zWLRH>OkRp(W8qsgUD$<|%3ne6{tHKDXUp)3Vi_c>w#!QcM z+&auA6&~kqi&$Uk`vDi4m%?>qH*tG@_;c=8%eWfrRb0wACvMe$aa?wN2DfnRZ`MYT z&P_U`!6}No6624Qa=RxxasqW5&huLWH*xDKPW+-RH^-fF)2?N4A5AQ{N~0MZ5cOSa zonpC*YmRUgyP`R<6UVsYYy!90^duLeE6%n1S#eQ+ZnK^?1>9huHMiic9d}@zHrI7E znR`w`xYMihxRTLs_ULpM&au`()I?I@d^g5&F7r-t)~YADZT9=Plue7d{l&4|PCaSP z&}s%ZdNY9wKufuylQGmF@*K)QkPF()U zHC)>KGhC$3C-(2w4IH~VnOocy&COyoxNN0luDRp{x6q@LMU6(>;i7&v=v@xC<=8H6 zU)LVascaM1lKP2Fn;p!#r3yKZpQ#+b?g}^XZVYF-YLpfJn922Zw6UIhf3R+GQ#k7n zah$Z54wrrM42SOda4MV3IEmLC?2i>XoWX$=TwA3xr=ym}8K;JF9S)Flcz21rZ+)D* zlNrU$Ty4YY27F;Z|4!r*?YD8(ea)=?^UYkQnUItHc$oW{yqWX6pusI)YsW?I8pl0N zd(3u7+HnSslexgEbk0t16=&?B!sRA9akHna=8S`KIrpts*y`>y&O0`Wo3~{G*LWv} z^Zt{_6)fJ&jTJZNWNIY2tWWx!-yZ=NyKf2ibtIWVUDS}pEuELl8Mv!(9;K(a zdu}FNr@)o-oRZBQn7f`+4ie|mZl!V?PR4O2!c@+A!69z@_EX%FjUk-fT4#<-4Cdax zv*3=p#Bg_C#&OkkdfX+AgB<7kjs4(znk%hH5_wW;*&P2WcH+M++&{@Cu5EA{cc3SM z%avTjty(L~34B6D-TQf5Q=StS)M(BfcoxEqt^UsH%>T+tKept2$J=o__K94q(;!=Z zB$_+ndWuVF9%fbJHgRiGmT>{LMckWt3ET*-;i?iHxlKDGImb~a?oZ%k?%U~e+?Fm^ z?%tkE&i~1Nu5sxu?)%C-PIccoZutX6E@)7ktDI2At%&gCW^GO5H2=nNQu9-}Jvwq+ z^Y0{1$yS`>OftByrn|U~qBGpU{cuicuRC{W`Xuhj?5W&NB*SUn*~4XSJHwUy=g37C zOK`Ix;S%B)uF~--T>8z6oxu3RB+Uvw>rfWF6Q&2RM$Ko<&2HeWG54r=z6l!qTg&co zYy|0_^wIB4er!gc3bC)hh#s3uac3>N;U@cj!K)+pIkBw~@NrfPoZK%9t-YsG{CXAm z6360zNl~cYu#*nHQXrpfTfjlJMfAq2t96G`ve4r8UgP7Xtz?+Bd!N=-ul-nEv20gUdfZ_M> z;p9TN|K)jnOj`+8)=7vMj>AI#dJ1nIv`1$umGKJsanMTbEYr0@m#Eb)XQzCYRxvU&Pt{zsrH@_aP|uRvLfEna%0bA-KYu0q=K-)^-5K zMGY_Evi5wk^x<6m$2gp=-ocIGH2k))*x786bZ9rB$)Q%K;rM-i5_YmhBM@j zLE-0I>|2N7?|Zwz)%0kXd`q4dt2{%C>$LDgkN}MYN_eW6DEk&HL-wqghmrac81(!! z#_}J@8o6R@=raf#j^(g%YJI5u(|vepg{#Q5WdzqOJyG{*-ZXZoHUuu;?Eyo_J-|f? z%ki>QU9jFT4Ck2C@f)mtSPlPN5dGPoS+sI3lYJ)#-+MM0K6USg5mb&HzSBk?#ij5- zdI^aCWG6b)(jY!E%gMX6i@3vi6*EKW6ZUTS3Z2I9AjnIJ$d#tzNo*R7ST_~M*p&#I zKHjX`f6#?}!VE!eqf2PY(^q8SyaQ13Su2i<&4<0BzU@^FN8FVuO}`aN@qYWy;0tdB zNLti+Xl`ua--H#wM_u2EN~1U^_`Vzmw@Sc}HD7@BsUkeaK#mlSQNp8FzXR}72Yjq_ zfeKyI$Q9S=?0)Nq;4>GBUo4Eka=S#1O|>F;_IWg%G~q6EYQ6=lE^H?w>7(F8x+%GG zBn1t1zd-i|)-c*+3WS?Z*2@>Cm>#= zwInvq2A=-lj{{eV!AXHq7;Ha5qNG&u*YioDGliMZdQKZ!aO56V>)V6NPn8n87tW0D zQWdz|D1u!gF3&X`eh;h?yhE-8|%+{bfD6j1y!h~#t zuVkMmLK9x=Kd;q7+>tV514XYfc z$4S>u;@(x%!U-+n?0g*_jxk-yO8Gp5OAocdI;hHhKXRQ-xm?5zX1s%XYwoc(jK4wE zpl+yq!;u?^cnps|Y=*bQX0f+mA-6~S5v|*~9?6Sj2?9*#%th9A5 zls@0UI!O>XHfj|M2cJUM)Oqaw^jpyAPz$t8z6dSbgm9PqPk3K^JgXadoSoqo$v#m0 z!0sEib*(ZnPSl9cN?DXUp zaO|r8V8OQlR_1;w?69bZ(E*d$sWgsV<6i{N_lt?T=^m{7r*spV5!49uJczpGEPUdMVoIdgimYy2JE)ey1_Yxu718C+hQ&EAo? z1siWXg>Ozjhty2ekBl^BZ7Mghwyw`$#fh_U*7T5W$;4b_vRSHij|Bs?G@vGr`<9Mr9?fXV3C6y?3=bSSWWlMxm zNrh4(TgY14L#q~%5<*c@(sJjVnTk{*DO*B{uP8~@i1^*#U(npSx7W;hp3mp~zJvan zU4z*VeQ7_tdg#+J2LF}cf}U++h}u+uCW(!}l)^zYt?>pFL^r}U*MrbnG!?1UYNHu# zXVH;2r;u6pSEThpl~zCOgv#dh!WZE=$nnQ96z#td-F!6ynRAno;?jO7cwvf`oXdqZ zmVK!HV<8Mvh(Yro$0BpY2uU1SVc6H$0DX42i;z zH=iZqZy(@?s!Q?5Py65+LnWdpb{)GJtiehCET^m-j-|2`akob_uhglHzez~arKS%q9IEB@9C z{c(9FXwM5qH#Uuu?vjfn%v>Fx`X+}&ABbdi{R4Hgli9noX`t>ljog}$Pkp`hj2il# zL3av+fvnMcSVB#}E30krhh`D0CEz@_=Ilbe)BQ5C{O=<^q-syUNlhd5n+I{Vehw*A ziNoi+!+_;BRT8;Q)Nr~KPX?4$&^hVH;Z-|dT;mjnE(A@)ZB<6_w`wKa8g`SNX_ia( zT`U2M&#xn!G)~~nqM1O8{YfrD#hM&XJVlHDe#J_t^|9+E@34Pjy;#YnGO+wn9V?vW zgZE9$r!QW25zRy5fuGG)xK25o9y{>|n)%KrtdA3so$Me~49p}mr*#v{htt7GvMcdC zVNRb7dWVNQF5-bN&A_9`jI=H;$9-yZ$n*7Y*zXP^U+s9&UR9XER=FzRjT%<)laVgz zKikW(hW9XRyG&LKbx2A4Ix@{YjOa>c5xwR`_-}19di?MkJ|TOGw0|&xCh`lZ&0~h# z-^)=XvT_v(QV@{Zq9V*K>H=Rf^N6>1F50+Fp0os&;N=k!w07$Sye`-U$457Tr7QFC z2_T>+XDbrt&GMw;NgSLzIGG%}DRTMBR&vWfUdIZTuziHN6>-UoBx9PcMD9%+{+45e zN*4H&<<(nRFu{a`Xy#$njdH}>u9(QIHz$cp^vKHJhGa{jEq=UAgA2K1%ns*{BYh9H z13MAtH6IZqzr6%knwjE1&Cgg#yYKkkd`C2(G>m6WEaGA}S(761T>LlJfE=nS!zt#P zu9Gf8mz}dtLm*JYI8bVyib8p@$)%x!4g65kA4z= zMT#8kk%W(ZcAyR0m(fA?T5yJh8qP{MN!9IIF0_^sib{>8)vG}SOF4&*!+Svj6}GXW>_gGpuQ| zr-R^c9*Kx95IofLV>R&?FlAmgwBRyX&js=hYCn#{uq&a+RC8P}NS>#$v)Y^gavJ_^-Zlh}xiWZ^`~udM%+AmJrFQxQYem$Vw1vtW6) zL#2f$dr!DQm|7<*v@+%!SnOjF%Kiwpk9?1bNa7FAyk2KeK%83w`3~Y zWPV%lagzerQnidKNzKBFaxaAOKF8sm-&X7$iT5?%U#qhXC-c}@(^Cc8{8q3x1`~v8 zvpv{=DLYWczDS($VHZ2vca{1#jT6rLSs-lGnM`avS5mtcQ0#WqH~7>+C_pPDsSCFl z;URftGUk#-MeY{ktQsG%stz036(8CJFSg21S3gVDG{rp=R2i-jf-EuaN|G_vZ|%<- zTa{8q?(#%!0*9Y&En@Q;*0PnoUxb+}XR%IySF`nT&JK5#max>Usj#n0!cO?^wQ$W( zbM~w0DnanKF`-*6&9=)%3g?`)p%%oRXALF?vYJ*Z!asXU*hPz#*_Ib4Da#$dgrD?W z+0!L=S+ZT68unf&?BCK=qjLodb8qwDz*dUYx;+`+c+o60Fj~ldIaB1|TTmkme<8*G zm%oG^qRR!#hH`Mr!}|`?*Vq&3n2!PcQg$>~cSn$#OYT&b;CKTCbmtB znwWD{=(VkkO7@DQLeyUfbpzsr;o*pycj*XtX!TM!TX!73`m|N3duBFeE^HJ$nYW7l zuEf?%(EcV&IVyTK4#f$d4BdBdzO0RRI^AcpW3})*&9g#-57&jI^BbV_2oElusin+{ z3IsI{p~4$c3T)=~8VYXcve$4mbU0NtiH)x?bm$q^CJa_^A(Vy_>+G_U-Mqmbj2P8Z z!d>d@W5q4(k$1=NrQg?urz>IwpRf9{OQ)X@?pl|H;}1o!r8WMPKc|na?qsu_+x}qV z(-hU8l!cw!Dg;9vJ=BAY(^yeulr0?Sq3V}?1b2_-Q42R80k5XM1crGj;QO^c@Nroi z1ytO?i@Z4W&xrun<67{knG`Vcz6|#L%7?lA?jYZk1EXulpi784SaR(c$MUDqto#gc zrlcS6-g)3UjOENkXT@yU^XWM6FB@y8;+?7sOiFs zfL3k?7n?gl%4%D1=&R^6$S{IYoCh+ZPJ_h3R&dPj7`(IjE=V*KGY{!$^0~qAvlitfj%)-#tLd>?`}iA&1@FX9Bj&$bqFFJb+0@I^Y%a z+261pYPJr6;F<=i>`)2kn*$;x6fSc%uc{9lC@j!Pq7lN+dQ?TgEdT{?%6j*yu0!c?l zfr9=>kk;`Dd@0TYbUF>(%O8Pr6HP!&Ln!DduK*kF93c<0d%(MikZ`CT^u;ECniGSdC}SMZH0TE}H+q4h`EM}a^8r+UE&=ck1H9wUf(alG$c`TX zoU|wxeM|-@^nM4+dN+cvAsV1W!Us$-OahvnQb_B>dT@OX0FtS-AmYgcc*@fkJkxIj zGI6h|!&@TpA0KznGv*04s(b?O{A*b3=nAXbMLwi%8<6yS5FD#!V5=ziH@u-69KVk|ObB`o+$0LX!=sU4#qIr|Nxu#}a+Cu9^3H+1OI<-$ur25aB*6cz z5VSYkr?lR_084Eaf(9asHpE;2dizR&%F=kyV<`Z4_8Za-I!;u9`C{K3e^9lz5_~W?4W1>30NF#OVCYRhxFV@XiRDd(bB`8-`1#wxfh9HI#tums zFFBXuEr9r*lC0vsEk0-mlOqVVc! zkme!c%A{w)`(Hf3XG;-Z(@KHZ>fM1BCdWW~#Q|VmoDF<7^@DTXZXo$ZJeUK%Qz@qm zf!wU^pr$H-dX5^vyzY4*dG9~?r0)T^J+c;>wnJ*}`Z$5L>2p}zJ%T2dssS^#<5YeEdQe(U-SrqAN_uU%1za% zVRAQ~DV`5TW*-Gt4}IcCpD)G@E>lGuw`%HTg)UgLE^PMoT?`K7mViYmiFnoIbHM!H zX6m?R0v9}-gI5tKGN!xds-T*kC>fQbx73MaC_#lV$()P&u5jLTv# zs+t`A*eSHdRiIs?oUN>>r#7~zP}*V{uyv6I8**j<7mi5+ z$-K+(!Sl0VzSS>aE|Y?@4rasZ`<8I7*#xYyEta+ZuFsWC954K;tPYdMiGv`uDstyw zBB_I&pygR16=r)E(C1eZCCe!CYr!b>qg8>sZeT>cD^&&P={g<+ZWWv;Jk1_7lg2?# z8n{cet8A2$gJHYus6DTq2$g2N7Cw9>4l;+cVJI8THpp85uluqv+-DfL2tQJWo6msl zXfpCPb`N3d^$(*)xw_;etjj0n>I8wAZHLf#=b9`KA)GFixBV_y=P> z`l`d~vB@I-{bxR?3aUh)80(%Q~8&wch4jJn9Aaz+uobLLIbjVDzTIKK1QdXvrcb>P%OOe|ZeT?Rojm$#M9g`>rF;zaz z^B~F2J<2_!YlzpnFmm>OKKXF$HPJ6iB6p2nl9;_~xXohI=|lb*L}}#;C?63+LX@Pr z91TJ;x>xb+8a3|f{#tU(r-3|(%0l}B#&c}N4iXZQPJV@ilD@y*Wco1R*2)|6r?>Acs+Rf_vAtKxgU%UT`2jbQ zjJyflkWaFPcW|1lhoZ6l zT-@SG*Ci*SJ$jRS;1Nb)Ly=qN(=k%;Gmev|6N&p}IX-BvEIGG6 zi-hVTE_PdZhnkz)G|U#<5W2fyqO3>k8!J)T;}wDnsVtmwnVw( z8tJ@$gph4-$i6kv{uyxhf>mu&@fYY{;aXQDGY*WN z%)q-!j*%mV92oDGgcDO%D*O2+>CC#iYpNP${^$Pq~(Iya1X~v{B5iPXLt7g)N^g$?vcdkbL+q z=uA?8M`De^_xCs8{cs~PF})bfEc#7Oc^Ywdr)lHW?_N>Ye>tObKZZcSVsUEY=zDgj zfg)TvZ9jWrDuheQjgiE`P^2a!>H@m$t{YzN3Q7_j!Ss-LXyZShJ~%1^uguay*+*4D zL{nw$Br=T@RE~jRw{PIkdjLbHcz{pyO<;-CC2rax3EY}I|*-d=;3a}G@|?0mhkR*r{T=KPVjT&!n!&Gef*}di?{1420Eu!v4Xbi zqWfKK6dxH9 z-)sgO=kCUv56HlPH;1U*pJhpBog=t3XF0GGTM25}8n9TNGDH%k)^MZk|-iR`N ze!SeWcdTS;6u7o44j34Sf%confwF%M5DrY>?&-<^q_G-k1XqKtN9{rQyng&3p#tZa zpW@uc%=i^dY3(H*J|;B&V52=pCVj8oPo-Zaxia147^ZC!6^I#CgMIw!@Y;F0+{Z}@ zMAu{o+U`FU#J<`Bo)oA9ogY$A`pF?W*Kj2W=x_ptA~#@e*HTn{=PDQWCP~m|cAncb z@)5{Q^2FcVmT<>x)`Bb%PjTqLOT0nEZTMJy4|sh9?5^reyrps&PkXP=Z~Sx-yq`Le zHTmFyn~tr*y6>VuV9Nme-B5+fo{$GxOixqGqSAoO)`Q?_Kq;l;u|Xi~I}JMnEm$J+ zik(mU&{;LBNgyS9#-DyJJkvN4^`3UaS_QG-<-%_uz%v8PQomA@c>FrsEjllWUEM|} zdnrJ%qoHV1m<5)-`I;RkX@!-0@1Y(ADRL-)Ld7pGP^B?CT&i9c_;GeRwflJ+J~h1v zv@XpA=|MBGXR>I{shum(xfjFz{(Ku=4o(H1b^F2QA7bp+e}2SZ{yTD^-JBFyTZ=g2 ziI5tzMnNltpk#qGZtIjo`j2yjZ-*r4OUq`!u&aM4ooTgzPyS9#H!a4yB}Fc{Q=33h zSvNYNmVvh_H-oV&*NKtyUXk#I*dyxF5O6*N0f|DKv!JP4d;M>ejFy1nn z+je0!sF5qh6W&;Zo>aNm}-!sTR?>S#!#%ZFk>oV6>Ie<)&PUY!B{ zTNFp1wa+KbFOISuVKTrx;1NjwJ`0PR1Fq4goiaVLkc_{25^u?QPTBTbfQ?e?K-;sG zV0P|J%KgG4)-Fo)x6DF;`n6r^}nkgcOit@Jp(Gv@@OGA~%SzD-n+#YNz_ z?gaGsZxA@WP=HZ!X6%$_@}ikt3SPLU4r~vr1r;jCg|VBblIG&0+|s=aT`J=OerVUS z4JNmR%Q7y&mEJ2?3a;Tw;7l`T@M`9dK!Jc`qK@zZV zSX)ud%3j?>B8~INi`xVZroRCEidLeUn8BfCeRz|U$fF{K{;0Z#r&0{>`=0H3ke0pHJS6Vs!i zl%`5Dmp$|vAD1u#FYmT;)Kxz&eyR*#r|=TjKHSA!E(#{TZtL*gRr9%+iJdiaBk4lL zI4L4ErICyHzK<-G^58bkEWul2_ml8vgCuUNK4;e+g|E+O2D_YdxvG`~Tzg!d^MB}s zpG?C94`BR2OmvRF_7EuTy@UVKN+f7t9r=4biVKX|!I@6!;!5L6$uj$VZoz@u?71h| zoQm{Esx6`mFDnzx2$FA*-#1rao84)m{?J0M{!tcJ^UjXMO;hDV%$AXWFTJGALxKD? z)8x0!9Uwzvd-*@nuZa9!At$}hk{s@KVx3qSviVOMUe0K+ZzxfJ(s+nd+_j4>^pfJt z=RM%A>?y-`LnHX+6dvzBI-4_-QR2)uN^`b0jokWe6{ zcCz894}rKb7>xoB62Ip?!2+_%Nue`?@8~87QuU%p!Q@k%M0ySNuvi zmF7N77tJC%!oZH^v-rT=SllvrmutDMju&}SSaI()e6QCSP@9zb>ViyCu_hc8C)8u} zhmpAO+h=ae4m(UgQ?YxI-l|M#k$-Qb4UKy!9ii?s{uUNoCq$TSKvP$Zz35@Mc7Znn(S(7 zrb0Kmb8c~QB#$2@&hra6MG=Qh!;axYy0?+1ZD+Y@^+jyYy@g!DjB&i?{2VTP&qqQf zDKaV(9?%kdESP-Z42C~Bm&p~$&)@f1F@N4P&?VZ(@O@beCcJAdogXXD9QvcfbX8{1 zAG8&id%d%m^-DWx+jIw}aMMi(sZEEMi=(CowHP*Z4{8Gnk6aw#@0d5)6C)FS2-L&I}xy#V}b@ zm{XB&==5I~cs%hJ?f*`h`5Q_x9a~@3-rZ--v=vJ;qB;pvd0~{Ma%d(nz?KPjn8d6P zvt*)F_oiCac$zsp*`@Jnxvs z1YViIScZL|b*GKe9rs#lPvs~xc~8UXt*`W$j;MZmj=nY%fx75P!4nzXKUs9d2W4ip zxPGmj%?EnheS78vG-8^jL#Fp5Vyt!o=H^=?W}>DY^X^$>ZRIX0=9Q&0b4opy{ynV5 zH2FNDk8Zrf&FGxOOzUsuYv1ZJxkp&KJ6xC1_V;A2^{X){UK1E!i~d^s{!#k=Q#0nL zjuNwc^$2a9qQmqCGt74lPv%?3huR+3nT$f*M5g!qV|rH0Dmr)eIl7K9W6}Z@nN-nz zI-)b5$+YdKy?*F0t!~nc@B8pttsC}?VwnvS-=e@A*}II{9O}jxOWQInnT_=IJ+91& zo|#Ojt|@c6REzOA`I%lQW55ht`HU)VPhcwA<(Q@&63lfmDdyJ+9j5TBK6BA`0+aJ! zB|S}O!X(yAV$|gon34b1F}qLcFjETjn5+%~bM2ZG6Ea}T$cx$2J>}Mn{o5>NN4g$! z*-4X;4SrI)X^JgFf6!$ll(iV|7Y2-CuP1%3Se99>V9kU?OED4t<@Cq%kohN?|NQsJ zk_p*0i)qkRWez-%WgHDHn8(+RnRRpkU72LZ*zeL|?($|##vO5Hd6F(uW7JCbEki#AK#srXmyLs?F%kpTGoPljCh4yr+XD1x(|^+05BNH)id- z$;|ClOPIA;fKlz6%@m$gWZIOLGPcP^OsbR-vqnvx!QL~O(eIEcHn3v`^p-HihZLBL zjWd{*hSf|vZOH^H&#BE(=t2Ag(fvz;%o}EiMmu$whnozUThojfdi-m;N?MkY>{n!d zO`gVtPv1)??_a@$hiEV>pK8%9Wnpyk=2?u?Llx%F@?gWOkPuGu^+U=}#_%_v3Y$X6Lz- zlaW1V@^C5H9a<;y8OGtJpBr$GD67%a_lC8cX~pb4qmO>t>G2mDUXnRYvP9Wj^em&Z zNoIBp(J`0ff9MGS7cKJRy-nfld|TLa%cqc;8}*5m!%W&-7)e4r`pAMyR*t=XZhTaj zDP35-1^-Sn<%Jp@wuDo|!2Fys`L#R>-`kZyZZH3XgA^B$b$J|_|1p>N zzF0_h+6)rkoAY4|TFx2dwiBD^aU6Roo9OK?hjtPRM1CME_DrlIndI^TJQ>X$TLgHWcLc9`Xmuc9lM7oTsg}9 z`85t4>Kf0Tl`zI9Ubit~-~347IujB+PLWG}y@oWG1<<`uPYTSr5W-L1Mmhs>$b~FP zc>R5I13zzG7pO-a>PT55geh4w_tQBn9QcRYO zI|48E|HQ9D0-*Ik64~pbLHt+VBs0T8iEBMg{=K?RdV8kA=GkTBD`iZqi=9cnn73%n zKLPal93Wp@jQAI&dSpfBN1!iafUkKv30yldlSme=f!UY$;VZrFXm<4zoN-kFHU0G^ zhsW*YY&x32SkX$dyHT7RM~P&Q)I9XXLIC^s789zc75UbjAT2XCkuaI%d~m-fiMG(i zHkA{}LX=Bd^Zn_Pvr-^7sPOU#gx>xIvbB-Ti>0>3Lkavk~c&*09pEX09cb|m`rpEN{ zAG2}(4;D+$w;}!}=IGYfDzZQ>oh%NGA+J>CkcLPfY^9(@S_XLfQQHRc>dS7nvTr5L zY5Q_7r5=;w?sh^41#vgee8k&51Bry|8RA|ULvHzpffcLQf+-7cW0Sdd$Vz6e$lKV? zDXg&o;tlC&@t1Cr4t2N#Fom}se8@$J_Fjr@6SzB#F35L{4K#C4BAy3#;k?gaD_g4(XiQUJ&DYGjYs3#74^qz0I0@Ii`c@0ZVSxuCG|n{~$M@(@$rf_9i{AKk<1NuaoK0 zH2fyN3%M(+GuBidk!h9Xqt{O7Jg;NEdsio1Z|Oz6@I$nr1&zw@FkNojDP|LTX5jXR#f>%X(Bn+rFg1?*q0>!AGl|64;sDf? z-^OcvdXFQqb#1Ln40qw%ahUM+0@j#X5A8M@A?e4dM7qnDR4l#1jK6Op(5jvekA$5@ z-lc$-D&EIMi8z{()y1$WHHL)G-%DQFmywT0r*X?H1CX_#kdY`?A|F51!Ga`~t+`c> zlTMV8TZ{bB-MlW6GC`jdispMG({#AM1%tTp$r4bQdkq_DyhlgtmSLQHL}1qQnxx%| z;C_j{W`k@tK>e62yeE_FnBR;w z>_X9loK3hYEDtprFx30IE8zY6){N5b$Jno8CfE?J1T3GPgu|~n&Nz^V$~za48T}%E z^Tt>rhG)S;%f?YBf@vt^ciZ@(> zmo+ZHr1IJD-3l3OvQ(K{WE=(lh3QZkmQJ-5%^D<7(};}EROFOJ9$3}oRkXCX5X#jr zhQsHAi14;QL2dVWy(K5`zN=ZZn`ln3K=C{L^tB#7jr|U$uJA^B*Ru$!sv{3*Ir24o zjA(5;4JQRngwJOjq87|71@lv8axr=*>B)iR+=th*(Lbtzh}}zpTRnH8f+$EX%pJp% zR@i|++f#5$$TbK|BT47(1!y#69|7Y#;U$+Co)5W;;#bwLkg;b7Ta8H6`KA=|(hr6%vFt{Cw($mpz#L5K~Rp!tv!tKQLM9&TVmw0ofyyMZlyEe4a9pyA9sRGHQhISlIBu#m`1pJV{QQKaDsNuG zwGndoR%Cg#g6O*uZ%QOiE-`{%wz1&6h$s8itDfqq*Cx?tSKu>&bBTMH*yRB~M@Z=)=W`rkNp>yzx z>P@w)Z%)DTFK-CWK5L)^Ny&IbD;IA+*NOc~GReD;9isVVB6oJ`f29B3KWe>W1dJZ7 z#>>8qC&iBT@Xyu1tbIW$ayWjIY}(d>+Ah`LFWVnbK~sK{4HHt?LF;4Ok58xYoyN~X zW9MS3d{qSY+Ej%Tl=SfniTCXG>@qZarU3jkl^{YZ%i1TwnMA+#Gnu0JT=-(e4a#kt zieT>RB%xcQ8dy-*ia&A*_`aALY7N^js7dglIIjTWQoca=rB{-2w%7{PF6<)zjJ4QT zsnXzArX)#oRb!j-v_#H8krS+LI+E}4#%WXC@#T^0_=2dV{iiz)`>YtFS||;cuF|g? zdOgYEoK`#5uja7$Jsos-mmWHeGzCQsXR*f}A!R>ifuA2+OyX}uP#@M#qN9&xv1z7W zpl5+O?P>6yGU+>w&C5dYo@6szbJr67a9c@K`p>Z$5!%Evtr@G{vH&VVO_=fRJ=MBh zl2pwSQto2LApWZ}6_C&*RFPgtDii8z#F|b~{J|38scsdld?g&G)XR|H^|wV$avjmU z*8rp(Q=ppQIPOm&6lR70Vhg*k3qyZwr1Gs#z^i4SaIgF=cF*!Hly=7r0Sv{kvAU4T zs1Ef464oDx zlVy`0QSRgJ;Blh&R{hdf>}O|Zy#Dek@cHg(a@1XpY%U6>tQ?D3@t=j10bD@9_A)%Z zMvlx*2jt*)Y2r5P05Nzyzh+H_H~ddhlX481O%|>`$43^;B^73s`0FwYc=Xd8l)fbc zqVHZv@y2$XA}7j+cq!pmzx>cwCx)&*#GkO?(4Te??48aKKDgAlVlKQ{hYq) z*8nE%%!5s(AxJ*M5GHI}3#r-1pkDELBMET-f@K;J2%A*pX(NHm(LrblH z4{5#20AD^S>I$VIgVaIzCOH%}#Xg3XE$dOx?~hQuH4k2G>VvLkE8#B%h+ekXLXWdY zpzeSy^2^glvwvTL<3Daf8|V4JeR1_rX-gOUIO8Q4ye&o#E?NzNx+I*t^B?@r!vn6@ zsHJnhE<{MF2KqL%Liq!8VB5rb^gPYUaJb2o%!s=SL0A!tcDf1YsCR+yR|?RE85(fI zi7Ld6UIHnmH$dC=U1;_9dRRRC5(aurMeM(qXpzZyCdBXoJ!8KP`e)S!$1HZEd6PO| zPTG0YUX}%It0mE-8}2Z6rZ}?ux(|L+m_;X7uLr~X4Nye%7J9_;K2cIV0QLuKVPAE5 zdSb$Cv{|7Y#^6J+W`7KnR=kRgf61ZtMF7Rlxrcl=rK6Ep6?#_S8Tjh$byN)W;NIp0 zxWZ41Mtk1TaZNm|9H|FSyq!>yy99WF+TpVOK6pxnGYGstEbRVr6->+f4}R164?67| zglv%*)O;ikf2oQ7w^b9tGo7_mN$_X#e^1d}TNAS8$P#2XoC9uHuLIh#(&R*cKCPPZ zP2kls%3e@AgStaz(8hu*^ueV2K&Esv`fpSXAMtm^O-bYFJ-g%BbvoAI!PdV(sF6vB zPJd5He|ZAmpH#u8P7Vq!pH8M@GaBLapV?rK`F-}Xx&i&^c^2FET*Tg*=m*sADT2OV zE$q5t4e%x|1+P{&!Y^VCL_FJEkb3wW*cm$k+H_9f1kGCXqMsL#5-W?hs3?gC7{iNVXhd%*m~E2s*m0@$`dpZQSrjTX@JGqjPK z<6X*h`w95L)eSEwy$)@!nbR5R_rNB5LptbwI11W)TjYOU3W)11w*1~3bmK8cz4A+? zllxR*{62N??dv(v^%zrc4Yq@9D>Y99yD~=wb!!WNdVnqDS$ZxIZbS5CFc9017%9YW#F zRyfCG)FHyK9BljQjEVJGVbJOEz*NL&;eX}OmF3I8lO-GA><6~s*+OmTDPc-UNPQOd z()y`yXIXT{=qmNVRt8BtWWfwo7dpFaEqZo3QLv+Q9FANo1-o)A(6@)jP-TKSyVdd@ z{Bl|WOwQ{78;Y-^4I*yHaC$V*2~!5Kt`d0M_t(S$<$^6mxzy^F-mu~A4p{GA4W5iU zPLG+Gf)Af&qP_|Ffc2JR4@h1B?gtD(=nn%j@p!E8nphZR(fON-+#>_dB^HAl2TkF7 zTTygt#Y#$P(o(R(DTSK2N|k=6B!_Q|`%Mk*sRrwHms4HY639DX65M^=n(Asx121zQ z3HDHOV3&gnI9Irn{u-zQZV_*Aq=W`17Hk0l&&%QY@?dP}u?y+IdV%5!XKI4TasTe2 zC9bTE!xc^qkbgQ8rCQT;;=<*?Z}BC{YJn^0`t}k|nDU67GRG9AXrzKRr)2EY=?9K9 zO0!KHw%`SqpCWC$Qr1xQH7xjb3T=P67Q`w=2?m#WQn%k4AWB;jEO|B?ln3kL`1(7n zU2`Q2VUj>(fXHutQxB!dNh7I+Dzvn@7*I2aMB~?wvD?z6(T?x?(ebw}X!+V<>dZ}f z8kMi6r*vB(*3gHpn%xP9|2~J|a{3~k-Y(SC)k*h`ZljAf6f^nS#>h+Y7W}fe0_D2S zMYEqwL17_EOerx2ui_6fCbs|4{8|e68!Ms$p+1a1T*>%486)~32UAx0Fx$;HGk%de z%pJRJ=#y?b-Qbl3m&zHUqmTd6>%FeQy6_e#_bVQ0KsRRQj{ztYPoncv?9s{ay(n1A zi&>h$BeN$AT3soF-0gY zCzuu#L(AJvr(eXFGS*i<(!cG}=)i^}u^#25M?u%*+*KWM0>YA)SO~_~H34nmdt>Cf|u-8n+ptPyA7)&UwD*nLZ8q z-Bd%t-VdNpXE?MmwWeQezD$c}&O$aiZcI|*GaA*4eATXt=sN|U!O!0gbiIQfqhFYS zm?s8|R!|!KM`jO`_t^@~xg!+ySl2VZ%@mMtO9^8%co!``CV|?YPDGDdj-i9sa#27~ zBP`k|i!Mj*Wil$?z+YRE$;1{}CisjWv;BB6J)q!#e3lMDNpCIWvs!e9%v^)~{QF?o z_?K|kz;?#;=^%Vvq=m|dr05&Pqfjzh4LQEIMXGf#(DM2=SiMXK>BV*<`-9r3rTsN3 zJM4fqp54S80n5-WO((kRQ5dp0EQa3tWFV)lq4ax6Yt*))o$h$Ah$vHUT5DPWO6YE& zyL+2yl@={@ehz_ui$-8{-4c3#r5y8Za5{2&J)Nl>XNsG}=7}QJLbxzi7Ahv_kz4yb-JW7^>gb zK;A|hY5gr~s8eqPT9`8q`B%21pm-K(q~1a2Udkf@`2aHoZ(#Y7>47kNS(DChiWLIAtw;cAP3Qy4g9X zN(&WOETI20ONEu++Gz2ueen9bNobDU0J5kYkII@)qSp3F^vS8s^kT6QT5&=Gy>p-z z^<<5}#Y&JKzcrm+y-|&RZKNV1$i*XvfvL#qObD85I|2>s=Q8z2R8f)3FxoyO##{}! zLEA-Yqlcn*!JPv}jLwCraBR9dTBoFg;s>=*^8FslyU_zJ*0C1xBG1xGvQE%aWD%3* z=Zk9Je4_0F{=!dJCHcd*%^9n|^BAowuaL!UU;c(tB0v6I9B)9o@&7JDK1k^lZz)<( zzcEecoxjlhVznSX*fEfw@pUghMbC?$es(+mDo&C|R_VO(;cR}LFoBoaT)|)RjNnJF z=JMa?Z{RJTx%0Aefjr}H!q?sl;NAasc$qt5PpO&mQLkAJ)6W!TWj;T=gi}m3`X&HN_X-qNwfK&`vQKd+BN=f z-gJJ?a6CWx^%6eC-JAchc_}}Hz4$QlhSS}s%&%UylRs~%&-?tF!F#nH=Rcfp;BPOC z;VnhH{GNZy`Rf&`{2}She9rBqe9NcZ{GIYC{Ce7hS5lwKw<{jy_0wZ{^NR+2;aV@g za(_PW^HGJr`{yLT>B3Qdi$)SZ)@07Fs6Efm@PES(nZ4&8Ke6Vo4lUw;d7k8@uE+87 zYIAt2)9!qkf);;eER&z1oz55K-{)?KJNYw_c6_EyJg@U@FTa163;)vn93Laf zqiuPYz<*WD}BBJQHBb``um4M>odu)p#Xu)ECPOi%#X!V5<1_f?*$eoM`z-ivDzUt7Vhq3a_AreERIE{GBEPUgJX)uk|I6Pu#kjSF2LuXNq7= zL$b$s?N^8Sm*Lv{C+ovJmo3A$FFwb6PweFe;8k8i(viQv{S@EwIfFNjzQQ{#p2d^$ z3i=ud24Jp3n%w%4})tjHV&4U*YpTWmpHQ+0juIC>pM)RY#F8uF=0q#s^694{y zJKwa&h2PdC^6E@Z<^L+|*wwmS2_`&7YYk#Zwpe@{YNHzge-1zdVHa&!X-|_51aFu9P<)*?5Pijkfa&*Gl=4 zl2iP6X=(oE=4}4wkUX#RH-LXqHAD0pHR5H`5dYWRpT8<{g`{-_^2!bNyj9?OzD{_C z2esq*neWf?Tx}9RawCoZB|D9;_d3R>*&pL23LW_SsV02-lqG!GltsM$q1F7#vibb# z`+5AifH3~sa2h}SN_6(`O6N}=Y2zOTCGk;EV^?|7>IKaL{` zg(9m|C{ZC5a?j^;J~WV~v{h1C-?Y<`WMq%*Swu$4$S9o8xTI24WHhuWL_@oz((nHM zyMLa?IrrTAet+KY*YjC(nqB-Ez}yZbF^>nq;;#Eetluqx{l3`F_D$X{4jvZ7e4O>f zWOXqM8up6`7tV>tm#K?O>S~zsyz?yl$ZGMh2NIvAudKLfp)U(nzrn_iKF@N(av9XT zz;XwIS^T>OW=(V1^wA5&bqQ0%b0?&WXTSFoKe(wa)+CAS&*4Tk;MB-&UrlCt+2{s* z8&@qORtOj^-hVxZ1qEJU3+E@X(*9V6evA{}n5-clpLmt|O*|uBxSta%Z46|7|E&`b zv{bR6AZ4*%!d~%qyCLFB`6=SNnN!3E&-b$Xn&r&>Lk`nZQ4;IS$z}DqyTx-RDvEc0 zJI8uAFxH~i&!#F26ZgE3ytgs?*}SNI;?xzXOtqt$<&_09H5(IgiJhx>zGega+MLgV zy|dZkmnlqXVJ&mamlEseTxE-7R*5YeQdwqlBKx*MNvt;|o~f85vR#{t*}c|bVuxXo zY<|mG79TGyzIN;)t5s1JS5#%OD`{)Q)n9J1ZP9Y#Yss4847WFIwPZi}O63S~$CHcf z`O*cfflpw^160IagU{JBWh3!WcLQ;+d=A?<<1#CnR>*#6sfopl9L1u(ORU_+mW`Ck zXI-a@*zNb(;>0cftYTLR6B;X7)S@~z_Z}3R)r=9}lU&Qy!Y^#r_3`2>M>E-cO&hU7 zeF7U>qbpuKgffxLbLO@tl`YMVX6J|R6;JicVI8_H?8@>&mUTi}d~}PlIBuen`0SGN zEGaCRIohT$tCeZ2UdMyYpAx}-l-yxgqvF_#MR9CIcf5G7#xQZx)hy zdAIDs*pR)m#M76bVdjOZ;=0uJ;_~;Q;*x!7EHkK#t$pDnZcuo@R=5^3JnS4JThFqn zW#3t%*Aq7IMO*x0h^p9uDT%*^t`VmfH?rVnX>nz166=pnU_(ZwvdZGq;&-pK#W8Xb z?D^XH?E8PpY}4a4tk-h_>-Z?gY&G=Q%w0#>ik;Cc;@nDR@wkl+S4w8vuB~E*lK1Oz z`((Cn!)cbfAdejrPO~|i&#?1}d2GRxr7U{=Vixl`g~?B_V|()qnbF3XOzu%In;86# z*&|KnQ_dlbbx0gF8rR1&+mpMQ zXmAT#^E!b!bxdQ2HFmJy{beji$CE9do59k}!W@TF)*xM>i7O^Uk z?amHm+1xoM&Q4_QFOMC9N-%#CqQVqT(J>NP%s_3Eu= z>5auKYoj7-I<}m>=?i4X$HuT_ssAxlAj`%{>oYCM{nL*0VvAFJ*zV2#?7ZPrc4XNo zmKdVO7F2q(f6qc#Vb?FZ+{l`3F)U(!Q<7Nq_js0f_&5EqZ8UR>3uX$bBK9c2fbDyx z!OkqsWNHN_%;(Z}=Bez!KG?)C%fB-i`kll=H~O)+pZBo0j+QL@)>`(1@1bK816lL= z7?vD9j5Ss7Wub7Mii1ZQJL}3Y5|~8eGg|(pY3KHamwtTp&_%>GGLw0PO!{a4fdclj=3p91~0lWS~HZHE?dGrZJft-vyL)7HmT?oQz;q4Y$TU?RsEZy!OU3PA&-0stTB!hfj=8`| zT?WX0%pzV_qY{}K3fz;%NWte(GQ3zKy16Y!lZZ-Hyl5y>dz`ECz@S#ab!cW7WRtG%Z@$Jrj{5bgt6mvQp*-GXO zClrjh9Aia3?*0gPaOn^*d21Xv-pRv;GwZ>xzamik`X9W)c3F>2Dga@^TGSpjAKWRq zE_8?Mi`s#>cprWa5O+6i`2+*6GYD9b&UKlx43+D$1OmpC8h z*3Ga*%li+Ast!%JUX>%qM_Fca$EvOZk()AHr|2f6G(F=Qf6f3$HZHPmKXyiF85?Xh zU3iF`%DRx6rW2CN2?bW?R)9r^M!of6EiU6p(G<;5KM+nYvJtV_Q4>h*I>?&Kj^(jHWwE+l0T;E z0fYWPxb)2@peQ?xH}_8i9%pZ%Z+cFkaqI?uV1QeJ!gJfPGw6MT3$2@LKJ#@A1L;0q6vH zIA|iAHo5=@?GD3{Ho>CJw`Xv3tSu}13(kVQD%~itDgw`Yr~;p!k@!yxnz;|AgD7{B zE0p~e4xp0-x9*h)D1=|5ai4Dk#Uc~5;_YZCFPp;Y+Aie^Jih{u$-4ZqSM50D*#M|DqiJz}GEUecJ?f z?bat`JlE2C{JVqTnOY(2@BsXu!4r5)S`)l$TZ%n& z4i7%REzC$Y<(n7B@bS&^s87@Y&Q^Nyky0~A*K#Rhb0LbKqnD2xm12PTdNrZZMhgXM zD?@uNL%w5wAJBDM3zsZ@$uAqD$UoBy<;<4{a$jC;ptqWgfrqp--yYs7I^w$xZanY- zEWND){WhqO=RLu~Fa6UX*MA?ps~H7dz#x2_RzjXv6!9O$!Tc8Q7u<>$Is6kd0Or0M z0h@MJ3d{A*LE{h`;Qyf-_KlI}9n1kb5#kGt-xW+~X=%C5Uo3C4No8TjBmy6@Y%*g92^D*Lo_F z>Ed!=@bm$E;yag1u<3$}T`R$b?*sf-6AYT$(xB6+FL3V0_b~fE2RNng8g!Y7LBtCm zc)nP&_wH=Me>aYU6K?hLzRD5~-3TKb`MU)4%FTnXCG0@ksa5>RS<2+%yl6h;xUI-Z zV-7SPOyj4lY8J)~TL#9Ad_|6msWcO{aK*rh{4b{+&uvwA>m#`lV@>yOFAnNQ&E7&qbBo4u6=$J6jy>l1uN zJ%>l-b?{jq%Y{b@zd$v3%_Y|^6Ff~;^FKF_!|{nB{0vFT{+FE`a2HAQV+^lxh3e@N z&mGT?UEs#AH1J|)tAqG*nJ_qH&vbx2cJbXr44ul_p~1O$;b`ejICAewxc(InC-0EO z=ME+Exe0Z6md7BRelVK9ueU?0b)8iIRQQUIv%EWQPw&Gr!A;lG>Cpt4Sq{tlq-xx3#dHUSbw7 zW`tO7Trw+4H511y0PF~eWIuLRu`Tn`ndif3BBRfk^yGZDOwykot{uh{GA^>8%`e!Y z-cVLQhBB`UCG5mkYc_1`6(-wX$b96kuv@b(u$_|6TURu)jN9q#@%Vb?a$za0J1|si z{H=hEaH?i1^EznwSxG%Svx!Y>D`vZ|6tb$ljG3>iV6d);O>QV>2kalSw&qGnWTc*% z-Y90DMr(-YR!?AFi&VvHe-|+!R!i)-u%7jGi&<`I4%2qYWDY+hwcGl7=4;l>EW>xA z+5LCf!qQzVa_3hj38Jv1p117B)dV)_eloi#$BF%TiI4h1JR|3FS#Y128Clh{xsxxl z!)wLN<#qr&xT2jE-C-=W#FCzt>tUPErn25Z6IvA^xz34o?8%91?Dn`kW?^kauS(87 z8gA}ocyBQqCzr}1^B1zHqH-1l;@HohwM@H7VBw`@?6uZ=Rw4a_{irTr-D%lOIU|z~ zQ!ZzdVji*xgTLrgg&*5qI6}Obt7o#4tJu@0xlH`=DwB`OWn1qDN&1#AELBOw^tda` zU*;nFZq~*!pI>6S(Rr*+@h;oVtBG&zyu@A{^k+`budyj+rL0GEi=9T5%vaSwy!{ko zm$)CSQR+Hty;9CTUAV`roh9dgPa@e|r)-vN7{!*g5cWv-F1rkz#TIK@*s!me>_lh< zi*-z7%WM&G=PK3y!{Q$t!s}(oqYo;o7vzaKszQPQR%ZT{~Oi zA5*_U8umNV*@t6Ux`?A!yk^k!dTXlaa*#TH&z3k)#*4T8p+c_tQ2LF(MutltXVpiB zP>+xhYICNRU-)yG_>RLc$`@|u-FjZa<|S`Phvf|TOVfR+LyZ+bI{GQ`jh6Tdv~9$ZM}C1_cPN?n!I@p=Oz7E^ z-!M-uT<|EIO=W$hc(3o-#1Dr6w~Nx&;sz(a@a``3{9qkvlry1iYU$+r2v_vFyAe9u zd?d2*f&41V7&Pt0Q1E(458?M?STkxB8S>&O*FSiYZXDELbDIzIKXXfo*YCId@6^4# zyT>zD)HI(?i1ekEdw;X#QF`?CidoiS^eW#&F#lA+7dFM4)1$$+cxt_YJece&?%QyL zpWmfLR0|oIfFVut-$Yj@$&+r!cf@O?$a>7C&6Kpr(SKjuX=}?%f~uz>mj$xIlcimB z&Z$aJ;`fP|d*0-~7MN4*+k{kK4I?+||4O)|$@KcWKsuSOBFwFTQ#{*1ZOTW|%N8c| z)WmjbIq5br_-F{P8C|Bknq28qxfU7}a2`Hb@r!8WXv1~AGI-DQM?f`q6KPw1i$7n4 z`8E2FVXRlTaPgHMt$sC!J$030??NY2|9vya_h3nns~=8Z9qyoGC(WkDza)9>&r9fl zM;bh(Rfj5PsnCS78?Z@5gC13HAb-Ay&{!P<`a9Z}EMK^$BGE6EY*MZyLDj?9LQey3 z*;F66Ajgi*)42zyh;ymd0f}qGS>i2S9#2QLa^&*!JZNis0=PNfB2QFq;)fnj;O!q_ z;6Ym$xz~Wn?O!3l^<54;{luHHh)}xQwoh*nx{y1fD4F0gc399FJ&h@gQ3+`BTk|EH2bV&PXA47BVKPMBEVHLyzOZf5;S<0 z{*IkOJy(VD-)vseb;h-{bfgnhZm}WVBTV?C8S%6@B%Hd$1&M7sT1m{LNI3bP7Tn;K9Hdd1O#9HoSF|kv zk2Y5Ezki1E`nUA0r(byhT&09+W_c<%P^%5Nnz69UNgWt7x^c5(>ty#5V*AD#pM1(m>+=~H3MqG6&fNH zEFwYBcNciQ;1xfpU<;$J9s$Hrnvb^D;{A%WflA>6ezn?IP@*yirk!aMbt>!z?V>R7 zZ)GOfu}L0Xubhm(9#w$hhW+4nv@%?%R}bVP9H3tZ16{%$?#WL(FedyB=bm;R)z5q- z>~Y%y9=CRayGH{nw{+_8vj0 z7Svjyo+H2v_rXG;8dO%^gpb5qx$=&=T-Ct={KSHTsTQ&@>x2eBYC<~kH@;8##}&Me z=_y!Nw-?;la1w6zxexzw{m|&|7jD{u55OVMMJUyM3nJV9aGO@WCjEYtTi{m#6>OvV z7!Oare^LoBS>Xq~G#~O85BR~Q6QsE3{Wm4tKn~2z*5g(EC}^1`%}Z#vWNO4-;P_x7 zf2d!=ed&~WkTn6I@En#p=AU{XZ z5*U_j;Lsj-AS6xaC&``WhB~|h0jpEksL>nYduf0E>UcT$(msR|MOmo%QUG_#7xD?8 z7vacE4QTpe8GKOkk1thp;cgZ3I0vMF^7pqv%kk3Q)1)+5mLu0GZOHxjlFQD-rE$AQdxH`sM*1hASN3npKS1>1{ol^sR`A>B0{U~5zJO!vH zZJ}$;qe$D&QT)VZ^6=T$Ww^~w%$aw|a7t4rLG6rqxHhW^T1Z90hcDE?^riMdt|l?LaI@$eV;K?vqwRwfEji)^sxi0z`q zWgB>Xe@9-!Yy+&!w?I)_wZM>qM_|m5T6!hdAH?6#fCcaOfeCHi;NsW-Djau(v&YQ> z)rYsi!Kf9mGBO-2Jot|f)b8Z$PyN9+KI+5uzkUJJ=oOfAHijMv+Pr_sDG)hy9NZr{ z0g~-l;>?i6{Xv@egmJ9!E~XE3MSth|A_d`fw;?fUZ{gIiHvAE^6i#&21#j)vk~bTw z03UK0{H=b$-OapF*&nLQ&&byS_eL$?!$85c!i5{a$%muih2B3vO=k*RH)^6FGCCl>g$~rRXZ@4gKeJxkwZ9?CF+QDB5J;23mHi1E>URIc1 z-wV%-PXKU04AguJLHCw9P`&dKu74H;t3q{%!cHfkchsFXs+s~bw3d=bm-hqtIaB%I z(1X0!#f3E6avA(xC}CoEa`dp{H>~FAL#L#+3SL$LAn9Zg`SM7HbVTQ}(n4oy7I==f z-B+iIm!+JQP62N^jJcielp`;}U)tWv>TngMfP3SPDHjq~&As;mN9k)}c-`hSvV{0`1Z;~m!N zn?+vNn9<c9-6Rj)f$gaDQ!s^*l%soSe7OKod zA;(XV6sdYx{W6*Cs-8^(ENZ}(%&jE;?Rc{JD58PyZJF_dT+-_ynF*UepqW-hxKa_* z&(G|HWgEh2pW_;KM6;Xt`lzrwe^uDTTP@7-#5)q*-iMbrsj{iIXUGzj9ke-X8couU zs!aIw`JQgCiK`BGVc(b0Q+O3*rJeBFlZv&E#g@iUxxX$L*El?s8!f=Rcc z1vz?nEh!il25qfV=={H0fE?S*F7`YInn?*DblqwCZO=#Ct8xY_bqd((LK^j~(ueuG zFW^t_=g`}`O-b|e#dQ1HA!OsM3Us?gA3rqGq_@63A$~1u=-OclbpE_W^!)E(62{FZ zl2qZ1*DT1Rx90E?KY%-uIf>z3Ne#Uu@dgf1PbH=o`>3(eaHj8+h}R@cXAN79<2mpV z?pPkkyww*FJK1ECa@>ZqAKHx**8Inw#ut!zPN~Aa4~&dBnn+)&o)T71y3F6WcOUnk ziK9=Sk0mt^L&+=i5C%s@hz26XFLK+?@0l6NOkf%HHd{pu`5eZJR%pLh=``!fOSO^n5xokMWV zKZ#Ckt_4w@768&76$6t3G4@^%OM@3#lP3=qCH?gW-1zJqx!JUdCTI_(W3u$+I1zIy(JMm2h}w^zbf{0!8; zuI56e%kiz8UxG>S1Y|YgkD&9hnCqxD5$1f><3lEngjTD|V2^`0-rgk*)m)2ty$@>q zL&YR6X5qqN|!S5d+aE*Dg3Oa+(c z4M3BqQ0{)CDSz$RbN;`#>+l;3O`*x^IS#k`CM+59kGq-}0m2H?iJ6HH=dPs#H|f=I z_xlx4>2({}d0)!f?p{CGpkxld=7gD zPDJ;ocMy;F+j*`|0~U2Fk@w$*TCdHLhCv4}BlnHUaI1eVNXdW3owaM`rd6at&)86q zdT5JaqV*clpjyx(xRA0p3B>E=akef$o_}sQ1g=?a3buVVgrZ6Tod4(}nw`214!s@^ zVX~(6@jSv+m=1>_8b+iGhE*=|a}ZkRJ_Z(-zkq~iOURdE7cl0GGR~Wk0o0o9!P17A zl3IZ9*C!-`WiR)`!usY^AEY!O^12lmIVgi--G-xAK-73LGRfe z;O!f71hpj>K$oWvm=v@NYHe!3;@us>TIUYD-aUB8&DJr4bIh+{ zqp7ugo0F2@karhsop2lu6f6MWmz0Bw=$}CJH4&WKpbz>FXyhd1NUFciI4cx-|&%{!8W# zyjTWmj%mTCvn0M>-B9q>Yz7HWX$Ad*yNTTLvp~$M!O2!78ve$Yk2QMAfBqYfPL@uD zMk#f84tkD{OxetPj+Y#>mo(rIiK};}l%nuR_9kdC;-QJuTJ$MzIB|RoVVjzi^)Y1| zK66B+U|aM}$kQpJC!8bTdxP2Fh|E8bEsO(yEOq(Vw-13Ty~8bAR>jM_YsS%^tms-p zPWWB^1HOP}WOS%6H9vcmcC55P$A7IOM>1v5(z`3r9j$m2e&8=ZBuNjQ={biQ>?ffm zW0TRop({{_ZX}9O^+m6rZbDLIG1@UC3XLvHL?MGyk<{@W65n7ds+wkoUcK9a)&z>s zic0~=Z=DL7Cviz0_uPS8oll^~+;yn;O)%R2sgI{RIw(TL84a3+N<5%}==i-F6r1*h zH+gP@oK?xBRZI&1jF+P_#0}4^6l*3;8-Ip?R4)Xovba)Mvqi z=cc8iEA|LgjSD~<)Ohr`KnBIRb)sL@OA(9s56!5%h!#(K#QTZHqxmK~&_I7GYRNT4 zBR0k%g_Ox?`sWywWw#7%&2>Nu89R`4aW0Z-xqw2XF)9iHv6si7lkTSId%-l+vLO!5ndOV(v)=QQCND!7vnBr5i5>jOw6pwmT?J(O zsfWKU>xf?dmyYb!Fw)G4KqI|9(2H7K znmDwWd7}?6)REfEb?CsbO0=g@;#8G?&IfwNp)r#0+%}JcD4=wJkI|Wl@}?g_^&P{J zUuqCiDTzfN(__)}J(lR-_fg2hY#fUBKaKjlcBAlfc4*H46_*RJz>w@pBo(-mJ^U{>pMOx z;u{|jG#g!TIEYSeS%gkM8HEnqj6>&&-|(L+_aeDh7m&wz1=MAG90hgep^pXONM^qW zy7X}jO4ulm$^+Z@kN5piyL<-PHuNeg%k@PKE2ksDJqA@sWupVHEm7yesmQ(RJQ5<~ zQOoT#v~Ef~(o*$7vT=LRg(rK_>#d{FDwlKUgj@jn(Q*nsiadmRQnJwGGz;|n=oysV zvIP~47x)D#_UJ)zCK@$kA2PZgjXtV|qe=Gz(AhO%=Wb8jxBa*iKZJGKg|WT z#qC2{TRL&cNDZPax0rZ-3?oCA_L5CC1?2w5yJR!X$0}pa~U)Rr^r!hFGHiUcycO%E-ZoH(0Pi2I)RuK>7}i z=ab?DGS@i?DMyVZk82Onky(| zvYTj}NG7wZl7YguKr}s8pTxcH;bk5F;y#}Y+&1JcF~0MPS9j&X0fk2B!a?$KE+Yly z=i%u)i;0neDmi6-l57Vb$jB`@WR+4azweD3d0elFQo{=PGn;Zr$oD#a%+PE2&YW$a zcwz`Td3*_}zMqTihG87NVk8Ro+zDahh+kBd|FSY{5i@tef@~n#RU>L0igCaMV8hrjp~>0C*8CGha2XSxvdY#KZ%z_ zwDB)~JU<7#n^n(#>0Tq@p0Q;9<^9CI-hqr9ZcmzhqsXhx-zz7~&L*SF^vQ&5#2bB% zCZnGDft^+%c!%O0U;SXZ#i>`Fa8Ag8g_>HNvCrJ|}(06L{_3+a#^Nv9i=Q z7y^fIi~)6HxOJK zNMb=WmYp{Yxg4q^y_pyA!odS1tIY-P+OdlCACDjlX6lmA%x3)bKnz(p^)A`@d zfywiwlS%x{WI!ShkYSI#$e2Gqq(U=_Jip>YavHKp*4TV#YvxTZy8EM~ahUwpPayhl zy-7{qSyBiq@Xndq?&z6Y1S&FUg;8CpY#e6QegJWU;9$xmcY{?wMtiYnL1OWedy6M1x5DcTk7- z(4K|-Jsrr-pbsM7S%qZg4RfM)U>!+Wu#IH)-mT0CT#Vl@3nJMD*MZgCB6!=n0w;Zs z#*4aWvL{B4T9}9uP zuUA6y%oLLLU^(B&63F4Pvi#q8MQRi)MQR6B=(#sy(&W0A_=!sRiw1YdcK?3<#IJT5 z@%0b3=)55+RM#Y`-Q~Qq#7*|$gb`_5dzT(k&|CaHW|M1NgUB)28K%UQ3t zbYDm)o_@`ltez|39GIIhTgg1{YwTDWa*ZeIhnq;fegIq9U`=l2r{Yw*IJ|ywGXL9c zI;lJ+ajzm%vZQ@2+5K)7k*ss$O`;>j($AiJ8ab70DJ>+0V{K{ZUn?4E=}CvR>ad+_ z&(ZAQdwAc)47x(voBXqKX3D!hZ~c8 zPFXU$vLbxKT(&yNjtW>6QnvK-* zw<~Gd`kG3QE2Z;m1IT?og)XvFqE~;)(wee`>{jpxVt*@@cCaU8o54HUxS^bIa(&ow zoit2y^CAc343UcNN22*K1e>k>nDoSR zWV-8Lyw55CcSLDWoiV$}uh}2*lZ0sc?YAXSoU2Iv{~Jl_7tUiU?n}vZ>vqE4$k9H} zOk!tnj*OVNnH>1?8)G*cD(5tnw2_t6=s`U7{4s?c8@nCPoAwsVFCI@6>D8oh^!^=V;^A*h zvsEGeVd_p4eDqj!RXSchD}ZJ%FDA0~Rpjf66WHYD1TuYZ4pkaGip(&qBWt}66UD9? zk{{njyru4tT?X^W<-u`8^t4Y%Hy*$b!2)6tC`()mO3AYGN#sNEX>vUK8-5)AlX$zI zBA?TYNRXKlal5sJbXJWb@2aiXTKED#y5>g`%>sz&11A!c@31GpTdo7koI$ zk}RJypZx0GP8=)TNrU_)(*1fpb!+Nmbw*r zx67OuU7SuDybjWny|$#qqK>4B0%>FiM-yM3`2TF4Wc~R>thcTu@8j0c!!~#Mo;9QB z$MzgzH1Y~QVe5n!0#m^;(}JMkRJ76ITBUi}Vv$DCU176@pUCaMAYxl!@0zKtsY^?k?`1U5AM*x453#_20wlCf*-YKn#gqQ=gKQr zRN$o4K(s1;E)4T2uBa?5!v`DWu+P6^-1;HW{I=c_;g5>~o>Io+lN(yWJ3k*Gq*Im@ zSAP(evU9>lNrv+7Q#~@yz?*v>=p-0LD5JBLFGc(BWZ|)j^DFEfy+MPFK{- z))k5N841;=P6;PuJB2>AyEtr(Ht}uzL0A0^2Qwt8$XTO&%7^MEM zr?yF!3|=eZ{4Xb9?+p$3hk_Nmb9oUyQ#F+=jIa@k+iI{?^;CRjzBXP=T5$LVKXR`$ z1+RTnLx;Yy7U`moqNX3gqV!97qH*{8%Ae2rg6miL;D7DW+y?_gbY#O-66LXp^K~8y z7k(U#Z*P$y>HBlInz) zn@qW}6HJ7kSuOZp+g$Fxc{!eOtGA-BR0r3hWMS-LK$1v6z$AOym-6 z1Gs>JVZzKK@pO{?cp=w)0q3;mzly8|2Yg&zht|FOh+pN60PnKHR5R>Ga4=Sp8bWEvuSnwPf@fa%w9Qbn+BQnap_6oco_E?%M)V z6A&Xza}C4;-=~wC1%7yZjv~Llvmf^_qPSi5qk!KGC-=6f68jh3*iU|ya3$j?PLj?L z@+zY-`{ExL2Md0*g*6{q3**)GCs_d)KZlMB(-tHDn#x`dsbta9h6 zkD`{B62^F|7{{w95~=b)a(9~xxt#MDKWxduqZ(HdHB%eW$v2D0S(1*|H$e0>PmDDt zK80^SKcGj@3Yc@q8G`1W@b#-VKs5F!Y>iq7^LhdiO6`JXJ6+MQC1ZJJb{XE>bOUbm zFyRl*EQWJW%cJ^!H{RAPk9FzFu}7k9@cEbunD%EVzkRX_n`0t|AEstQ)%h88equRO zH@RhPYWE$!99qTywYmvcI-P@#;m#~MrUI@Hps;G2F<)maXOp=GgIZZB-oGXiM?Bfe zr)eEzyY>{rq+CB({`eLAZ{#&rFsc-Ep6CRIvv0txy#;U$dw}P*AA!%E3*l-h2H!r5 zfrZJ+{GZP*wAWRU*Z6MCQiCPFr2}hdo@N+SSapXFmUz1xS~kGQlkU*@##MNi1;N4> z4`9DSI;rxnXG=ajg2@loA$nmHKbSb1=OYby(ZK}tw@{Uz|9U)Hu+bexJPF0a^S|NS zmoi{vz6yVK_(Q0WXu@y%S_5D2H02NVWI`eI5-V!`$NkjNhoRnm7?OztL+b2&|T#0C;;*5OgOw-}(8f&D#Xdygcw}9nFxxhoaN5R24(!9aqa@Z8E z&%ap^0S!7+K&DGKvr9h6_TB7Bk+zwgDb!kCW7en<3IZ3S*1>V3z}jv!|rMu#7P#>O_I^E0QiU7fjl>^seq|<~ z{3Z+e)>)!)*Vn>7%Z#C0qA?iubSOP^>ov4hGJ#UT$}meO7k(-uFk}t5J?b=KD-yQNW%5=8%ZvQE`@tg&( zH_-=5KX!#Gre9%sYAD=K@A1oX;-RrZF1WoUj6Za9H=LYMMcO~slAUvQ@$SK!n4yvy ze+hRBlQNz|S8*&n`lXbxNfK|)r-#64>j?hz>SB0IcO5_RZUuMULdp7*41o0|vCuol zR&vg8g?@2=2Syz+z$2gU=g<6H%8yxD3+K9xC*_yQ;poG+zVF-W>c#MDBwK1d-+jV>>}t=iI9t1rst;LAUY)v1XagigYgB1ma2?I6u%j7|yy>H5aWLyz zJ6@B%gqXhn3NwSQ6ZOIY@|B8-`E*&{^u}afLBhEVmA?lcZ@Y}w>{cf%RgTW3U&))d zH}T@g1L%ss1>G8@O-77;PTx+uNRI4}Bk7GJ>0n1P(bfwp+6EbiW{g$Zz{f zR;X-*H&%||8{1Q1n7uv)@^K`}oizu^x0=t~`DS|>%h{5(-KWWu@xA28uJL@w#~-+F#sSc6ltK3XEyMSdCQ|)E z8|c&%4#u69=J)Pc08bc{lCKRjSffcaNwNAueClNBSrQcf(fVzM4*OTc?loH14LF zn`>~LWIulA;%0JvOh53NRYa;vCDO4)o2j06JbXRtHW+b@hb{qM;mRYF-CdVWnvLI+ z2iJFyw8&2KdeHzcbVv(hme<0q#~^8QkFL0+JVV$`J{uzs__guu2uwqwUoRx@-isE5PIO8*+5zp9s}I8|e#DKp`5|IsYF z^(a$T-Nbhs_rs~{`rw+CT{u@>g(Sb!<)5VA6UKj-PoB*8C3V9IZ$I}k)=M@e#hN?F zU7rjhJ5G~TAN@|2N|>2)kFIg&OK!1Tso!|@#CK%kDj5>edV&0WDi4=E4V2u|&p34O z7alWe0n{qC61Qzib1=zT zAdA3 z03^Qm5SH_h_@pyukm_?1R_@poGPtc8CqMXrT{Z8q3r3}MM#pzfJQzXmcN!#C#VWa92$dTq&uz!mURVdZKfvlLuot2DzlkW;EJ%aF$h3zEl zb|mS$dk!l+e1tb-w9)~`#{W@t-tkoZe;m&Y8ObJ_j3|lXp7VL%&_F2-QmIHNDU=pU z_9ioAD;0$#<(|*yNJ9f9i8N_RgNFKQsNebhi^t`ja~|&J{eC~+uP0UL_ZAyJb0=0V zH(Ay5H%a%|J-kh^arI$Z~~V!>thrx)Z)JI8e`n!h9g}# zCuHPNY~X1|=XR!`__r<8Ri|@|o85Qj(~VVF`dtq-iQ75(7M@2WA6{dQ{Y}9Q#lqA% zF@3yQaVb|bU&0l7#@OChmVWp^ims>{U{WV}pn2zN8S0<|E;QxgU;EApUKXaa$ICqh z8%<=WSD8X||HO0NxQIT>SG~>@E!9E`d+K=+K{_}@Or7Cvam8nbb&0%J41NSgc)>~q z+^XZp%p3oWS7af)Oehe0sUF3O=N8Z=Hx9G6HgvM$t6XuC&Lt*zU^Om$T0lPCa=|_6 zF}#1nM>s!X7t?)jEi>ACizy4rB)&m`OnzV=ewDX?UDJD?*sPAlW*?pK%FD`Z_Wakl z|C1zhfHxHzsEgqr?ttHH$-**5+wo%gOlGHP0lnGj6j_(pgjGLTksJSUj`;RvB+KXp z4vbsGzTW*DYuZ~uQc5PYN%SF`U;2<-)PW+h?;><&49Oa+LvG1plzmMFx?Xp|HYGmj z%05DUXEjmirC+E{w;5?({fnNLmczA^L13fojo*shL^(enBCkI}@G!I$*^*~S#&-by zx8@YoU3rg|*9XIGoe311nt-H3Um@$cok%Fm7x~wYqXzdewByM+^im@goarHS!^#U1 z#XqBKAGP8BY8faf4uHtnchUZ`5#;zRg`V_wh}&h`2uix|(A!K!(201AVBvpg&-qX2 z@y<~cb>k^|`A`^A6^)?!E(Pb+wZOG%FZsSX7s%OBloz)bRKAEnQ2jr2L+U+R6J!Xb z+RaGo>|D+z5(5%sBXkzX!gY_+uvc*$>B=F1!#d!fdkx$%AJ%D{gj*Y}q2Nn8o3^zI znT>X$Su4MxOux^lX!8%$7O)et_n(3P7C%PUc#hm_&wzN3VN{L%vEq3dxYpqdX4|im z##P3kvTZw*yb6WaogpA>Y6N3nZ=u2s+&rja3X}-cz%NS$JPq%ng%2lz_Z>};tl+_v z=07MrOn~&4*C9Jejw6_<0V}V}gkK9%;h<|Gn&0~fDTv-ge77MqGW8ouce;R-c85Xf zWF;{2H%Fl>pQGWtdNh4?Ff2Im3N^4@NT~2GItK@#K%)iC8xSDj!P#&^;y(KM+a4wv zUPr9Q5Znuqfs>+k@Sxiscm}R;Z-x<^37rh?(|(}89S%@d)`Pyz-U5Y5%OEAZ6$Oo5NzMz4D;_S106pJIHOyQzMgu5Jarx;%SB00r9Fh^hxDR++h^!@ zMH6axIUOY0o#Ap*8``9B1M%Y?p!tmgDA>}9yq8Hsz?Lp9H%lDuoM}TobS?_{lnS33 z#33=_D6k&A=t{~}q*CVqg$^Ik!3Fuq54pl2c9?svG(miuFr0an4Lz2( zkX!5jzutioN4Y-l<6d;?mjWDY{EJ@pU4>g;pQEv#uHYci0s)NzW)uAht!b)4+lD`Z z^9nmy``-ifarSGp+3z0u{AUv^37G+&0TEE6y9tCu(m>f*3i@tnfjqZ+Yd)QGw=7u+ zuY`}Gmv5TU3?Vz1snA5_B0q8<*8qQc5I`eAH|M@^!mqYOGR@OhVQ2L^6p~O%r*U~G z2|l*8^0*Mrf9pUjA8cnPq-NnQe<}!X-<$o|O`V)-nU^WihL6b!_ za!eOi!7G(iv9wM-WAjV2Wz&b7Sm8xHPVT+Py!gAOW!|Pv#^dN|X5}r+SkL{%)P_vK z-DdX$FD6;znSrHDn1l(wv#^&bSp1Q?J~;#LvTmhTSnkAGdXj;hS^waFio=|5G+I z2RaAYo3&gRR-xQp8CXEr=FhX86>0^;k#w|OV@6z+F ze=rxOJ|w5s?PUy||1yum88$4)iU%j;T1w$z z#m@2>Get}Z%UQ4CdRT6}4zmWPCcc7sv9yF)P_m{aTS*k#%&%e`M@vvxOC7sl#E}@7 zo@HKe)#kLMC?+vc1fSF89JOE4XrZ=M-$h zal>=8xn9{HF`TeK2&Js+VFIVdPz!?%vDe!GwttB*cKCdXDV&=?r76kae@jw%Yi4hy z+uubq3pJux6$K&Oy=)7ex?2(zuF6Jsmi1)dhBBVzaS&@gu3`erDYE^JCwu2%82$*^ zXinZZ^JvyN#yDNCWm?EFrt`*sOsn``Mys7Lp$mq|xi?|VuE!SmNoLLkZ%rYB4}M{u zDCdwg*_(`BSQCvKUNhUZ?=eGqi?D*B55Boug=qB2;3ZCe%{r5c7%`&*l+a=urfS}Q zf*%egOi}zBW}UJ$_VgHIiUp43O_2cANg1=i@DD>VHS7v!8|n@7oB32<%Ph$E zW9-en(aX;&_`rhQXup#%t~_DROb?7>f}EXjm-RS4pJsubSG}P^OkOh&>}KE#@1g}0 z4~=jzI*jihnMY5$%%e8$S8Cb2lR=Rclj+6864z9=P_3F@nXdLT0^Jwtcwl}B;~AC8 zIKFkm>sM4WGCFp2Uc^%-yYZSJcF@~)=GR+f%Y}JZuObTz+UnVgO_h}4K11x(Yl6ov zZO8vs9;YSliec@DYTV8_{3P?+sT)BHNW54cBd+C#CX3ZD@1torVLph?SHELt_ZcuX zb4x*E`*A4DjfcJ83cxiX7=rZ^;G>vnNfbnl1C` zdAJgq1k{N?per2?I;T=#^+jEzm5>aN^u55n=qwa$Xa&XOBzW#!0=b`4pr$DazK+a+ zR=N_Lsw<(ZBp$-c+t>>UM?h609hB!qflFOHteVII*OPl-+v9Nf8@>zpzGYDB5DtTf zDj}=sI6UkZ;rrI>^YxCM0zcRee?yX>%P9)p>mLCAl6d&tkpzA|39xW16Wo4fK%&JT zw(3qY#GD9#MN5xD^Wr!V?~I4}KaPV;ZU$_a>kDGbl0kHAfb_1p1d~fs;J8>Oj1)z| zvZ`dz(>wu{at#oHb71EC3U;LCCWvT8!G|tUes*jWOxm9eGqTnAy`u?`wC0q}6LcAV zKZu45{{(nb77KQv-JWiS%Sxwfi85#|6N+K{T9w*T%~5 z4!~OTQ247^138bfU{2i$*t4!2BA>>=9h)PdaUueiik^ZGx65I-PAJ^8s9i9Ib+`q*Ks18Ur&;v!Uz18({D;3idB72HyAySaPQaEY`?(x)lZxQ=(*lq?QV0h{7P5Nc(jfC78D<8CflGc4h@5PIYg(0X+%XTtQ3|x*i3Dk_ zSeTx>0|uwLK=zF&h#t*`ipd3_E6O?1zURQCz*;zOb`z#(<-m%KiO`9->rzq*Du=Vc zgZn+V+{586oCE3GN5R$I4<7w4huD%jFm(xr`^zpt-TPw@R&|uEJ$@W+>&gKCvIq2y zuSS#p{6vT2#%SlGE+}Z0ITHQ+65R~UL0N(3kd}7>%q%{mhZE*#;bbqk&cA}5o5i5q z&m+Y4pB%)uT|sB>YVgkmA3=q8>`^~+3Tc;euDrBBG~VrtCU+kvn zM5wXP2Ir##eNiayxg38KrlF4A2hc-(F6ZM?FQhKKOD9X)fb_x<}GHuU)c1_E!m7 z>-q#`3T;C7vXlYVXruP!caZDx0Ce?bD%!YJ2Axh?gmg`<(A6)J==RAP)aYl2YgHsCPu8%=O37PhV4XnyB*KbZXFK$ug9-DGs%?o3NG!e^LF15;S$V0y#$P zM&D&)(dHlW;Mw{bE!0dwb+2Wh{6rqyaI{8e^ctaOd<`1f&(#~>il8yB7M<%UMzXwh zV7)Ppz22UP_D@-dI{NM5OS%J^CP(w#l_$`bBX+1qwvV1E`3m_3_oB34I_Q+t1YKUS z4mGInL<8p*q8VG$(XlBxP%(Z4^6sUgmg31sD&h#*^uPkmh<%4f&1~UoZ!mJrvO~}s zkM6$bBTIh~IM}R>_!d5BnU@NR>$;6}cS@l}4e@A1O^9Q$rlSwTlCW;aVkCNjd*;lY zk(P7|LUJMyFuw!Uw&WnS*HSQd&kPi~?F*iFjYdD6SHRR_RZwow~hFoe@<_IDv#W69zi{-SJ6;NG(VGV zL9cdhMi$@CBGqLwaH8=DGSQgKpIb5$Ra_N;)}+12@In(BGl@ZmV~(L0?&|z9yQL_8 z>Q_|veI;7o*M>~?l_P)DisDpvqDvw1sFlARJzb`PwD&iI-+o0Tzqk$^yi|ln7Pz9) zjbeO9pE*c#wF&w#Nges`l0kCikAX6ZLR+kr`1Ar5^l7^z*Ef(v<St;Hw=9!hfSdW`;BTc$WrS^TeU;u_z=uZHA9C_CT^! z0=&QN2K~$ga`P5~1!0lEiWNd(UJmfDmcTonDX6QSg#C`bP@m-hHU|#Dh4XUIy44$g zrzXJM58FYcsvIn%_ko_DDcBY&aJZE@3<3F}NU%Jb4~w+- zg5;Cw(DWl5Vk)wsb($)OtFD7fu1Vk;u??pFa|GQ#JlM2W6}Ctw!PN6*KwVA%=AQ&S zI&>U_gX&?w`cXJ?XFv2mO@*ejNl<;D0%jgkfx$fq&~U;B%;v>H=!KmSc_ao5R~v!q zlO*s)*|5X!Fy!e^hGlP#!4iqpaAdU?ye;QyJ>&VXm#Z~NuQyPi?Lbd05w@LO4k;RC z;MMB@%M>jE?~#CQQyjs^c^Xh#HbUFiBnXWy1q;ocFlR^~;;#6>H}{#ar9lyfU;2SU zsWG@7PlsxkI`lO`1>kHS_dJP#UyLt2-xLoim)F5|*(lhjbOz4F`a|5+X>fF287zHv z6!=~nVB@(cSnRh1b}RY9{O=24Y=bsDp6Eoje=0y^%5Lub5e&W4)^qne1$O?s2WJ# zQwWGW*$GL*R#4}AABDsg!kZU7@Z7r}#zH)xEzA|RhlIg?drx3(Ga$Y{8XVMJVEy4= z$aiioOj?x)tZ5WH8AK3O835%rYM}Ss9K1Q^t25sMvds&jyTuNMMZ92kaV7i`PX?cI z7eE(-pyTCkxISPFLr*kejEaD&h<*@@FNQg<5`ZXK!YkQQP-rLw!Fqp?w26a<>Z&m3 zswuRd=Yyu`VOXQ*4C~({z@x`%P-7kp0fJ2U^jjPGpW&hzeFeNLVSakGiT^O%KWqHIHg z231;4s~3?2LcO?B-wP`1J^jCHB9RP7wS1S&;(4@~lv!Jts?lC(jy~E>v zlkjl4C;qXw6sq~r)VW?k)cFgS?kXZYiE+X zCm-Th<`(2&a6NXvA&g%eNt2d-1LFM0m?+K{K#))$9U@(WMcF_$Ony7wz|GID*FS^C zwS#yfwjSh1%Q?nQnyp)AgXQO5##fKTk>T_>=7o?5oSQVp{KT2~ua-4qe&I7-cUgdq z^=IOXcKdMM{_AvWS1G2wZ{o49xxBRYDJ12=NgOMD5px0Dyn>4aJN?wgZu476&E(s7 z;R0I{U3!NYoiHZBWnNfv*cQ9(Z@@ZUckqhk2XUjyQQo?PUHGB;UA)I|2C;2yz)9ir zaht0H-f&)$7&ZQ4KIC5FnB7RqE|lxx>}kMjV-Di1{xPat*Nyt)dj=bf1Tqz+_00V0 zt@NT=Dbnd`PZY1+#>f7;^FA5fz_*&cN%V?%;_rNnS+~NRh|V*^+r&Sj(&cv;95-L! z)YXW$XdTDyheF8Oz9{^0mputuD@wTWv#p5@#BY|+Oun^to?uDuD7F$l)4 z8;poU#XaKPID(g$%_YB=U1KE7Cy_5`g4yEJf*m(K$0l{2_~}!QQ{b(~5>-(G&yFc% zft&={{X7e=>((GIB71PNcM`VFtrOfa38bm&qxjt7Y9c9g5>Km?CZeL*_!No5k59?q zhb{y!)(>YK&RxXI+EQ`V=Z9F`RR=rIy2l*&n1rvW%_V8Mn&kDyQR?;Le(#bGV@Bf!H(q{{94=%8^qt@Jmqt7-l0GEK>K0bp!=HSg`dGS9zER8YqBF; zCj9b5?)3v$Qsgw5%Vxfe9XI^LD=O3QXn`G}YW=W+_8Ea@Kr6obPm6MtKZ?IM%d@T~ zc@+B1W28*?;jXs1r1#u?9O%#wUn^Viv(W$~*qw)}PsyYAVlC)G+ito&UKMv((!?vb z6o))M4>Q|J&=2tq5WHa{3KTg^K0n!wrHd{ivZ4aNSa+uWZVMruOp_&_iA{{r}7nWa;`Gx?AVGv+~oqqr|m=HpR8LpwQQzO z?Nr03uXrOR$GM<$&XF#)^F}V>Z|DuiZ8V1mvujM|BNaYPPhb3j{t)Q~AHR1~_jaBl zp$CsJ$NEl^S=kw%mIrWHL)%6~xkqmk1+=mPEQKa2UHC_23W7skIn-8ZdHi zRmq{B7s=#(H|YH8_pA|>UiJe?JnW*J8xqh9dtGwS`7IGm;~~SU*T{e2 zBf5OtpWS5Hie|o1z`hqV&`Y-!=-=!>TJp0{i+7nAI$fR4=h}@Ztcs;}YG~6lUCz@g zI~~BqMU^N_9VN|03TU$VW~{c*lFX{UP8a?2qNVF2$m>}{99@8R-6M}F@hRtv7v zQ;l0tiS=K!@=r1|xml8oNzWH@GS{FPW6%B?Gq&**eHWtug4$INSp$fcaO*S(4WNMRpC7-^a>OrTA zOeQy1xFVfLcX_QBkKo~iCf+W;57f$uEYx4YM|Bb}z_Y2H6ufRmGWR8j@uym%I7}lw z$9(j8)?Sp7yAQdzgrk~`VyOGi0{-J!USyX=7|~a;MqYZsbmJ8xc1qbAqEujkL>VtK zASk9KWlFiZV=pqdK2GVVU7%kE=b@7kQ&CV!FP3MvcX`aZeI2L7wd()CH)6s&cN><+YFr7KF z1SuEVkow!FF=<^+N37jPH}*?mty6M1e7_^T{nRq1BJU#=_*{+4ihRv1YoAU(8Em3m z_6Or|HEVQr=mB0T_alzp zvGibz^L-Cq^HCu_BR!q(9a;^~tfui*xS3PAFbf-w90RROqWqg>U*Kxz2hh0k2G^Be zgCjYoAz`jK-#zp;sMvDzi+`h3X!i#=&DUYy>;8s`!gCN6cNsMEFT%Z?SxD;YX{bJ0 z1y1#O^pelx&|DCK28|y9EPsL?M0A4NDEI&6p9;VG+W)=x82$_Ghf~om^v-)lu==Sa z|9L3qX9??tuYQl=LbeS5Q%*m8`JMrjl@EYbO$JP`dVKw+vtSVRADH>x0{+$}SgCdf zc+q*_WpWvE?_Yzo996#Ospb;$ju%nuN~3E_(cF!<&wET*RO4=cA& z2=S%o--Vkag)nVoGBaFJK@M|ZI0zPO+>h6a@eu+9i=Hz6) z+_p*V7AS_&$ETo2S^#o8&I48u=RdnV9cpjXz%QkCq%z|;pqXbNC0T*Lbz3>;lvTo_ z5C$5jOu(v(C%L)&7FMN?HD@MhE^~*^eMT;)0}1$6;Dv z2c)^r+wf|rQvb=NwA2% z0O`#Y5Is2xHKYpj!y?N;s^=PL?kR=-!A`K*{}!xTl=-v8=JF?!4ftknUXv>Q_hA2X z3g0lT6>e!(!{U-#=n|{R|0(kZvaByKUleGY-y6#Xe`Gaqf>M^CD!;FJ!C1fGdTuvU zyuBIwsLa95b_uxd!aHWpneT$01LE|{W@ScknF!_mR2eT{?}1J7b}-vkB%vea%7UxH z>CAHL*5>@=X|O`elMy}szWJ9P#Y_v(fPonkH2vtOt-|UiO8t);@A;QUyo#yP@cwOd zvz=E#;ZIOK&EgvVs)c?oFGNDfdYuG5p*<>NSF+9y2_bYE6sy`*b=LF~ymnXx9 zg)=rhWo8vdOi+WDAb9YOV5{-o<}9*R@IG5xAXz+@5x=6&^md6*hJW~ErJ)bf@pok= z6g~?c_Z?eV@WSUEeH-T_T6Gt`#@qh$5!fqKzrP7zRa6WlU2?Db_@b1)rwm zFiIN3Ok#G9;5)~p`$k-5_Pyth1yY!{4-92=Z?S?{x4pI{LZJeejiSuvhJNO;nkRlp z1cI?hYk0co3bSF6bMwe75d>EcF@wPyn5PSqk#JWZqxQ52&U5q5m^~D(R^#|PX(>i- zU@4J`_ zR(NM(Ka<7zf4yWiu}$tG=7DfDz2#W1z;&vCxq0;)Z(rven(~ZhKHd9d+tYeR5Lq3` zycG_%)tKSORJ@)`4OQ@PZqYu0LYgGg_ID$!${rRt%IPr9;W5o)zrq+9@2%vq;u%Ki zMxU)>{GecbP!j)o`=4N1#W4LOktf*ioGtkM){bfAl?&D@`ZEXOa|L#JZ<~KjcWCbP zUL+VyoW|7M;+*0)o-yrI8RHfs%8)5wJ7wo~frNrMqiDF4s_K;$xMe9a;=vuZLA>M5 zg8h0h2j6AFBX2kB?))YouX6;pN9^fLODA4D@uF*SjNtk6%YyrJ6a-#|N(;8v1@(faWLsM|8FqG-v*vi}m zIlQdN62H;cWb*a8m_;6DjK;_^#_)x_Af6OplWrm2MB5$aSLR%@WokLi z3LPXtV9hQ$S4ajH4B&0nf60|qzQkhHMSSm&6sy1OJDKP*hND}|SpDuEoG3b*ggS{6 zkqe3~&czttK6H@WAU9Eg;||um`aHWTWis*kgg{niH@VrWMf$CTSqbC!Sonn^DLGd} ze#lj_u@9bL4I^~|nU3WA-`S-7Yb8B(#{;tGs}Y&~c?{3u*gT;(B^Zj!$R^n~eEsMl z60KuP_{vJ8z~d@5;s3=QUsjRMw}W`jjuqsyK{+`_r?3tQH;Jxk8D7!I;GZdbh~>?l zMDgZ#0mIeqpQj!rWmfGt>rXcM*XK!We@KzFIVQw^XeN8c=PRCGok-?RY$6?pO4*5_ z<3#4S9vCd!Ms##r(BevtdmVDbkJCG`fBk>>Ox`rIVpj|C;B!pdF%1HfyxHX)6XZ=& z5l&rW!!}LRBk}bo$fT8FjL6JN(i~btW>!>_Vz;@lrcj>@UH^s?_0rf+-6_~Z-o2$P zDVKaw`-Ky?suA%KK2f>DBa@D?c&WrZY~Itz_U-&D$e+`7WEZzrwk9%(J*GB;tf?HpyD3>R>*jJCvAz$x_c)VJ=6!gv zdOa5O{>8nwhJV-cjsg&oR7L_$UnBc!-Z% z8J+bQv9Ciy?l>%83;(|{Ti|*vJKnctY+Cxf|riU zvf1mny}%?*GP~{)ZW&uhZk#G4hfL&2SFRHY+Nnm=w3CUDgf4;aRb=vVQQ|!77Irkh zjSpof;(v0OZ z2LxjCh%$2bIAdG2&Yabpx0)Qs6bT@F_LNf*3EQ)heEIeO>$WP8hj|Xd1Pb~FqlKO(pAJ%0L5lAz!=qWZ`MMqRJ5*MmEVxn~L#+kFIsbX910%s@}R zB}^@t1*Vxw5at*Tj=W3A^g}B=9h?Tg4%NWjA36}0F$Yp&cEGcpmLM|Qffy|n;!6s= zQLI%Qt9i^CT(@XLPKP?IY1-dXH(&`5)~dtCGa4|wSE=QkuP2{4uLGMqXTTtGE;Mbk zfz#KOS~6^GVMmocOn$Np3P!^C9}~qud)EluZk+`)b{v80EB~?SN>AYqH&=?`vkf@5c_f7dXTur2mhqS z5TYId?(>ttv|5#a^H(}Nmx)7?Zw|m#g@>%1K7#AMGe9dt4a{x%kfvt{nJX^ALh2jL zdSV9`&ULXVi6=RJO&a#D6GyWneIYij86r5ZqL=MSC~^^Pak=^#Eacje;?R0H&gD|P zD=LNro(U|)>sk)CW`Kc#DV#R71Tr-YW+@zlf_GZ5s(l`)?-6TpxjzTY-$rw8j$SY; zJq@2nOhKsH0>1wehruP*AQR59n>q-VyK>(X=4Q_c_t;gFSHKBTb%@$=1llOug_Z}D8P(o%#es@34F{7D$Es}31S><1Iap7F z@bDDaA}b4-#|%K@zX__XGPv1mRhj2#5x zCI$l)F8re+df=YE77Av}0CL*~_9c{n>3t`#J1WnQU2Fh5^rl1j`gjnzyB@|LZHDK5 zEnvdU57lzMv;OYJVCP&8!oqg2=IR?TeUbnl+y=nxrV#|>M1hgMHN-vjfP{65P^OKc zX@?Gc7C#4d(!%_x5gW!POCN@ctf2hH6)66g4Vq`nVXLkgoUxb+#!pIlgsiFjTG-d8||4 zGWKS+G0S_gieaeH(VQQUv>F z@o9qIwz2N>LW%onQP!yW4H4ch!Um-ckUj}j_Jy?^EB9WBUDR`ubhmU9vYE&BR9dmZ zFXPzQ>Cxu-+-CJ9 zuaTtUanjOviP$&>u=WWStl#J|_U>;LcEIE>2|7HR4RKCpX^AOpij*;H>s(LTA1q~y zv}9SU+!``{K#z^MCeP~qYhk;+^4OUj{p5j+2FrSRu=|&{6S4i#?D7rUSOece)=V^w z&D@c}8m&H0(3H7s{x?r{#|}+a`fmc8Hx|$CTsM=gX?{p%D=uO+imsC+*V(K>mlbOl za)^}^c|%mFC9J6F2KM6BKsNSTDZ9fui2cZ(AYn0Dtd*@XD`#QI=6(N8_Pz*Yv)-}n zT~&Sd<)pbBd)ZBbXSNXSJ!NESh6mf>slcYVXtARmI&8LVBwOGk!tOn?p1rn&^R?;8 zvqc%{q}D8$t?EVW^^JeYZ=EGf1JoRrOanvX1*d`tCHC5#c8bnSS=Zj z=pj=Sn+e@9N=&x~uu&uOY{f$x_Vc-9c9x7dJ5OdSEB3XFJ#4MXI!PTR-TMS2=70uU zFguYwctL^P_v#qgZ=cHk@z-Q8EOce3Us%faDADYMWhCoqq`>mb+*$O?iuH-OMiPf6 zvla!Gtn|&FB=55qyZ`(N(k&~;mc_1M6>7@JT8Bq-65ayIBs2wRi8nbr3nAg8TWSS;zlIyb!}IWf!Ft#jwGDV>U}MT;#fOXji) zS54W+&t|g^%mUaa=fX&XZ6yhr^qXXs?qvUVyRs*K+q3T3Q`mVDw^&E%O7?unMs|bL zYWBv3UJ^6sIO~$OoPGR5kv$S@%bxzjUCXiU?8vb<L|w0=5NiM5nJsk)jJV7zZ`* zI)7*J)^VB3sJ4Oq^1Pc0>JMPt!d70PyttW7VoH(Y9>rukL*cC$3;0?|#on zyWXb%QyydLI*wD{tRwKz*!z@mKn>Gdev!(y5yv>hmG)OvLaPs4raPyqp?@aw%;S1o@3>4|lQANDtqhrQl`+~|+khxFH&8mVdgR~RN))&*{mQnF#dPO$ z)293v$LQJiOX(vz{j6gDG5SGDG2_$V!@QgIgSz1FDcBZ&gb_NI%lO~xAv+dC(e_iH zGd!qD%ZEq<-iw7vlvZvE@5Spqf=!Z|REBFh4*h`p4QO1(mu?+fA@*f`uW7`C8UW6?~pT)>dXQQ5s5U)HR6ix46a%=|4b+J>+rtHyws9 zFy&?pLsW0qD|9y_4gVGnzOvN5gqb5m+4}4dhb;>iQp?3^s0U^*nEo<~4(j}Zda|}* ztsDw}6uhK9$4H@tOEU!M;9GR~RUx%@Y9`Ya8pTT&qwspiROXj?02S;eO7)&{#M|Pw zP^TWpQ28MzsW`YtnR^(}8fUzi)PyqLQhOP07OTOq>owru?k48C&r?Qkqa=0YO*1q7 z?G1W0Gejkrgb5BADpPV&b-WSDRbVzSiODVbjMPSbY*T?p-;Na%s0p-j`@m_AEz_sQ zIM@1mlEIAV&!%>>E0K1RC6^m|myv&Cjz1bs#XAm6W`o*X;3P+StGXNa5+V^wG5%vsjCKiliUe#N;gbg5TV4 zVwKu6=-sDe@xJkP=01Cjx2OIw{kS28u74qfrdIExDddY@Hs5R+ZCEIf|9Tls<6WU0 z@~+Uu8$Yr4hmH~NmCd|3mA@!>izrl$+Q5qMQeZey33=ACP|ec@KL;~7shI-*&*kgA znFcot*Fxf2KX?qOuwQsJD0$6+^82mu&|x8n{Ks|0ooZm?lQ^K2{2~6ZA7tJ?$@M!k z!8jlRuGMUX9r?kqs>u;#IX?LB?Mhg(I~@FeE`h&Qkr3~x4DE-q;jexUY`d)sJ`%Rz z{Zj@0#R13mjw9C(>)^fIAd0`e3%2MvfUDOfxUytD%rpxGG;J2t@3jG$Rb}uhF%^Et znZoL#7}z*z6I6UV2vqn=IApO9UJjiFDbF&{a$dyoCSefd77VYuW&_o<9V)#dVUm6b zoR(P!o?OlFPTL&zBuMk0Mft(O>xy7&6#~^KWS~t}4rWfu<*vaIkUAU&5nohca2AHb zJ1f8;-yY_fB}34reK2s@1&&9DgAd_>Q-1~s$}$1l=RoJrQuy?`8Kjlvp=WwG8Ys90 z|Du+{w$w~W?xx}D%SEvHS2SEIO@`qQ=TSuBL0G(}0){T6L({xXz@9b%w>J`yI6D?H zwU&T*A?K4%c?8y9m0`!3&2aly5%exH2Ho)4&@s~&^b%d6uzfT9u-pKK^SHk$z5wXT zW#FhA2=Zz%;QwSD=*jDW^7KMD%=O{#9^L~x#${nfNEDb_rbFevDWLjt0?7xtz$Xty znD%rjR8QXs2P9hqPx8RN$`ZDtBq;B9fbc)Z;PjzIP#3=!_8c^ZIIq9Z=Jf+jet!tOy0}_rzdclV zVJIkH&h53hLl$iau3z4vgOo3%tvCwP)`vj&NDMTrmxQ4eb3tTkB!qIDWuDn?Xew9% zeH|L${&gGND{n-;rc1#7iVif6Wk3bT)Z8rQII`+x@I7=FtT}H03*LJ|;G9I56>I~I z&ZQ8c_8JW}NW;xFK9G1I9ln>B!lhDU`E~6h%Mot5Cbo2%6&mi7f#XExBXZLv1>^q3{r&dAe z**wAO1-kf7hXSkZ9Ls1{aIGcfD#mCi)kcNA$4hi4Fp~6%r}p6hQD2fziJWy1{Oz)}t-6&)E@e-l`c#fn%8m`T{5iEet;Mqi zMVlIVel82Bj<3p1|JH~wZ(8DbTCdMC*R-;zNy?W=-JDY5wkwDdAGD-Wv_CV6V+*OB zVqVmgH+8)2GMcpCf6JNnf!92rpmWsxR~$cYpF&~d%TyYiyR7FBLpdxVlv5s!Pkhv) zPR;VBuHAS-&F+atXGZe`zLNWFN5d!E?peK&iKjFv13z^tLt~hVI{2ISUN@TSS4-f4 zE<-B4z5t&p&Y{K|)2UG*4dQNcfO_ZS4o?Hps1SEPR?;@59%)^+HCp$d;G6G!YS`L_ z6>J;kh3$&9{h7w_d{J`SI^ihKm~GDsik?=FLayZh<(wlb(aO zoEvZUvkPauT?d*4zQw!-GN18noKES*o#RO)xsf?bFWDZLv9j4Me-m~7&t}2ho~eS| zgGqv}y*^asrKdcB?L|g{1MsME?)lyGh; zwQo$CdU~mxdQPfwZl1Hvk9R?o=Uq#x{)iN=+Urg=Oy0*Fay)yPXv}21%-2&3kvMHj zRxrVF*3_OKfjp~w|C%RBy8-=IR&Y#OfDqNK+*c9!=Q#bRX zW*b}O z@LS!fuALXCUEj~lmpuMnFl+e(L9m%RW$n#n#<*SOO$`dLy|wl+!)7zQ^LfogT3erb zp1VU}R}{t@3OAwhPUcZ$*<@SlTm^=FIiA*NBduN%fnSD zt%uJWwHa}0V(WREl=28_pqx+n4D|~RACRV;!d~+nPdZ@r;W}*a!jutkPF&f?+v$aO z45%5{T=2^23s225NO0dJm|B|`LnWjO^EOrq(S`HVsLhJ7-=F_D0dlpVt-bFe%UBcm|C$N<146G{PvI1`GB?>u&EWDb< zi%{7^df65(3vLl19MhCE-h{t2b#gUFhlEmzi5Gieafh2NE$>%wBcrTlamwNE|G4G7=s%U$%`+Ggc;p*hQQaZky zbR2sW@4*qh$8Z^!$+u=#H?Dl#OWeYXh==xCqV!9Pto6G^hF+;+i@R6JdxuB(N5NEF z(m%%NX-y`5Aizzso5-|D9>n95B!Bf{3Gyx}hBOxq;!gr=a^uefEcAFS=V)I>?mMTG zFGPlzKGo&N)rOLRPR>zP_W-}WREsS>#N$b2k;FN73cKht!3ly^=>8?j`p!Oz?^?Vj z2aXTmeAg%Tlv^zAlQZref{ELM?%>}Zl<;zoo+3rMWvPW)xhAgPcH z#4+|2ol^YuAI8(A z&G?mP*W=Z(H?U>eQ*1n4humIu8qI$dL8i_u#JaT(+`Y>n|EVv_bNk;zZQ8&>GSzY#MM)oO!(KQ+M17gD&@V-Ly8j={`_ zy=>dgG(7P47S8I6#^DYp@!FDeq(TAX-~XfNJp8fz{xF^q5fNp}%1l;@=RTkNA+u>D zX^CW1(pHp8wu-W)Y+A^w@Z9GdB8t$Sl1ieXt-kHw^ZOU>*LmINbFR;IUGGC@h*ZlS z+?_ECcWN9XkA;kg)y4W`hFAjm6rD--SA60x)V{=qIXe=mbIEwon`mOTw2mB^e~5rb z4vqj#VOI;qk0r*i;`AhZF2|I}Z5DC`LV`%1TRPb!Ztv9ehbS?4v4j89!p^3ODr;Muw4SHC#@VyH`0zF@L_xd$G6s!ca(){wf$ zvwS1dC1jam5BF|o3f$H~;B-C)=g-ZA3*L8t8|K4oaRtbmY)LP%I$+r%Wv{vc;SJei zf{ULwvg>Y|!uMY`5E!!GKH*tCPVVxuzu;rXU!L|HIh~S$MT#d8U#FUX`9=-;n;;FV z9&M#opA7gu(kJ-FS^9!-e>MBzltb{%e=3*;2zznWm%-YOM*^P@H(_Ix7vFi>5E|qS zlJ5$Gj7QKt27c)9AANs>3riBv`hn-@+1p_LMyv0F8i!QO&ukNPC1k=Pjip?{h#42W zD-%ui6$cZ|Tg;~9iLido67oxNIWDok&+cIlv0`hEkZhOroRV)LTUg)=RLcY^C0)#jTTDv+Ol-t(@_{l|Xz z^MWzk^$;Jv{F;@7PW)P?lVyG;fP+;LEouJ1HSTOh8`H+yiF5qwI~>P}D#LYq)=}-Q?KsXzZR09$2l9E=PBx zNsTQitceQ5e?CUnopQikdKg`}AW5Y^{KB!FW9Uz64%Xkj8P7*@;FFNZAD2E&u;R-M z!uzg);;w8(Ni9K*N(&sg>d~zvud4|e_DzBZC=1M{CS&Rhs?%-S-M5T}*0!M&h6ZTOoOoPiEa0Z?JR!LM#tckmSaEhs^P66Z9)YdTjA71- zy)^1&J$m*z7P;g&5WgwTZ(OvYttO zECqXs4H^1wN(@p0?Vcah=C(cy5j!u3Di#b|9X^}g)dk7 zko|u;uukJs&S|+;^XYRzSnv7+u3gKOD^8Q6eJT_2Ux7ECe_P1voR!pkut|)F3HwlW z_VclGf)Pfm^YJDB)Mk|((s1G5FfE5S+LM zhpaoA&t-=9XPqE&!Ie590 zXbU?KhXdaA;ec<+SPJrSXk9E`Cg(uD{%3{1UY|pheuUwBAJD zpm~x|>x_$7jvdY9$Rnvom=PHzhwm0+wHSj3w`{$Pt%esjEU zHrA*bqG5;mE4}*vB~qhqtBUb%|lL=gC^E zp?wRNo^!-DO<%a6mlZf<@CnX8k@NhY$Q$N_qzBv2Tqr+4Xi3 zXF7csXFPHS8|+)syrEVUuaYi+jvQ}x{N+_lt%?XcA$K#*q&v}i6K6c`tOHcXC)$09 zp9-T7EvZw#8GBT@4oqjvWBrp^bo}6X=>1CQ!Ex>IR5Xnr?`20iERRF_bq1^h*P<%T z<#y9FiqVV6^IT%tN8+;PAluOHiGP(}1(A|1{B!-T5Y&4aW>?9PB?}v{xI`JU-Xy`Y z4)Nqx&qBD;Lb=Za+MIUf9D&d4IyO8>j;MG$uu6B-@SBa*l!=Ha6WG z%R`xauJh-n%o^;bzD>(*lL@eS#ff zog5r1fp`2;xH-X;)zyB(MeFZ|!U@%|dsQ}>b-@Jt@RPC9VsUo)O$nBNMgok4Z_}_V zr6A>%LbvWngutfErf0pMxosf^{Ekm;u($a!{5enn%04NOcfg;-M|ZMghIVY-u~0jU z5E+>8@E%HW?x4Jf2RYR_Z#ewS2)35p;H2~?vFsLo?2)$;6nCYv{~RyCQIUI~{LznS zmrAkycQv>bSH19-r|F=wPY$-#WB6;S&X@N(h|9NeFz*ou>T2(KE%S3YXO9bDu2#py zzkUuO$D4$EoLcncT@L@T(lxZZw;Xn?k!MYnOX1a>QsG=9+)Y#+=FEk8jL2(ict7qe zqE~)yg5YoIY(aY@nsa0UH?n94_fbm+rbY{xBd!MQmu7kLMMv0m_wFuu>tE$wZrh7x zc$4t;pIvxp{wDGyL!Yj)E8{Eg+JQ%QM8Ia}Yj{+{h#fy%%lck-fd^te=;8hwNT;Y4 z4`mDepCut=$-jAUGoltrK_LI|5n1-{q%BY|wt)42Zv(M2#aY#Xa>y}szAuS-D`r&wYu=vV@GKIxNsOR*T)9=6v5HBGi=4vWV_9;HDKFlBxJ3g*d(_v z2mN#w@&TkYxfz*fkZ!j-I>727N5wSc;j2$09iHNKw*l*ad&`8SWFw{H@8G;+Efe%X z1=7|y!&dQFj`2+g??Q1l_R}>Ks1t*Nto>2MuR$<;sEsb!iNiU16q$Wr3Tj^hQRpNe zBt4`=j@2rlYx75t?plVuy`zcQ)KQ4O_sGKE8P1>?X@vO6?$|x;CwgmYgeJNEWM_O< zXQbCpfH!N8p~?EzEH0Hr)BQcrtErVtMewkoR`P~kwT`LeMZ*oUoDHG6@ zCq}s9U_3Kde-a5Bh+}_try|AWJQQA^jUOqTYYbP4W`1m-Upigc+ znsLe$JVW%+522Q_!iYhAp@m#yWD28be+=um+n@wa8+(SHMdN(-(BEo*8S|lXw4#0~ z`s&hy{!Gjg=y^X7NXcZPe94O_D5r@n?f!trWj3I&kJ7B3>gA>%N~&l??-^^tg`x4A z>{;#LL&#%r2Q%--W;8|V99rfvD!9NMMDmh7;8Lm#e(6rAUQP{#8H?He&40^{pC*IQ zhttUJ@=S6)EENgbk8wRT7Z#YmMEguO;8tE2ax%SyLhIwu#szMuf4>x5deO!mmR3bE zGTn^xK0oyM>=ATzQ$3QqGzHb_#M>TPmVjJ)Tbu4C)S(OVw~+PHgRqj_iKN_5ql5GB zpz&5MP_j3dsW6q`63s3#GP@j*P237}yzw%A@#7n_YUN^f>4+~H_wo*N%*h>X4JqM7 zm8x;hS4r5B@zQp2!yM3|TF8PCCt2O2NXdO8Uhz8|DcBrA75{#qR-wMCcu@uBa0}5j zUV^Q>kRkr710z|vv%GDM1Kc>?ek5%?4VETqK+la*#B&ZrF&V`|HmEp^UA9NYru|6W z@E%vvCyQ1JVwx`5C85)o3IuvOj>xO#I@2;;oUwSR3qQZiMO%u@(1x@+PGWi-QoH&X zaNKqW6!&O3t z8wo>b-Md9_Q0x|tJUN4N&a^>6TTUTSD#l8_&QD&E&1%SKnQI^2$}-`5MYhjnS)&h@naR6KpMB!LbnYSO=>Db%KR4NXcc zC&7t%bg?X-TD6+e#=<1(th$IUeEXfazmKNY3Xas}VKHUA7<$mto>p$Cp&O!S(w+O} z(zOP;bo<#{YI!_>x|h$Pv#Tc2w#p#-S<#;EW-aK>hG?o>=SpRducQL+y)@i1h~}Zg z^jTy8{S=u-;6Mt|ZP-Xn$3BwZH&ST*%|`k{UyOFtEu=S^Z0W_uBlO$ftT&y5Ph^qAfE<%+q7(?;B#YLQaofv@W40L3PyklpihIyq!wc z6;Nf_P1MyhggSkhL2V3kXyd9w)Z96bPJFh9rtFt&eyLeXtnT?!`AbXaH@Cl}Cq0}h zbRDINl6-o8r!-ZG3Z@3dQIy$bNt1I7=;v{{bm^qeMCLz4Qx9m;YbA4N&zKb5U{*o% zmgG>W3^n?@Gn!6OolUKK;_1{oK6J9MTe4&SF4_%CX!15&T2N&`3$%ji?4g;osZ@uq zzB8R}U2aK>f)CLNlQz8HPev99_3Xj#}K^O&^-9r4zj}=_?yqy8M(K zon&H4e<)c{O}A+J!S@Uu=&_}XO2*T>W2yAD^?bTR(w~m_k6!=aKrfC< zqVHOi=+De~G{Dw}@*m67!PBRy$YptY!aAG25tX6*nQN)q!B52T^KojiJC)u{I7uz# zVku*gMvK?!(_<>bH2>^MdOPVPEi6PdT04lYpBzTpPadcBPqS%!O$sfqw53~{C()(3 z0aR$pq$=mF=t@I|2IsvZ?U4uRyORc#XFZiVXt>bbXY*;2C!anmOQJ8;PSA7}cY5F! zqVB8wXwHNbI$XGcCM%q#R*pf`FEx>V8azxVuiHb%zweYXYsVNTGGB)=}O{ zSvplEl_q6AC6b-y^yg$R8euY?-dUJV4-KxThWDiC#?%7(C^&+~m&(zz!b%0j@r-@GQxA>n@kllo{)LfR@6@rK+kXD)4wYU=(cMyRQ!i3 z9p|J#*Q@5!o`bRUu+avp<>*3J>!;KE32}7vvmVVvJLu~FN7V$b^zuwKR^K6v zU2=C5RBA=BhxQu6`!o}1u{40cQcA4Zj}*JntA|-u(8>mF%YeS!03Jw^{p{fjTa(=2 z!)_zIa(^M)Ew&3012^_TUNZRolZK7nmY{Vty%DT-3j()%o-SQi3GiOb0F;Bb(n0&!mRd0aB&G>U*B!O zAJ!LvrukgH&Z3R5d+jsWCei|ym*((Gzfw3hSDIxS5+Rv$w;P}R2z)99u=RoqglQuf zk}`wGpG8=evPxEe<6$r}eZ;LzvVh$m6JXuaW9$lH&-{|WKIokz27#%AAm(ICV)FFZ z8z*$>Rul+Rd|e>?kSaf>EeHZyuH$vVs%(4BU6kAx0dM|Pg14_08!4g%P5&9ev`@?U zkM?PRQracx@#`e{<+1GMA4xdBW+5~S&9&Ehd?4wbAKcrT48}tWtg)aHo{yL2?|LiD zQfn&1ve)roaMc8kb*zHvU2DPr;5%TWro#!P8E}8fA?VFF0P(5!A?%1ZsE?~gJ!Csn zDlcWdt-gRs`(yafoB&bn#cY+VDKzfr7tYhhu=b!6d+8kq&H-?tU! z%$N$+Mj5PiRVwQ!eGJn5%=wd4B0=KzY&e^60$j(dL*$RW&~tVZY!F@p>CFlJ&H!0f z`pgFSxZ9X@+t09(WGzVV?qfYN0NiqQ*y){n;9u)5n5DiK_}0Rl>48?p_47}}8$1fp z)sFCdn1Idr%`hQLj_qH}gQb70V4SQA#H~>UiN~kdWd=qN{5uFt%Ojy=@od{6y$qxCAcf<-;q7jbJra%%xu!#j5^nqE0VsUpJSXJ?h0C5u5=w(I4y!7O?gkCD<1kLSEvORv$ zX{4`<&6`8If0OmkIFb~VN;@;w;h*R1h?Z;%y=}CU$VEhxu>*so?n*hG^W2$y9?hWK z+ia4-Js}5fPiTIA)tL4^DJKqDs*D!Jksf?O25iIA(P5n$mnw)VuJ1w z=II7f5%7u3j-Ej$w%IrPC>D{%l^dz%r)Ux}ZMcaWr0nl7^uya$U9)+mDkXzlyEt;{8qJ%KIYXy?s%0WOOkd@K{VN?;WEL&)V^O})Y5?$$ZiXOOgh)4vMk#5lpS{JgPB;Ac{w$^+>Ru(IgXpJE9 z-^vkEVW>o7JsEOt?+!BX>R@wGOE_r`=8<8poqEI-P>qRW)M0QD@l#7AD?aCtBX>8D zX?3k6INFUyZ=+<#tIf^p7at&TQPJe7&=dM|Lpb3aH;~Wmo}^07f_^aEM9MM)$%EI@ zRB`(rGUv}KdN4bZ?9vy`e#NaMX}K;nP?e#3!zWWQSVM-{}#mz4-|F*O5UUMY@xMXZiI0;&J zAcj@0BwVbVbWG?cH%dlmY_bKt5s^TKBb&*PuOof_dJ&PHA@uHwd6Bi<>h!>sRPtV; zh&0Gyzd!A>B|(F7?ePY zQ7TbXv>+FQbI6t_-ZbBHE>Y@zMM~GFlV6Fk#CX9y;{BnTkfU$O#KXEmU+*-kao(jl z^`9i6)#miCMgsYN4$5Bl9CaF=L}x9~6`r#j#t zblrNjX4yZOY}MI9H4goUT}}(n{F^RneA1%XbB+Y{K5<1L?4`sG{;Ehr))Cv;S)!v- zQn2ZE8H#)yz-cvoqy`~5*gkxaTriQsE>DjLj-LL2RQ1Bqa|t)5fV;x3Zn?=#Ey-Xr z?%c)ZQMu@%o+a9}(HypR9wb|T7NF%dZyEEsy0m><0ZDjWgr(++GNzfeR0mIie_FDr ztbRYcG)4;24_-m_Ur!+|YDtc@_;c$a7Qbe*kRG!hjkH$5fWIVH^j;o9+9z@FpEWVk zQbT+E+c`PuK;hfAm6@D71)i9W@E(i4;ALO8Mhnw~8ft(UTHD0O{;%D*p%Z^dM-j_L zTBhSST{7TjTa3TN2=iW=O*`cu&M>m0W^v>1JwVuL2w$#KS`Nj$j+acI?%K-7D5FAmIT;PpG^ zLdZ6CyDP>W`0Gd*w%RBHCGTv|=!m>HIwzNdWD2AB`(nW6uS1L8r9aPlb7dK3EC!x zBbnAkkRde@M-5e@ECmIUv}gdeEjf-ZUTP5pwi_e+UJY>e{*SYMJQuGkIE~JH&|=1m zI-`*)3w&}(o{*7!3MDjna1oar1?CZX$kJmqYR)+W;)@DV+N3me^u8r;R=zQ}X7o7R zxGvOHg!5>XQnKJ=(R&p5ip6(3SD@tA!fb-MvXJMr87a!2MK-mcP{^`LxNNTqj6xRH z*%M>C?7utA4YyVJ!HHGys4Siv+!4*C1YO6L&)eC_5glaJHb-b4QiiCLpEx4{A5 zkF4a&s}&%h*d)Ppa~_+q%7T2q(*wVHvtaJRatLrf2bVv_!3+f*_Qv#d%y$=I$NAu9 zH}(lmZ-&utF$)y?A_02Stob(sd|Bf_8}jyC8vCVV255YG0o#t$g1owJv;XVy z;NSlaJeR*GPrkOYd)sg0Q_5{9Z@_>oIXa0=v?~MNTy5gBv=t%9>=dVGJDI>4jb>V7R7i%}D&*n7h!m#^e!IVJ> zmbsq}t1pTWu@lOI>|nzA%3p+QZhRW9(hX}CR3Y(IAuWsjK7xhR8sWSAI5?zNLyO05 za5sMqAH)8_mzCY%9_;~IZByt`m$h&Z#ez<)IX2?UH+$0yf9A?h$L&AP|_?UDS{`^Pb+R2^J{ndw^>30E+k103LGnGPz8WV)y z|0858UxDf!{_Hqgb>?r24Xc)W2F>LiLItu)?A!9A+^w_IP{*7ocD1PytFv5}EI5nt zm(LEk)Mp7aG@k~op_S~|t#7#g%pp*UPXH^0WsuWa3HtG`;KKgJq-Sd)sKlwTTB6RR zx?nu}b^9FT8Fv^YA}`Xg$Hwd@*^^N3Sr4TIgZ$fC*rFv#z6~s7Q>66Sg^zxt0l%i^ zGquOTUw;j&Ti6Yy5591{?(*D8iTQZHq4W)P9KGn{cN#g1N`+>DP3xXkq? zXAm8ZjiNA?8yQb(-#=&d8k4YAmmJPb_|DZntZtfJzLtw~t7nxNK6%->h3_>znje+# zhOK{$aVwe)aO2Z3yVF^YIHt_4NvTW}Ia@i}X~sB`{)ywT(yT0UO-G8edM@O*C_UwO zc^Tt9{9v3}z71>586w{TTDa&|O=6e-jEh+qh!0<{;!oTqN>nav!imnOxeG0_cK_^J zxUDyC;<=R#oKEUjZkuQgm!Ef->o4EW=}Z@LZE_q0L9(fWuKCqX{xuwjS3KfJplfXR zxJl%2_(U#ihc#KcTn@MDPsVp%>Er34(=0=(bZ zj8yd8L6nuZJ6q`j0ed8Iicbbx{wW@x>EvOj;S6@+fmL{W>?hVP+7YJYNVDVk%W!*m z7q>IC8IPID;m7Ne?R0O*BHt@lIdgR%ETSiallK;4+v%g+whb=$*Pk~27OP6m!O$EB z?8+cs-63qYR1{lPp2b-j9KiK@64>If2v(`FgttlKh(yI2T>({iNw&LhD9{6zEJ93(c5balcv)pPmvQZjJTDWi#Yj^*IdT! z=|pdX6REqT&DjlCl6vzp&g4-nPE%Ip7ss36l+lM=T8Aoj>Py0dZ%+#6AcCV?I=HL1 z67ksV5nRg3aCyQkOsrc8H>@y%#Y5$A?afRs?|BXuzaN8xm;Xb#8H3!F`CIYw3B#Q4 z+90g>;~?HEdJNZ&8sH0$Z*gVA+1$nULhjF%H)LwYZSMHC4_wkW30&yu#~=#{J0IVx z*zrURjyW?Zh+ApSDSoJA?fUOxuI#dkI=`N){T9$_hSpiOZ<*2a{Q>>UEI~{Gw>4h7~d$miPhsK zlcS|E_}BK+`0&x|oN3z>yy5yFmoiO9Fe}fGd$0iG`8%rc*d_yhCx04tTcQKgf2_v~ zNEY5pe{ehc?XaE3N$%E_7dTf&5x=lHf*T*j;f&K27*74PP4_vEoex?PCexGi9b8GW z#Li$DTL-(x@$$IMKMXpXzQdwRD;T*unGpK>v+czi6C`oJfQePLhGyO;$hdR}PSu*R z!Tm9CUB;7%+2V={y-px$Gb6_2as}_Lo!V*B?o++~FA1}NUiNIZEI8{TWf&Z{3nVB#U$mU|+s+!lZIQ>sAV zwe^VYvCBI^KW3sWZ>lqt_V>Wd&~|!#S2OBr{sQ^N%TcIsw)tc$0ZQ*Agq`TYaP)H` zD;}r{S!EqmqeBU#y>b|B_od9NRb$Ng?SsrQd!gM%Pn)It%}}n0P&24kVej5hWint9 zr2Kx4;tOlx?%)~x<5&i$RSaS7-Zy9x_F(UpSB0RJrj3k)EE`?57%dsRMEyT3gd+Wu z%r#jT6ri9D-$z*Rs}w;~JzJY9+-qoe;9=(EE-lD;dKPL78raN1VY~l0S=4#Knu(0* zV_telqv7{zOzF$3@F?mV(tLWJo_{mIcxv4ioZoQ?uKO)$Jf!!l>9vIgGP!>rv`o_) zvs7FKSMFDXT-06OtJQhn`@5#;L#sX%uGov7@Z#W7ssp_1D}#V!E1`yY3|y)!n}$!T zfbY6Byadg6U_I6j+gi@Udw(sq#P$IrqBQ z!>o>pq1UpPGRgdQMo3pcwF^d(!gG$b@T~*+mv0#4<^Z=fF2OxZac0uy$!wNUNaLT) zS77dc@#xXKGUVZ2$F%P$L1Vx5SdnvPyfvGzqn)xL=zg<3Tl^Yx=Z-(}?hFu2 zJ^rp~AlsG632%e8DGX!QmVx$I)F4fDQ-MIHl&HP(fViU%Z7*zR1S>2u7{hc8ROD%-r{B#s|M+0I@ z`ao)REW9kSphk8Y?5fSHVEA$*0gC~945dwNV`sFyMr!7>Q1;QIEWheHEOE?3HdUtl z?;~fyd*nT|{WRb^MoFTu>MrD~E)Vh>U)V06p^JRgW}-a?_i@gC!hXIR$M1=*6nbYY z=_*+*==v)OH%Gns&TK{F;A%@y8T$dnojdtASO@%djz4$l!Z&8(<4ky;GLv8bLa1*K z%YrdU<>oj`^KV~Vz*!Z#0uIz=vaA-scQY?^t9KldHT=gLs1j!?y#JxY7s64|cU`tL zxDx(2b+hlytoieQn6qZfMY(AEP&Qvv0@mNN<#z6FVc0*WV22(FT#B1uL*sOAS9l?b zGO9st?uLB*<3ca&Zd3NiCTVbdGK-ZFT!vrXw^_cYJgEKJA?zfUU{{ss!SB&&C{ZjI zi8f5)n1Mgw}Ob=uQ# zTJTkj%t4gC0mFGm*wQsaWP(eC##c`Cz!BGg(Zb{>|k&>-*l=r;pNSO zs8y2Sxm_Bic4xA?T+Cs}qzGoe@MRZjyhBl{LN@S=Ly)}m0hj93+0-s6B$*dS!q)eV zEbs4hHf8HXlq?;>oa2bM6aD;ztlu?JN0|H8;O2cP6@%e3!Xr-G+2vU`*7>qFKj{FBUYub9wwVu!}vMs z>%WRDK8%7)&Mq$D`$;%%rOm1nNp{{>Gsw{jhi!IC$m8I6 zbo0kKHbZhM8{sL+|88;y-sivM9{f`vcP<;_^)89r>(kL3m-`x>aNIBWr8Ef&=1y-u znlS;nsk9QN-U?sbA`Dvpi^aqKE15YXeFA;(aC7h}MkowI@KY_DpbPxuRu&Hw9 zdTv*KJEjlqkcpTMDQ=ENR_|H_i+=W!bG#*Z)vV=QioGEYT4he9Z0tE1g-y6CVG?J( z;|h{t#zRo}An(0V0g|sw5=e|WFvh#9Y^8mgfbrfecsaYBon+Pz`96a<&ou)rE!)Ii zsHn7!`=l(0`E!!Xn_ko)*1HCSE!9o+3fZsVZoZZj;ty%_ob)Eu~q}f8vRk-g_xdD5I zHP{m?RMFTpsV4vACP8Uia~Mveh~6#*jJl zGD!iBE%QZUQrVp3$CX?FV*-!VPoT){<~MDSU{=?#hxBb5Sy5%R;pM6Q47@UW9}$9#B7-O|;k; zPPb39c~!_`?o+@FZv3Z5?9w0Gxkojf+{Bjzl`N4#i67GGm5pUhmtvH;n=(`3Y^o33 z`cjOR9Bape^QLek9V@sn+ez3xPK*n!oXuS%7lrw%Ja+P!6joe$k@N1!XPmf+I1e^( zU#n7>Nm_5XTfcocr{AjFu--N_d;WYOH_)BiF}0I>+`J8EbmX9;J3ZJ6q|dB$EfH)# z@I78(y>-%8H zZ7;}fdg$wmmPvOAMm@`6@xWsk3=X3crbhE0T`mXfH>S+98~xmjv8n9LP!;Z8TL5w? z7J9|p{TPRB(?Mcf9Xc~;LP$c1Kt+F4!0aAm#TMnGnY$twUvnSk#`hxFWl+q)fH=O6 zhfuV~H17CJ3GVvG-5{gGXGT8PFta+3;?m?CvR~>VmoYh$k()ino8)N*qi%n}uy`Vy z^W_lAUO10+brNR}tuTNGgHzc(;x}Q|^bhD&fEia;_5+^FAL9SC7|$-=kk{-P1#IX* zA-I`1;ou7~Fy3k=sy%+1{yJL9zIuC{e^@z@H9Xsn3}*MjKd*iGfwh?wxqOu5=94ZoJez^+ z*cM)iaJKCGaS05rxsb~Z8`zB87`|SzDmk+yk)3L8$IVE-&DcNohbKaxv)JlRRA;ju zMt1)|pC=izDx&&O91+0EzF5rt>{w6rg}d`1?IIYc$bthKzcPKkvg|s=9&GuGzB{;NT4uy}BMO=B#IvcFp3uhzR{-iY!>Xl!NRLf-F2&!=!|BoOsR`loUK2bRP#m>ZA3n z?!k3PQ^@`k+{y#)$yNU6Cl4S$W)R+&=fa|}UhrQ2377@bg$sTWSoGb7uy3tVjU(a&V=dO z8ep>^7p=Q>6Ox0s%B~)bLF477K=kch=;AAezr3c5u}m2+q-HPY zI>pk!V$cwr9PHp~#(lOf=NJqe5@&zC>p_onQ((rbaCSyvy+GsI7wF!&nmu;bftB0T z#BLkvVuzz*fP+?aAkve?-jTTD#C2gm%~`Z; z0Q}h@U?+6`LnAZNk#<--SGjZ_-lso~efif1U3hAMB=2#|G-fiI=2Oj0_B+M~#U$eN zz6{uZzn}a&eh@YUTC$U)!=UUx3%>UiKfY^h4YgtpvW~n;!h5dkZy`^t;4->* zRhF#E=^|=@-*L@JfH#QjI64OYce?Y+1Y`;rZ z{9Q*cgtd~?4XbeYfysEualp3{>N(kzKio!-ahP46i}wzip_SU#@X2$fls!<0rX-ou z`Kv?)9?VBBTu}i#6d2QnqEg)R!-D>^*efVpGeVm_oW^8z5x4XAT5KIha7924+9NAR zw6u!B_h>xX{>lw2ygWqHr1uJ0)Ti<4sR?Y_%$YdYB7>GEWP#VeAiC{t zG3sy{!w*e2Q<1pftx;0JdUNo(yrY=-Ue zrv=x!G>a2h$LT1(^W_CjNtr-YpP#~wLe9ndd>gXxVTd^c1}NA zI0Js+b4SAx@T6~5_)yPcqC0R94Svt3k9*?r4lj4ICs{c9)=%KO7CnWdc|4-vw+&rO zea-C?n@(o+2C^d6Y6Sh8PnT4m#bMQT_^ay$O8>;*RZW@P+6Xb?8t8@fWa85#r_{|OGP@x(V@^pd?HVOTLep9B>v;WsNAaEHc99M&|BWGxs+oHeHqnb3b& zD(WMe_Dva&F-hq2{c%L&%Y3|mSB8CSqwxjjtDIi$RUGoW1uG4Y;SaMuaEIz=;nNj} z9{t^j50q!&`Y0)EIb> zBq;6{_THp}cRy9bnls`#Nohl3c5yAyyO)J`c%;*v!F@2F_XUT)S%nYKQ9SEN1AZgS zbGGl5A$7}Yab&0mUj9modr&gOtvReh*B;5|y8c;`c~Udz*6V@vzBC^v&)q;;HgDuB z>?}v#S3B{yxP5rjt3P=2op7u!Wk~dz)k*XZLgH^L5dKwBGR{8>g=BBW%JW>Ad7d{& zZ*Mlu4R*mFx98werDe!!rWuF};(7WKacFl-ant;|BqsjPSKA+T#z<$00VF@Vjtb7E zz;BgUbkV$z+0m(w?(HpMPRYnK#>yAai64{U`i6~gqPG%lesV?FHF1yU`?8n0Sa=Hp z3QQU0)zqeRdo0wylpqS}I&9g*6z1gQFG%Tj5;Ld#DsO!0XKdEo&xD@8$@^;Hf^3#a zu{-+~Uiv9=i^*P4#%T1kGJ#SCB=p;RRNJq``#uzIn--S`3*JaGTNiYoE;9qBfBJ5; z>vVc!!A3WxHnNCWzA%@GW;Bq2YdO;r@B&>Et3sBGJi&S>67Ta10WGm&=G8voUTd$0 z?WwALwjZo7H7$`zMSE-Nd7svrpzacVxHTOU!>^4@_st0CZOvw`U$8^PGFzFv1uZaS zw;4UMIso$}2bf!-Vl-?|1PYN$Vgfe&u$chqP5qHSd9!%W&^xIFU{*eCN)8*v2^%o_ zu3gF#zp4Wo{g;`c!Xb3=ehKayu0mZmtnuv53D9`G1I%wfV-){=Z#uQb55&(|F#OF7 zd|5x7g1idRSJ?#Sbb}IaEZv>$o0DUk7e5Q}uXw=S8FkS8YAbr! zJ!H$r)VV3aUH}u9fF}qKd*hY=x(7BCE1bZSn8!z9@ z;6)8QMNS{Ac#fs9ym=#)NdJl~6aQcr5_NMDd|vkkrtvN_?xzEhy}cEhD8C=M6#AfN z3r--zS#pAdk2gZMUjwf%z7dJAd1zl-8=MpBsYPar%=@E;OvssgO``VG!LnfoXubQy zYx>V0eyQ6t+w^P&rndq}`lA_wFxjD|w-J@h==%96@A+@oxBom+2uNmD1azU_Pg2mk zAR|G`9R;?$_Ecl_Mp>A5@)q-TiYn?Z?Bl7A#xsw1?nj%yT(#ZWtc>hMcJSJUdPJUia@W^W(rKmMNg>47i!hm7$= zw=EQepA?55r!<+0Tt4a^Re^(cpU|Y}NBHPKHB4N83{|b|WN6-0Xtj?+GszKTz5Oub z6sm*jxNA-FK~7BLVIH%4Qwl6$i+I@RBVvq7;m#dxczYl5*_JsL>w({kb*Za5|NGSHAL+sVfj>9ctqpDbLM8|p3FzI;nZcGij6R{|Jn$2uchM6 zqGyoJ&X4rK!+-R$DWN2yo37lEMcLe1`eNl-TAuNTwsuN4FC=~R`ST-`*YKA*mffI5 zuOyrI`?OKl>{{Bm)37;M?FXIaXh>HCO>9=W-bC}K_E2$&F8ZrZuQ_Bw86CaUN@c!f zlhtmsXwLXhx?B4Obyv}7erM4_6FxT4UZIye^f#e@y`IxWd*9OVl~<^1UOsiJmj7RK zS00yBxBVNHM`)sf(qO2R=2Fi&Z6QN4B|`KlC8dEB%G^8&Q5sAsCCLyCdiGiml?;(m z$UM(unKJ*57x!M|-uJ%u{p08J@j2Dr>$}%ld+oLNUgzw!MXDoZL>4aFk%~uQf1_4h z(XSPiy~^-(KrM4^3Yr*St7Gtt{ z6>f3OMES+r@Zh}`?CxBPYh!gqJtL0eh(Qb%sXoN}*PGC@_jL^Ic#gXO;3&pa@4_Q3 zCHN|{5!Z!3!$AFF>>)b>`!hFi>zG26i&hlLiLPOT!c~-bdJs!9-(u=@by0=2oT#;W zBV0>sLi^HG+-kNF9lTCsh@`eiHd0D7`&Bubi!w1F;39_K6JncnF8X>D;QqLWC@kBB zciyz2;h{}qQU>9y)whny~Q&BRk64%~+i%+a2MVRi6>bBXqBDe%Y z#A~s{KOH@?60rIHIqcwl1zn^QFjz*2HdPuTLH=%hzeh|o_+}}dS*a&FXI+T8pPKN2 zjEpEwy^%W?@BkP0*@a>S`!S`zq-ZIZdo*|1S$yrFCOXs_&wcx-D^h8$K$Evy@zjvd z==MlKG<5nj(Sg=VbegP&@ku#&spdFp-m1rK^NO%qyAu0XSK&v`v)FJ{T+~?Ch?-Bf z;LJ&~qNDX4L@yWJz~Y*|qB9>hye3Qt1RM`$Ga_&gb<*B-`+ zv6oR*(1_B5HsV_3E(`O|!c9H%Fs^Vj4mU}~%eB35wqG&kbh(b(#Uw|+v&Q<36H`5hQLa0iZ-sljFUyNS+cJi-e)3ZjG) z8?flvK{T;{h5dEKMD&e}sO;2ZOuh0Nd#OIa2E!hrV_RO~Z2n%%SbHCHZ!3$c%b#L$ z<9UqfRF9(v?#1atkKv@iZCIcGfXh=GfiokX8c*{*q)T+K2 z?`6iK?heE>nOHW^=@aX|t(Gu!)M;bu`e|3_InjDC3~+iU2k<^$D^X?sWEW5 z8fuyTDRUX~PsQ*?rk06(kj$LeF$60v4qy*Y8OdA;FkoMcE;0M-)!EYHmCO^?)Nb6} zI%cI>6npxK7JI%-n>Cu;kKNkuG<%+$U``FQW{PVz;CzX>OeZNXb{V%1y;MJ)-Kuke zQI<_)Oa@PB4h!G`tTJ5IE}_fU3K|L1(kaPAzcel}yx#c-!@En+>+^s?(Cy_eaPEJ?@Q z;P!Ak;uxJl$JsMmrn5KYcd%2G*U)t*gV|fYQFK=1Ms{K1Fnm8qhs9%_jC>`}PC|>v z7LBgNjguo-MVT#(y;?eRL2>}=TCtL)t1TF}?#+CimB|?8b&)ZdX2I^cGf(7x#1#X4 z9i^*iB z_k)Yfu<4Yo**=m{Ke&-;P`=A9^~q+=7q4Rn9lgnvn{8tU_eVy5;Cxo5{{nXRnHpBS z)twzP+<`s0)QvG2Ga2fc7-rLjQWiGtW?!0&!n@_WS-y82v-RCa#v!$yajO5oURUd4 zXS~9f*>PuxsMb)#$Q#{Y?kFhYLYei<`LT7(#t2z<$Rl-jdWajVyQ+W^4s-7S>mF z+v($L#-(a5Q+G3(NmlDNmCEDclj#z;`k}->H1VAZc{PKe{q+c z5>do0alF{0j&E{&QuwD1ovNNp?uwx?5vR6{?u-gM{*!ZUT!mIM< zm~J~MYho8KYK=G!cY{{3i_Z;(WfM1mm2Mo(47g73W+c<>4hLyf)O_L7d7WTiusM6* zNex~!pT?M7E^xbRA~my`$$gvth^B>9(42%F_{cL5lp}R8e)kjFOL{HccJmmWy{$Vm z7nY&IiH-E$hRyVeqbgo%aX=ZnO?aj60UF$>CK`3t0QMX>M+a$Sf$j_oD*t94)pHhu zkGJyigZUcT+DDyw3LNRScdx1RmA!CRZ3*m6K0!~X#L?5sywGj(AS&VH2vYBsQ;ifc zIFqtocyyN&jW`@f3mK$Ab?4~5;%&5Pqzb6c84H4#dhqZ|1C!W?aDF$RzMq&2E@U5V zaJQ#w0|azi?kPI*b_8v>)(f_4%Yu)UIaWNnK~2hX(Qm^&8Z_hu4ZFCOzEO3+H9>0d zY*!{4v9{oPT}6a>&*|%|;k5hpN;*CKI9;f5nob%d4vNDn!AAc+SCUFX5A!Eqp5q=g>?pVQAuig>EDJ80yjLwHRsO@2R#F3`D&8*VP5oBJ)r6Z=O4 zZ(|kRYrdC)?l4f`V!J!D6Y2IDZ-nODxy{#xVYI1>9?S^Xf^RG%*~t1Z*eLgi=Iz`< z7YD>shvloO{UBxN6g(UTS>2&mRQjM{!WO#aaXM}KR6~zOb9u!Ud(x&=FR07$;Sg05 zO+{hz>8XYiy3%1JJ#h03y?ZO34jxvH{gozA`|DB=d8dWWCkjBKb-A^j5fqhK()02? zM7o2s>BR5}@bR`g$n1}1Kff7(-PG#n2o*gVFIh>SIUWYh6>|7I;wD84X}gqQ2kN1* zf!?*vMU>o4CF?KI!e^VQy?8hs7jO&ktTf17qO`2`0H+^EaD2z*czckJZC6sLsdWx$ z54}bsOJ30&4=b7&YX-FH7(6?WN2}X9!Rl}doNt>&`NC;_fxGqtLf`~>p?r`F0I^OK{xYf(IFAjsFSffT(>QtzP$qQ z(&SV+@su^)Q4&o(RGaBsCJNLOW2uQ(7YxjCr&DSJs6^6I=*r~^%$_0(6FTR@p4TZb z@Q@bT*$CNrJeHyrJHh~!l%R~#VWdr)efX-ww(S#|hMX#=g0n_c~>YH zS;cZr+dw2RdnY%gNC56G_whf};WfR~O-l3}9W}*4a zQ#gJNK|EIpi*gYjnr(wK^HxIYgv0PUsg}|2F@nLwOi}ldQBWd^fX3TJ;GPo&?<2E7 zt|S3mExJQV_aunwaFV+ZsoD1OiEv2YnM1XX9ERN7E~4WBTL4~dhe7MbU><+0P;#Ld z-00H^SE}=&=Xe2@jmU$oNmX!a*aPO`=-ZGwaV*MT*$fKreZg&D6QqB>MJ2hjw^uDP zz)-m}ep;FZUCmd6@svW*=KX1)H*gg!csv)zH|5dWNj*f$5ih7-=4v>eB+Kqi*bJ_Z zMNs-F89U~u!`{U`@ciK@@R3*#OO8y2hEH7nsBx#Es`Gjn5#I+S4#t80tZTIGr5nUA zP=U`Y1HhvTcV_2cD(IvoL+Mg|d?vddvRyX9gsBO1)Tcx^Zl)#L+BzN{Mx{X8!xFg9 zouS&Y&c@D~%c-U?!Vo)*%jIl=1@H0zI4g*Sr>D2VHWLFdx3b4eQfolGITfxxA5F77 z9O#P|N>H@X0m3pYP_2B4DEOm`$j4M2hMft4y7!l1M$$xxyAc7Y!U725(w@9owh4Bw z(uV#X3&FB0PgI_|4n*Ao;mXCnBF(FAxMQ;sJa(yo%Q%tSlVw%#Hs-+c@TssqY&O(2 zi)eH}5@hk}L0g7fq`kih2d&OxDbE@{JQ;{usRh{_sRcN3s;5XQcNRECyONw& z(WEh4n*@(rMph3}A`&}{h?Rl@S<=g!yg3*}&ilrY9x{bwr2G()QmaLV@75|EkhiFm($WRhkcG2-rVTs%FT zjB@Qq(l2?CYNLVVfp0G&BNQXkbTx_1?tY|ta$gcU_c$44t3jrcEV5vUCGlu9Cx!1+ zNkQCf(nGW0Z-%vl=Jzz?Y2;9iA;Il8iO1;Fd1*Q^^WH)}2@*)&22C<@c`s6>ph8kLdy^s?oROknji)VWg8tz5J zr)rWj^WTx|X?@AF^boS5U;-)f3nUxV29#P6oZaP5e5ALL zzS*gyc5e@I=Ugvx?QI{Tvo4<8uc{_+SC0hu%p*(BtCHunXS>JGbcoFYYcgWC zE-_m=gxrg0BBj?IiQ86XqE|YPXu8LcxOfe+KW{m?7O75(`bUwwW!6N=Q;&oWG9{hN z?aA{IkwnQ}iKIE05a;a%WW?i1q$b0OM0vXt^`xF8L|&Q9s8lC~)+)qXvx2OXP$v(> z^@yg=AX0olk>s_=knTw?~MpO=)kgh$4kmfhN372A?xCQngWzTDgX>Moo{NfZc$n5|*(%?)M zCiEqvKdBJ+TUF$!!cd}|rbR-f2a{354M>{F9Fm^qLJppqOL!?;$c?;sax=AvRF+R6 z_ewHI%0^Anmh4E*dnc1~bzO+~`RSyFxldy=kKL3nyg zGqbeHMzlA?lbT1B;L|&=gr%wKPW<$ZYOwccA}fTy!BHk8F*v2p=zp_h!2=4<#vo`dVw8 z!|nSf-;WVS=ziu;=dI!5cZY>HpO1pHY-eVg@fxPbi1Bz?5Wu`0?M?km{Dt0q;-GT% zc|I>Aqr%o%g^`r@WMYR5p#$VBgdGR?F!EBmwvaDH*RJ2dxHk@ERaROua=Pkvi#iO$ zDuEi?8m_>1Ra(pLt4rc~c5&V98^o097iW#F~ zZVjAgZa3pii*3&t%Y;c8ui0Cpxm@HKsqC8&Wae})t~l#pjXBwenGbT&!p|Ek;AzYg zP}bhY50KSl`VUryBf+Ljlhb6pm-~oHA9zj3(klMv+1|pcemYd?>SAW=rOuep z`-Sbm?C^>knLO4$NyLBc^s=HUGhb*oUXp#@ti()9m7otsyyNFDnJd(B-61S;lA_Zq za=AU7E~9g6IMelNDRXh+3EL$lu1wQ`65-s|lfsv;qFJ$;{&aDl2k>fN7+<`k_GD#om$l~XpM%ww?4SC*ykS{?a9 zjY$5L_1dBqdeBy*dk}MTcL1v}yO5cDL`gU_wGs>Xb=U zpu^7;W!n>XzC>mql`C5c6wnbzc)^!j4|_{Kk9yiT0!TAMZ~U?R>4nS6yY! zn27Q0#wI$k8Rs|JMpXHi)AQ@u`l(usKt`ILR59lto-kjy%w_^xEPGja|C-2F>7HE0 za*tZ3_`J4g!0cjS579Jcf>|R!f9MtVgIe98&zaM3XoDgwv3t%uIyC|=jZL);HL9nx zljZ2}u}+K=my=N0VJWTd+*|al@ff|=JDF~Oyv}w;;!u9a)4_Cu{(HXN!5KmeGeeXS zq_U-Aa)(+T$O-AaQek1zI>zNgZ@Mb$I5upH7UnqYqy5+ZnI><)YwmyxqNZ$aPHp#Pr z(l*r1>ZEPya&(l(>xv0gAxyoa6t$6> z1BOLIS?@WknO19-U1B7OlbeRo`qmJ3boL6WZt0-KYPDhc8{KNNvoh|1}o0|0@rYVJ)%G)i>uQ@~&XIInRi7)u&np|$wO{=Nm z=uURE#e-0*xd_Ky9LYT6Vi+MueeLKdDtzzUQS{NFLU?FFfY7C54~h%KX-|oH%ng@t zs*@_u-{I*B>kHa!&mS!j`iTPU(mGH)wy=;&bDu`r)(@tK9)Pg(GG*b=phH4!`#NEp z`Vi(*U=zw{iz;R%uB^y-{g}=jEX((Kbx^padyKH?+CEw`{I0NNQ;u-qW)D`~V+!r1 zbAeh7j-krz8k}Zs$fQr2!Mqwc^PqL7+03K(WJbl)f!VZ!rISqynJ(g+gyu41g^`OegDpwURrmDX>9FA zTNXvxPV}#~O`h75-&{NuH&2#gqfI99t^Ho|&+!im#a7DEfLC+S-nLAbx=4XJ<-T61 zbkp1RlIIz|MoE#dEj*SfdDV?7Eb7GcZFweiJ6*|4DTrZA15}0G8pknE+KtY!-&da8 z&{knzqj3nrUfI5MdsGpaFqtZ!>qdLab2%E^PcR+)2GCEcdi;K8i-o(!HnChZ-*%+e z1}K#qL!EsK_#a|RE0Sam(!vBe;ROjv+Hv4scAScyP_mmLwU6Pmx%ckTIT<6V-?>lB zsMaWU$O3sgNx@0%F2072N;$+2AJ~`e^hAjjJUa^_g=jj!HJMK6&&73mg8el$w_ zE*m}Tjj-ocbH373%->yIDAd(-pgEG~g<5OV7@4$F zHmi2WF-4=^(kW#;+e@*2c3s*IF>yx>nJ#OU>5ZE+VSnoqyN+m$AFm7*mM@dTwQ@2j z$K4aRp6a4dIUlA)Z-g}})o^S=7u-?lcrURVo_LS2c(fWG_dX04@=k+f zxDrk>tcJD2)Ujzv6sA5&h08_-pxKfDZSk)7FykQ1e!3dAy|KZbm;G?)u2t|%j6##$ zT1Ycr4-cHGp*naQYDk}khP6o$&fOOuA~lsBoU|IA+gRb4#*ANA@p1U99%yDR|Jp;T6sW{Jl zBG#V_Lq>izhC82wlc%=9=T!`@esdH|eA8gG#b(H;u7(>vMIg~b2JaWtLE8@Q-dcwy zP;oZG5~*g$vv>x}wYcw)^A@0d@-<*=UjRAO1U71q@MO#b@aVh?K1v-0AHilQ>w*w8 zr7NlrkwyQxwXoj20kh6Cxxpw(vsu+IKiEyF`wHW4iJ5e{!{f%Fv;IJh(tbUH-ApqhQSJR$>DZo3OF zQw;D%QVUGK5rX$G#=!czZ0>w*0i2nT3f!kLa4k|F&nyX zsi1w}8_>J1fiHcMAwTmKG?Z+CJ0U74m-G&9*^PiP52oS884bX%s)76+=ODKCNGu$c zgsV#$K{{I<`#(PgkHZQe#rg=|s|p1%?tW>h{?Fk2^{X)9NhjRXM;YI??T7mb7vNxX zf1JE*I(*g`53j^m}sx21xtDM(bu+eJKuIC+-CK2T$OLQ6k)yc?u7_ zWN@DSS(sK{3R$<~fPZi$D8=N$s_hLG`a|O2K%Zk!5xF1QF6@E$uI~6&{tjsAM8Zdf zDA1mr3@azCKxT0PJn1q4M3tP~>FtHK&k1mSUOhyKS>b!O6cp1hfI#CQJnvEp6TE6+ zNJ?kiCiV%=1ziQ%R~lHgTS|26{y0>BC4*}F;$T*;DQ=fDL{?56D|!QLoP7fv?DJsp zqXwwmy#sj4s+hhm6MW}#G7jp4BOn?3U1xXp8D(c$v{+exE$)*~2Jy9#E%cSieWS!`XTgSgn0 zbkpYEksFwxVCE1!qB9Gni+%BaN)qO%$>P}A(@^$Q5S~3U6z65zVCJ@~5a!v6cgMtH z*W}*V*J>$_p7sU|^Hs5Vi$3mM;ED%yufv#hM@%ehMr*5BoY5s7i;s=K!hy%;t9mSHTzL#e`*XyetF`&K{}yGi=v%bP24!|*t?yfF<=B;{ftcaNpT zmBl#6$pl{=O2oZ4lhCt+6=vLj0vR?DxaRUpn6W_zO{@pu{D4gu+He{#Pn?FamWN=O zixXO^48(;a{xig|bPXR+VK3!gY+#8O?;OUuHW><$OSSeeoPXL^6bzpTpR=SJkVgsQ@Hdh7 zu-p?zaXAUC6kSjzY7?g$W8BrqN7ID?SShgpn?9!C*$co8&+SD#xA3baOQwH_&}@y z;yYPj*E@#Txj_bHv^X7pOhCWaUD0a~7pFWq5;IlSU|7e&sOYJQRjVUW=7T3bG04Jg zZVT{vRv6B?zXW5hiKC)=JWi=K#ipxkF)MHyCS>-;ETz8akz;_Z_KmP3dgHp7UbDC+LFXMT-6m+jo@?%qEg!*-!8kfI`Yg14 z@JFNRne^VkO?1VuR4Sb~2UXLGX!z;PIBgD>lfO8N%ET1Y!}8U%OZsuX?j8YMbbOnz z7o@hOY9z|RJgFAh}w?6M8Px=9>Hmv^g-LXfrT4jav&y60(M@t9ExQrLlvUZ#B6mr+QdmbnoIgx^ z@bHYIS@bJ!}w5`z4y->zGJ7|Bw~8rc0-jEqd^;k0$hp z&RW{bei>DoUO>(B#@o4Nccl3zOKoE(yFlhBpm}qgphasfJb3SE7v1E*Dq!#&je=@1BeZ%9w2u%ZE*x6_4tA27#~a!BPP z2jt(pLi6>DsJXE$Zs?=MoUmF!Rk=LbCu6JVtRM~ki`jf|KHZDC80un|VbmGA8a41e zM~#KV?s|0V*yr>W7YNKP9JM}w{=EE4 zjc?V&zW@H6zCAiKo8QxY$7z@DU)uYQ|7Y4W{g?KBru#>GnzG{ml+R<4Hz%K!wWI7e zm5CX&S8m}bEBUQLd*zlkj4vj>mB5P`o(n0K%sFeo^{!{0aD<4d`~ z)ObROM~L^VQ2%ir!6Ck1{^sp9#KYg;_zxgQsZ`N9b7u{%&lh+Hhj@Ew%?b_*&)4G5X#?;GMhRg)*<8#p&KL|~z)#8Z$JSM1D_kQM*qzohI6si`{O%PkdEc-^>i zg>U5&Jg)ppMG4PdJlT<6-hmtoVeNu=_TllqR}h%~7RPToZ}1mpO}&29!PmbD%zi8G z*9vBwN!!?(`~d$&gzxhBx$>C*1^#DXiGKjkHl8pq)Z07U`!DhTo!GQJ9ZL9x z@TSZU3HJ5!_7cqZ_nql2u>7%=kXf9|_+}?RRW$zDK7J}U`$4oX-T$p?#r=YLf}bk? zeXR-vW~N+!b>(&D%&Oxb+WgY)uaa8)*)}cygQONe8{fZ^^lxmHlhmp`NnLn0KaBR@ z_o21%kKJHx{`)?2=8?aQ%8j9R`{d7aYV{MRR{zGj{ye9@qH>(Jt5b%j|AX}Y^Fsmz z*2aH4W_tVkb3c#v2pJvfFEBOx+4iLTJb6x|y*&bdG`PR!gumb@D)QtxxXlyCnP$VEU_z_u`HJ zTKYeNn+k?|2LuQNrtJtJCn8X`0e=fIr|H}gWLH#qX zIJZC^`ZwVIqVm5B_?PMW?*caeRTVBgdl!$u*?(EXUx$P7z(|>{d#U1^!+UEw!w-*0bjr@wu4HT};pWWPl$1%5I z{e@=^w;lKSU*LZAnZMwU|D44vO@5ul#Qhw2WM8E`C#>~%&ht-pLOTZc7u}=P7Z>%T@Lxk;e?pc2^tazt zY4s&U^ds7TROOc`>bI(F*GGQ|dHv+0t$ypHf9?z}96jG#U~cTfJ%fV1f@~vM3^qfU}{egQ~VErX@@UJGx1)u*J(;vvw m_@T|8>3^pD$7_bcQamwv`N3UyJP+RiL4kpb1_b!aOZ*?6Zby^= literal 0 HcmV?d00001 diff --git a/crates/denoise/src/engine.rs b/crates/denoise/src/engine.rs new file mode 100644 index 0000000000000000000000000000000000000000..5196b70b5ba02f665385c022a0dfa9cd22c1db9c --- /dev/null +++ b/crates/denoise/src/engine.rs @@ -0,0 +1,204 @@ +/// use something like https://netron.app/ to inspect the models and understand +/// the flow +use std::collections::HashMap; + +use candle_core::{Device, IndexOp, Tensor}; +use candle_onnx::onnx::ModelProto; +use candle_onnx::prost::Message; +use realfft::RealFftPlanner; +use rustfft::num_complex::Complex; + +pub struct Engine { + spectral_model: ModelProto, + signal_model: ModelProto, + + fft_planner: RealFftPlanner, + fft_scratch: Vec>, + spectrum: [Complex; FFT_OUT_SIZE], + signal: [f32; BLOCK_LEN], + + in_magnitude: [f32; FFT_OUT_SIZE], + in_phase: [f32; FFT_OUT_SIZE], + + spectral_memory: Tensor, + signal_memory: Tensor, + + in_buffer: [f32; BLOCK_LEN], + out_buffer: [f32; BLOCK_LEN], +} + +// 32 ms @ 16khz per DTLN docs: https://github.com/breizhn/DTLN +pub const BLOCK_LEN: usize = 512; +// 8 ms @ 16khz per DTLN docs. +pub const BLOCK_SHIFT: usize = 128; +pub const FFT_OUT_SIZE: usize = BLOCK_LEN / 2 + 1; + +impl Engine { + pub fn new() -> Self { + let mut fft_planner = RealFftPlanner::new(); + let fft_planned = fft_planner.plan_fft_forward(BLOCK_LEN); + let scratch_len = fft_planned.get_scratch_len(); + Self { + // Models are 1.5MB and 2.5MB respectively. Its worth the binary + // size increase not to have to distribute the models separately. + spectral_model: ModelProto::decode( + include_bytes!("../models/model_1_converted_simplified.onnx").as_slice(), + ) + .expect("The model should decode"), + signal_model: ModelProto::decode( + include_bytes!("../models/model_2_converted_simplified.onnx").as_slice(), + ) + .expect("The model should decode"), + fft_planner, + fft_scratch: vec![Complex::ZERO; scratch_len], + spectrum: [Complex::ZERO; FFT_OUT_SIZE], + signal: [0f32; BLOCK_LEN], + + in_magnitude: [0f32; FFT_OUT_SIZE], + in_phase: [0f32; FFT_OUT_SIZE], + + spectral_memory: Tensor::from_slice::<_, f32>( + &[0f32; 512], + (1, 2, BLOCK_SHIFT, 2), + &Device::Cpu, + ) + .expect("Tensor has the correct dimensions"), + signal_memory: Tensor::from_slice::<_, f32>( + &[0f32; 512], + (1, 2, BLOCK_SHIFT, 2), + &Device::Cpu, + ) + .expect("Tensor has the correct dimensions"), + out_buffer: [0f32; BLOCK_LEN], + in_buffer: [0f32; BLOCK_LEN], + } + } + + /// Add a clunk of samples and get the denoised chunk 4 feeds later + pub fn feed(&mut self, samples: &[f32]) -> [f32; BLOCK_SHIFT] { + /// The name of the output node of the onnx network + /// [Dual-Signal Transformation LSTM Network for Real-Time Noise Suppression](https://arxiv.org/abs/2005.07551). + const MEMORY_OUTPUT: &'static str = "Identity_1"; + + debug_assert_eq!(samples.len(), BLOCK_SHIFT); + + // place new samples at the end of the `in_buffer` + self.in_buffer.copy_within(BLOCK_SHIFT.., 0); + self.in_buffer[(BLOCK_LEN - BLOCK_SHIFT)..].copy_from_slice(&samples); + + // run inference + let inputs = self.spectral_inputs(); + let mut spectral_outputs = candle_onnx::simple_eval(&self.spectral_model, inputs) + .expect("The embedded file must be valid"); + self.spectral_memory = spectral_outputs + .remove(MEMORY_OUTPUT) + .expect("The model has an output named Identity_1"); + let inputs = self.signal_inputs(spectral_outputs); + let mut signal_outputs = candle_onnx::simple_eval(&self.signal_model, inputs) + .expect("The embedded file must be valid"); + self.signal_memory = signal_outputs + .remove(MEMORY_OUTPUT) + .expect("The model has an output named Identity_1"); + let model_output = model_outputs(signal_outputs); + + // place processed samples at the start of the `out_buffer` + // shift the rest left, fill the end with zeros. Zeros are needed as + // the out buffer is part of the input of the network + self.out_buffer.copy_within(BLOCK_SHIFT.., 0); + self.out_buffer[BLOCK_LEN - BLOCK_SHIFT..].fill(0f32); + for (a, b) in self.out_buffer.iter_mut().zip(model_output) { + *a += b; + } + + // samples at the front of the `out_buffer` are now denoised + self.out_buffer[..BLOCK_SHIFT] + .try_into() + .expect("len is correct") + } + + fn spectral_inputs(&mut self) -> HashMap { + // Prepare FFT input + let fft = self.fft_planner.plan_fft_forward(BLOCK_LEN); + + // Perform real-to-complex FFT + let mut fft_in = self.in_buffer; + fft.process_with_scratch(&mut fft_in, &mut self.spectrum, &mut self.fft_scratch) + .expect("The fft should run, there is enough scratch space"); + + // Generate magnitude and phase + for ((magnitude, phase), complex) in self + .in_magnitude + .iter_mut() + .zip(self.in_phase.iter_mut()) + .zip(self.spectrum) + { + *magnitude = complex.norm(); + *phase = complex.arg(); + } + + const SPECTRUM_INPUT: &str = "input_2"; + const MEMORY_INPUT: &str = "input_3"; + let memory_input = + Tensor::from_slice::<_, f32>(&self.in_magnitude, (1, 1, FFT_OUT_SIZE), &Device::Cpu) + .expect("the in magnitude has enough elements to fill the Tensor"); + + let inputs = HashMap::from([ + (MEMORY_INPUT.to_string(), memory_input), + (SPECTRUM_INPUT.to_string(), self.spectral_memory.clone()), + ]); + inputs + } + + fn signal_inputs(&mut self, outputs: HashMap) -> HashMap { + let magnitude_weight = model_outputs(outputs); + + // Apply mask and reconstruct complex spectrum + let mut spectrum = [Complex::I; FFT_OUT_SIZE]; + for i in 0..FFT_OUT_SIZE { + let magnitude = self.in_magnitude[i] * magnitude_weight[i]; + let phase = self.in_phase[i]; + let real = magnitude * phase.cos(); + let imag = magnitude * phase.sin(); + spectrum[i] = Complex::new(real, imag); + } + + // Handle DC component (i = 0) + let magnitude = self.in_magnitude[0] * magnitude_weight[0]; + spectrum[0] = Complex::new(magnitude, 0.0); + + // Handle Nyquist component (i = N/2) + let magnitude = self.in_magnitude[FFT_OUT_SIZE - 1] * magnitude_weight[FFT_OUT_SIZE - 1]; + spectrum[FFT_OUT_SIZE - 1] = Complex::new(magnitude, 0.0); + + // Perform complex-to-real IFFT + let ifft = self.fft_planner.plan_fft_inverse(BLOCK_LEN); + ifft.process_with_scratch(&mut spectrum, &mut self.signal, &mut self.fft_scratch) + .expect("The fft should run, there is enough scratch space"); + + // Normalize the IFFT output + for real in &mut self.signal { + *real /= BLOCK_LEN as f32; + } + + const SIGNAL_INPUT: &str = "input_4"; + const SIGNAL_MEMORY: &str = "input_5"; + let signal_input = + Tensor::from_slice::<_, f32>(&self.signal, (1, 1, BLOCK_LEN), &Device::Cpu).unwrap(); + + HashMap::from([ + (SIGNAL_INPUT.to_string(), signal_input), + (SIGNAL_MEMORY.to_string(), self.signal_memory.clone()), + ]) + } +} + +// Both models put their outputs in the same location +fn model_outputs(mut outputs: HashMap) -> Vec { + const NON_MEMORY_OUTPUT: &str = "Identity"; + outputs + .remove(NON_MEMORY_OUTPUT) + .expect("The model has this output") + .i((0, 0)) + .and_then(|tensor| tensor.to_vec1()) + .expect("The tensor has the correct dimensions") +} diff --git a/crates/denoise/src/lib.rs b/crates/denoise/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..3673b28c1c837ee265199b9be2d9aea4a422decf --- /dev/null +++ b/crates/denoise/src/lib.rs @@ -0,0 +1,270 @@ +mod engine; + +use core::fmt; +use std::{collections::VecDeque, sync::mpsc, thread}; + +pub use engine::Engine; +use rodio::{ChannelCount, Sample, SampleRate, Source, nz}; + +use crate::engine::BLOCK_SHIFT; + +const SUPPORTED_SAMPLE_RATE: SampleRate = nz!(16_000); +const SUPPORTED_CHANNEL_COUNT: ChannelCount = nz!(1); + +pub struct Denoiser { + inner: S, + input_tx: mpsc::Sender<[Sample; BLOCK_SHIFT]>, + denoised_rx: mpsc::Receiver<[Sample; BLOCK_SHIFT]>, + ready: [Sample; BLOCK_SHIFT], + next: usize, + state: IterState, + // When disabled instead of reading denoised sub-blocks from the engine through + // `denoised_rx` we read unprocessed from this queue. This maintains the same + // latency so we can 'trivially' re-enable + queued: Queue, +} + +impl fmt::Debug for Denoiser { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Denoiser") + .field("state", &self.state) + .finish_non_exhaustive() + } +} + +struct Queue(VecDeque<[Sample; BLOCK_SHIFT]>); + +impl Queue { + fn new() -> Self { + Self(VecDeque::new()) + } + fn push(&mut self, block: [Sample; BLOCK_SHIFT]) { + self.0.push_back(block); + self.0.resize(4, [0f32; BLOCK_SHIFT]); + } + fn pop(&mut self) -> [Sample; BLOCK_SHIFT] { + debug_assert!(self.0.len() == 4); + self.0.pop_front().expect( + "There is no State where the queue is popped while there are less then 4 entries", + ) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum IterState { + Enabled, + StartingMidAudio { fed_to_denoiser: usize }, + Disabled, + Startup { enabled: bool }, +} + +#[derive(Debug, thiserror::Error)] +pub enum DenoiserError { + #[error("This denoiser only works on sources with samplerate 16000")] + UnsupportedSampleRate, + #[error("This denoiser only works on mono sources (1 channel)")] + UnsupportedChannelCount, +} + +// todo dvdsk needs constant source upstream in rodio +impl Denoiser { + pub fn try_new(source: S) -> Result { + if source.sample_rate() != SUPPORTED_SAMPLE_RATE { + return Err(DenoiserError::UnsupportedSampleRate); + } + if source.channels() != SUPPORTED_CHANNEL_COUNT { + return Err(DenoiserError::UnsupportedChannelCount); + } + + let (input_tx, input_rx) = mpsc::channel(); + let (denoised_tx, denoised_rx) = mpsc::channel(); + + thread::spawn(move || { + run_neural_denoiser(denoised_tx, input_rx); + }); + + Ok(Self { + inner: source, + input_tx, + denoised_rx, + ready: [0.0; BLOCK_SHIFT], + state: IterState::Startup { enabled: true }, + next: BLOCK_SHIFT, + queued: Queue::new(), + }) + } + + pub fn set_enabled(&mut self, enabled: bool) { + self.state = match (enabled, self.state) { + (false, IterState::StartingMidAudio { .. }) | (false, IterState::Enabled) => { + IterState::Disabled + } + (false, IterState::Startup { enabled: true }) => IterState::Startup { enabled: false }, + (true, IterState::Disabled) => IterState::StartingMidAudio { fed_to_denoiser: 0 }, + (_, state) => state, + }; + } + + fn feed(&self, sub_block: [f32; BLOCK_SHIFT]) { + self.input_tx.send(sub_block).unwrap(); + } +} + +fn run_neural_denoiser( + denoised_tx: mpsc::Sender<[f32; BLOCK_SHIFT]>, + input_rx: mpsc::Receiver<[f32; BLOCK_SHIFT]>, +) { + let mut engine = Engine::new(); + loop { + let Ok(sub_block) = input_rx.recv() else { + // tx must have dropped, stop thread + break; + }; + + let denoised_sub_block = engine.feed(&sub_block); + if denoised_tx.send(denoised_sub_block).is_err() { + break; + } + } +} + +impl Source for Denoiser { + fn current_span_len(&self) -> Option { + self.inner.current_span_len() + } + + fn channels(&self) -> rodio::ChannelCount { + self.inner.channels() + } + + fn sample_rate(&self) -> rodio::SampleRate { + self.inner.sample_rate() + } + + fn total_duration(&self) -> Option { + self.inner.total_duration() + } +} + +impl Iterator for Denoiser { + type Item = Sample; + + #[inline] + fn next(&mut self) -> Option { + self.next += 1; + if self.next < self.ready.len() { + let sample = self.ready[self.next]; + return Some(sample); + } + + // This is a separate function to prevent it from being inlined + // as this code only runs once every 128 samples + self.prepare_next_ready() + .inspect_err(|_| { + log::error!("Denoise engine crashed"); + }) + .ok() + .flatten() + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Could not send or receive from denoise thread. It must have crashed")] +struct DenoiseEngineCrashed; + +impl Denoiser { + #[cold] + fn prepare_next_ready(&mut self) -> Result, DenoiseEngineCrashed> { + self.state = match self.state { + IterState::Startup { enabled } => { + // guaranteed to be coming from silence + for _ in 0..3 { + let Some(sub_block) = read_sub_block(&mut self.inner) else { + return Ok(None); + }; + self.queued.push(sub_block); + self.input_tx + .send(sub_block) + .map_err(|_| DenoiseEngineCrashed)?; + } + let Some(sub_block) = read_sub_block(&mut self.inner) else { + return Ok(None); + }; + self.queued.push(sub_block); + self.input_tx + .send(sub_block) + .map_err(|_| DenoiseEngineCrashed)?; + // throw out old blocks that are denoised silence + let _ = self.denoised_rx.iter().take(3).count(); + self.ready = self.denoised_rx.recv().map_err(|_| DenoiseEngineCrashed)?; + + let Some(sub_block) = read_sub_block(&mut self.inner) else { + return Ok(None); + }; + self.queued.push(sub_block); + self.feed(sub_block); + + if enabled { + IterState::Enabled + } else { + IterState::Disabled + } + } + IterState::Enabled => { + self.ready = self.denoised_rx.recv().map_err(|_| DenoiseEngineCrashed)?; + let Some(sub_block) = read_sub_block(&mut self.inner) else { + return Ok(None); + }; + self.queued.push(sub_block); + self.input_tx + .send(sub_block) + .map_err(|_| DenoiseEngineCrashed)?; + IterState::Enabled + } + IterState::Disabled => { + // Need to maintain the same 512 samples delay such that + // we can re-enable at any point. + self.ready = self.queued.pop(); + let Some(sub_block) = read_sub_block(&mut self.inner) else { + return Ok(None); + }; + self.queued.push(sub_block); + IterState::Disabled + } + IterState::StartingMidAudio { + fed_to_denoiser: mut sub_blocks_fed, + } => { + self.ready = self.queued.pop(); + let Some(sub_block) = read_sub_block(&mut self.inner) else { + return Ok(None); + }; + self.queued.push(sub_block); + self.input_tx + .send(sub_block) + .map_err(|_| DenoiseEngineCrashed)?; + sub_blocks_fed += 1; + if sub_blocks_fed > 4 { + // throw out partially denoised blocks, + // next will be correctly denoised + let _ = self.denoised_rx.iter().take(3).count(); + IterState::Enabled + } else { + IterState::StartingMidAudio { + fed_to_denoiser: sub_blocks_fed, + } + } + } + }; + + self.next = 0; + Ok(Some(self.ready[0])) + } +} + +fn read_sub_block(s: &mut impl Source) -> Option<[f32; BLOCK_SHIFT]> { + let mut res = [0f32; BLOCK_SHIFT]; + for sample in &mut res { + *sample = s.next()?; + } + Some(res) +} diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 7201dedb451c3db27998695b75b62847223e6b72..a01989a047c6072db1719ccef6278f80b323123b 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -67,7 +67,7 @@ futures-sink = { version = "0.3" } futures-task = { version = "0.3", default-features = false, features = ["std"] } futures-util = { version = "0.3", features = ["channel", "io-compat", "sink"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["std"] } -half = { version = "2", features = ["num-traits"] } +half = { version = "2", features = ["bytemuck", "num-traits", "rand_distr", "use-intrinsics"] } handlebars = { version = "4", features = ["rust-embed"] } hashbrown-3575ec1268b04181 = { package = "hashbrown", version = "0.15", features = ["serde"] } hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["raw"] } @@ -84,10 +84,12 @@ lyon = { version = "1", default-features = false, features = ["extra"] } lyon_path = { version = "1" } md-5 = { version = "0.10" } memchr = { version = "2" } +memmap2 = { version = "0.9", default-features = false, features = ["stable_deref_trait"] } mime_guess = { version = "2" } miniz_oxide = { version = "0.8", features = ["simd"] } nom = { version = "7" } num-bigint = { version = "0.4" } +num-complex = { version = "0.4", features = ["bytemuck"] } num-integer = { version = "0.1", features = ["i128"] } num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] } num-rational = { version = "0.4", features = ["num-bigint-std"] } @@ -96,11 +98,12 @@ once_cell = { version = "1" } percent-encoding = { version = "2" } phf = { version = "0.11", features = ["macros"] } phf_shared = { version = "0.11" } -prost = { version = "0.9" } +prost-274715c4dabd11b0 = { package = "prost", version = "0.9" } prost-types = { version = "0.9" } rand-c38e5c1d305a1b54 = { package = "rand", version = "0.8", features = ["small_rng"] } rand_chacha = { version = "0.3" } rand_core = { version = "0.6", default-features = false, features = ["std"] } +rand_distr = { version = "0.5" } regalloc2 = { version = "0.11", features = ["checker", "enable-serde"] } regex = { version = "1" } regex-automata = { version = "0.4" } @@ -123,6 +126,7 @@ spin = { version = "0.9" } sqlx = { version = "0.8", features = ["bigdecimal", "chrono", "postgres", "runtime-tokio-rustls", "rust_decimal", "sqlite", "time", "uuid"] } sqlx-postgres = { version = "0.8", default-features = false, features = ["any", "bigdecimal", "chrono", "json", "migrate", "offline", "rust_decimal", "time", "uuid"] } sqlx-sqlite = { version = "0.8", default-features = false, features = ["any", "bundled", "chrono", "json", "migrate", "offline", "time", "uuid"] } +stable_deref_trait = { version = "1" } strum = { version = "0.26", features = ["derive"] } subtle = { version = "2" } thiserror = { version = "2" } @@ -130,6 +134,7 @@ time = { version = "0.3", features = ["local-offset", "macros", "serde-well-know tokio = { version = "1", features = ["full"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"] } tokio-util = { version = "0.7", features = ["codec", "compat", "io"] } +toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } toml_edit = { version = "0.22", features = ["serde"] } tracing = { version = "0.1", features = ["log"] } tracing-core = { version = "0.1" } @@ -198,7 +203,7 @@ futures-sink = { version = "0.3" } futures-task = { version = "0.3", default-features = false, features = ["std"] } futures-util = { version = "0.3", features = ["channel", "io-compat", "sink"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["std"] } -half = { version = "2", features = ["num-traits"] } +half = { version = "2", features = ["bytemuck", "num-traits", "rand_distr", "use-intrinsics"] } handlebars = { version = "4", features = ["rust-embed"] } hashbrown-3575ec1268b04181 = { package = "hashbrown", version = "0.15", features = ["serde"] } hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["raw"] } @@ -217,10 +222,12 @@ lyon = { version = "1", default-features = false, features = ["extra"] } lyon_path = { version = "1" } md-5 = { version = "0.10" } memchr = { version = "2" } +memmap2 = { version = "0.9", default-features = false, features = ["stable_deref_trait"] } mime_guess = { version = "2" } miniz_oxide = { version = "0.8", features = ["simd"] } nom = { version = "7" } num-bigint = { version = "0.4" } +num-complex = { version = "0.4", features = ["bytemuck"] } num-integer = { version = "0.1", features = ["i128"] } num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] } num-rational = { version = "0.4", features = ["num-bigint-std"] } @@ -231,12 +238,13 @@ phf = { version = "0.11", features = ["macros"] } phf_shared = { version = "0.11" } prettyplease = { version = "0.2", default-features = false, features = ["verbatim"] } proc-macro2 = { version = "1" } -prost = { version = "0.9" } +prost-274715c4dabd11b0 = { package = "prost", version = "0.9" } prost-types = { version = "0.9" } quote = { version = "1" } rand-c38e5c1d305a1b54 = { package = "rand", version = "0.8", features = ["small_rng"] } rand_chacha = { version = "0.3" } rand_core = { version = "0.6", default-features = false, features = ["std"] } +rand_distr = { version = "0.5" } regalloc2 = { version = "0.11", features = ["checker", "enable-serde"] } regex = { version = "1" } regex-automata = { version = "0.4" } @@ -261,6 +269,7 @@ sqlx-macros = { version = "0.8", features = ["_rt-tokio", "_tls-rustls-ring-webp sqlx-macros-core = { version = "0.8", features = ["_rt-tokio", "_tls-rustls-ring-webpki", "bigdecimal", "chrono", "derive", "json", "macros", "migrate", "postgres", "rust_decimal", "sqlite", "time", "uuid"] } sqlx-postgres = { version = "0.8", default-features = false, features = ["any", "bigdecimal", "chrono", "json", "migrate", "offline", "rust_decimal", "time", "uuid"] } sqlx-sqlite = { version = "0.8", default-features = false, features = ["any", "bundled", "chrono", "json", "migrate", "offline", "time", "uuid"] } +stable_deref_trait = { version = "1" } strum = { version = "0.26", features = ["derive"] } subtle = { version = "2" } syn-dff4ba8e3ae991db = { package = "syn", version = "1", features = ["extra-traits", "full"] } @@ -271,6 +280,7 @@ time-macros = { version = "0.2", default-features = false, features = ["formatti tokio = { version = "1", features = ["full"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"] } tokio-util = { version = "0.7", features = ["codec", "compat", "io"] } +toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } toml_edit = { version = "0.22", features = ["serde"] } tracing = { version = "0.1", features = ["log"] } tracing-core = { version = "0.1" } @@ -293,15 +303,16 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } objc2-metal = { version = "0.3" } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } @@ -322,16 +333,18 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } objc2-metal = { version = "0.3" } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } @@ -352,15 +365,16 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } objc2-metal = { version = "0.3" } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } @@ -381,16 +395,18 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } objc2-metal = { version = "0.3" } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } @@ -417,7 +433,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -429,6 +444,7 @@ nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "s num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } quote = { version = "1" } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } @@ -440,7 +456,6 @@ sync_wrapper = { version = "1", default-features = false, features = ["futures"] tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } tokio-stream = { version = "0.1", features = ["fs"] } -toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } tower = { version = "0.5", default-features = false, features = ["timeout", "util"] } zeroize = { version = "1", features = ["zeroize_derive"] } zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } @@ -459,7 +474,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -471,6 +486,7 @@ nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "s num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } @@ -480,7 +496,6 @@ sync_wrapper = { version = "1", default-features = false, features = ["futures"] tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } tokio-stream = { version = "0.1", features = ["fs"] } -toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } tower = { version = "0.5", default-features = false, features = ["timeout", "util"] } zeroize = { version = "1", features = ["zeroize_derive"] } zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } @@ -499,7 +514,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -511,6 +525,7 @@ nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "s num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } quote = { version = "1" } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } @@ -522,7 +537,6 @@ sync_wrapper = { version = "1", default-features = false, features = ["futures"] tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } tokio-stream = { version = "0.1", features = ["fs"] } -toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } tower = { version = "0.5", default-features = false, features = ["timeout", "util"] } zeroize = { version = "1", features = ["zeroize_derive"] } zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } @@ -541,7 +555,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -553,6 +567,7 @@ nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "s num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } @@ -562,7 +577,6 @@ sync_wrapper = { version = "1", default-features = false, features = ["futures"] tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } tokio-stream = { version = "0.1", features = ["fs"] } -toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } tower = { version = "0.5", default-features = false, features = ["timeout", "util"] } zeroize = { version = "1", features = ["zeroize_derive"] } zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } @@ -574,8 +588,9 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } +num = { version = "0.4" } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "fs", "net"] } scopeguard = { version = "1" } @@ -587,7 +602,8 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti winapi = { version = "0.3", default-features = false, features = ["cfg", "commapi", "consoleapi", "evntrace", "fileapi", "handleapi", "impl-debug", "impl-default", "in6addr", "inaddr", "ioapiset", "knownfolders", "minwinbase", "minwindef", "namedpipeapi", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "synchapi", "sysinfoapi", "timezoneapi", "winbase", "windef", "winerror", "winioctl", "winnt", "winreg", "winsock2", "winuser"] } windows-core = { version = "0.61" } windows-numerics = { version = "0.2" } -windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } +windows-sys-4db8c43aad08e7ae = { package = "windows-sys", version = "0.60", features = ["Win32_Globalization", "Win32_System_Com", "Win32_UI_Shell"] } +windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } @@ -598,9 +614,11 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } +num = { version = "0.4" } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "fs", "net"] } scopeguard = { version = "1" } @@ -612,7 +630,8 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti winapi = { version = "0.3", default-features = false, features = ["cfg", "commapi", "consoleapi", "evntrace", "fileapi", "handleapi", "impl-debug", "impl-default", "in6addr", "inaddr", "ioapiset", "knownfolders", "minwinbase", "minwindef", "namedpipeapi", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "synchapi", "sysinfoapi", "timezoneapi", "winbase", "windef", "winerror", "winioctl", "winnt", "winreg", "winsock2", "winuser"] } windows-core = { version = "0.61" } windows-numerics = { version = "0.2" } -windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } +windows-sys-4db8c43aad08e7ae = { package = "windows-sys", version = "0.60", features = ["Win32_Globalization", "Win32_System_Com", "Win32_UI_Shell"] } +windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } @@ -630,7 +649,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -642,6 +660,7 @@ nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "s num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } quote = { version = "1" } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } @@ -653,7 +672,6 @@ sync_wrapper = { version = "1", default-features = false, features = ["futures"] tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } tokio-stream = { version = "0.1", features = ["fs"] } -toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } tower = { version = "0.5", default-features = false, features = ["timeout", "util"] } zeroize = { version = "1", features = ["zeroize_derive"] } zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } @@ -672,7 +690,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -684,6 +702,7 @@ nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "s num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } +prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } @@ -693,7 +712,6 @@ sync_wrapper = { version = "1", default-features = false, features = ["futures"] tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } tokio-stream = { version = "0.1", features = ["fs"] } -toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } tower = { version = "0.5", default-features = false, features = ["timeout", "util"] } zeroize = { version = "1", features = ["zeroize_derive"] } zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } From e14a4ab90daffc438886509e47ff7215bf6f9342 Mon Sep 17 00:00:00 2001 From: Uwe Krause Date: Tue, 16 Sep 2025 23:58:40 +0200 Subject: [PATCH 063/721] Fix small spelling mistakes (#38284) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- .rules | 2 +- crates/audio/src/audio.rs | 2 +- crates/audio/src/rodio_ext.rs | 12 ++++++------ crates/cloud_llm_client/src/cloud_llm_client.rs | 2 +- crates/editor/src/editor_tests.rs | 2 +- crates/gpui/src/platform/windows/platform.rs | 2 +- docs/src/development/glossary.md | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.rules b/.rules index 2f2b9cd705d95775bedf092bc4e6254136da6117..82d15eb9e88299ee7c7fe6c717b2da2646e676a7 100644 --- a/.rules +++ b/.rules @@ -59,7 +59,7 @@ Trying to update an entity while it's already being updated must be avoided as t When `read_with`, `update`, or `update_in` are used with an async context, the closure's return value is wrapped in an `anyhow::Result`. -`WeakEntity` is a weak handle. It has `read_with`, `update`, and `update_in` methods that work the same, but always return an `anyhow::Result` so that they can fail if the entity no longer exists. This can be useful to avoid memory leaks - if entities have mutually recursive handles to eachother they will never be dropped. +`WeakEntity` is a weak handle. It has `read_with`, `update`, and `update_in` methods that work the same, but always return an `anyhow::Result` so that they can fail if the entity no longer exists. This can be useful to avoid memory leaks - if entities have mutually recursive handles to each other they will never be dropped. ## Concurrency diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs index ab8d85cdaa6bab7ed1be3fdab8a66b42f883533b..f60ddb87b9615d2da9c2be248ab397c19a463616 100644 --- a/crates/audio/src/audio.rs +++ b/crates/audio/src/audio.rs @@ -211,7 +211,7 @@ impl Audio { agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); }) .replayable(REPLAY_DURATION) - .expect("REPLAY_DURATION is longer then 100ms"); + .expect("REPLAY_DURATION is longer than 100ms"); cx.update_default_global(|this: &mut Self, _cx| { let output_mixer = this diff --git a/crates/audio/src/rodio_ext.rs b/crates/audio/src/rodio_ext.rs index ba4e4ff0554dd3c9bc2a7e2691de270c0d00908b..e80b00e15a8fdbd3fc438b78a9ca45d0902dcef1 100644 --- a/crates/audio/src/rodio_ext.rs +++ b/crates/audio/src/rodio_ext.rs @@ -57,7 +57,7 @@ impl RodioExt for S { /// replay is being read /// /// # Errors - /// If duration is smaller then 100ms + /// If duration is smaller than 100ms fn replayable( self, duration: Duration, @@ -151,7 +151,7 @@ impl Source for TakeSamples { struct ReplayQueue { inner: ArrayQueue>, normal_chunk_len: usize, - /// The last chunk in the queue may be smaller then + /// The last chunk in the queue may be smaller than /// the normal chunk size. This is always equal to the /// size of the last element in the queue. /// (so normally chunk_size) @@ -535,7 +535,7 @@ mod tests { let (mut replay, mut source) = input .replayable(Duration::from_secs(3)) - .expect("longer then 100ms"); + .expect("longer than 100ms"); source.by_ref().take(3).count(); let yielded: Vec = replay.by_ref().take(3).collect(); @@ -552,7 +552,7 @@ mod tests { let (mut replay, mut source) = input .replayable(Duration::from_secs(2)) - .expect("longer then 100ms"); + .expect("longer than 100ms"); source.by_ref().take(5).count(); // get all items but do not end the source let yielded: Vec = replay.by_ref().take(2).collect(); @@ -567,7 +567,7 @@ mod tests { let (replay, mut source) = input .replayable(Duration::from_secs(2)) - .expect("longer then 100ms"); + .expect("longer than 100ms"); // exhaust but do not yet end source source.by_ref().take(40_000).count(); @@ -586,7 +586,7 @@ mod tests { let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]); let (mut replay, source) = input .replayable(Duration::from_secs(2)) - .expect("longer then 100ms"); + .expect("longer than 100ms"); assert_eq!(replay.by_ref().samples_ready(), 0); source.take(8000).count(); // half a second diff --git a/crates/cloud_llm_client/src/cloud_llm_client.rs b/crates/cloud_llm_client/src/cloud_llm_client.rs index 16267d86d806387140016dc0a25021ad92607ff2..12ea6be1f332960f4db7e9a7d67b7b3b926d905f 100644 --- a/crates/cloud_llm_client/src/cloud_llm_client.rs +++ b/crates/cloud_llm_client/src/cloud_llm_client.rs @@ -39,7 +39,7 @@ pub const EDIT_PREDICTIONS_RESOURCE_HEADER_VALUE: &str = "edit_predictions"; /// The name of the header used to indicate that the maximum number of consecutive tool uses has been reached. pub const TOOL_USE_LIMIT_REACHED_HEADER_NAME: &str = "x-zed-tool-use-limit-reached"; -/// The name of the header used to indicate the the minimum required Zed version. +/// The name of the header used to indicate the minimum required Zed version. /// /// This can be used to force a Zed upgrade in order to continue communicating /// with the LLM service. diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 131b03c61b9a36ca73bf91fbc3bdf35b0000e3ee..a8be7e020c01671d6ed1eeb9e3968cba1252f6df 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -19265,7 +19265,7 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) { cx.executor().run_until_parked(); // When the start of a hunk coincides with the start of its excerpt, - // the hunk is expanded. When the start of a a hunk is earlier than + // the hunk is expanded. When the start of a hunk is earlier than // the start of its excerpt, the hunk is not expanded. cx.assert_state_with_diff( " diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 4d0e6ea56f7d90f303f6634de1239a6a4542429a..b1e2c123e881cecadcdcb6582056c0e5fba64c14 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -1016,7 +1016,7 @@ fn handle_gpu_device_lost( all_windows: &std::sync::Weak>>, text_system: &std::sync::Weak, ) { - // Here we wait a bit to ensure the the system has time to recover from the device lost state. + // Here we wait a bit to ensure the system has time to recover from the device lost state. // If we don't wait, the final drawing result will be blank. std::thread::sleep(std::time::Duration::from_millis(350)); diff --git a/docs/src/development/glossary.md b/docs/src/development/glossary.md index d0ae12fe03a9955667a69eeb6e270981421b6c02..b3ff24464c12d9c00adb1e509c41f123dba3cb8c 100644 --- a/docs/src/development/glossary.md +++ b/docs/src/development/glossary.md @@ -23,7 +23,7 @@ here. An example would be `AnyElement` and `LspStore`. ## GPUI -### State menagement +### State management - `App`: A singleton which holds the full application state including all the entities. Crucially: `App` is not `Send`, which means that `App` only exists on the thread that created it (which is the main/UI thread, usually). Thus, if you see a `&mut App`, know that you're on UI thread. - `Context`: A wrapper around the `App` struct with specialized behavior for a specific `Entity`. Think of it as `(&mut App, Entity)`. The specialized behavior is surfaced in the API surface of `Context`. E.g., `App::spawn` takes an `AsyncFnOnce(AsyncApp) -> Ret`, whereas `Context::spawn` takes an `AsyncFnOnce(WeakEntity, AsyncApp) -> Ret`. @@ -67,7 +67,7 @@ h_flex() - `Component`: A builder which can be rendered turning it into an `Element`. - `Dispatch tree`: TODO - `Focus`: The place where keystrokes are handled first -- `Focus tree`: Path from the place thats the current focus to the UI Root. Example TODO +- `Focus tree`: Path from the place that has the current focus to the UI Root. Example TODO ## Zed UI From 54c82f27324bf14ac22a7c0b1cb1dc69cd95e42d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 Sep 2025 15:12:02 -0700 Subject: [PATCH 064/721] Windows: Unminimize a window when activating it (#38287) Closes #36287 Release Notes: - Windows: Fixed an issue where a Zed window would stay minimized when opening an existing file in that window via the Zed CLI. --- crates/gpui/src/platform/windows/window.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index aa907c8d734973fc4fc795b6d8ebf7654d1b40de..7abb4ee21a1a28356e15d09be3c22c688bb7e033 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -684,8 +684,16 @@ impl PlatformWindow for WindowsWindow { .executor .spawn(async move { this.set_window_placement().log_err(); - unsafe { SetActiveWindow(hwnd).log_err() }; - unsafe { SetFocus(Some(hwnd)).log_err() }; + + unsafe { + // If the window is minimized, restore it. + if IsIconic(hwnd).as_bool() { + ShowWindowAsync(hwnd, SW_RESTORE).ok().log_err(); + } + + SetActiveWindow(hwnd).log_err(); + SetFocus(Some(hwnd)).log_err(); + } // premium ragebait by windows, this is needed because the window // must have received an input event to be able to set itself to foreground From ee399ebccffe340829dbd991e5032acb8c8f8579 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 17 Sep 2025 03:49:47 +0530 Subject: [PATCH 065/721] macOS: Make it easier to debug NSAutoFillHeuristicControllerEnabled (#38285) Uses `setObject` instead of `registerDefaults`, so that it can be read with `defaults read dev.zed.Zed`. Still can be overrided. Release Notes: - N/A --- crates/gpui/src/platform/mac/platform.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 9bb1993f13b0444d7bec2721f160b81509629106..9909c78c472a17e683380be71b65484800c0fa76 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1363,19 +1363,17 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) { unsafe { let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults]; - let defaults_dict: id = msg_send![class!(NSMutableDictionary), dictionary]; // The autofill heuristic controller causes slowdown and high CPU usage. // We don't know exactly why. This disables the full heuristic controller. // // Adapted from: https://github.com/ghostty-org/ghostty/pull/8625 - let false_value: id = msg_send![class!(NSNumber), numberWithBool:false]; - let _: () = msg_send![defaults_dict, - setObject: false_value - forKey: ns_string("NSAutoFillHeuristicControllerEnabled") - ]; - - let _: () = msg_send![user_defaults, registerDefaults:defaults_dict]; + let name = ns_string("NSAutoFillHeuristicControllerEnabled"); + let existing_value: id = msg_send![user_defaults, objectForKey: name]; + if existing_value == nil { + let false_value: id = msg_send![class!(NSNumber), numberWithBool:false]; + let _: () = msg_send![user_defaults, setObject: false_value forKey: name]; + } } } From 74e5b848ff2007102fbb3cf8fb44d7e496659d08 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Sep 2025 18:24:03 -0400 Subject: [PATCH 066/721] cloud_llm_client: Make `default_model` and `default_fast_model` optional (#38288) This PR makes the `default_model` and `default_fast_model` fields optional on the `ListModelsResponse`. Release Notes: - N/A --- crates/cloud_llm_client/src/cloud_llm_client.rs | 4 ++-- crates/language_models/src/provider/cloud.rs | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/cloud_llm_client/src/cloud_llm_client.rs b/crates/cloud_llm_client/src/cloud_llm_client.rs index 12ea6be1f332960f4db7e9a7d67b7b3b926d905f..24923a318441afeaa2521064b4f433ab9ee1e55f 100644 --- a/crates/cloud_llm_client/src/cloud_llm_client.rs +++ b/crates/cloud_llm_client/src/cloud_llm_client.rs @@ -321,8 +321,8 @@ pub struct LanguageModel { #[derive(Debug, Serialize, Deserialize)] pub struct ListModelsResponse { pub models: Vec, - pub default_model: LanguageModelId, - pub default_fast_model: LanguageModelId, + pub default_model: Option, + pub default_fast_model: Option, pub recommended_models: Vec, } diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 6393117185e9104bec77f14fe334e94bb18679db..24a4ba6cc260a91de170bb665c86756c8d7a25ca 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -215,11 +215,21 @@ impl State { self.default_model = models .iter() - .find(|model| model.id == response.default_model) + .find(|model| { + response + .default_model + .as_ref() + .is_some_and(|default_model_id| &model.id == default_model_id) + }) .cloned(); self.default_fast_model = models .iter() - .find(|model| model.id == response.default_fast_model) + .find(|model| { + response + .default_fast_model + .as_ref() + .is_some_and(|default_fast_model_id| &model.id == default_fast_model_id) + }) .cloned(); self.recommended_models = response .recommended_models From f6c5c68751f686a4cf90ea1a1d32a361cb7c888e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Sep 2025 18:53:44 -0400 Subject: [PATCH 067/721] collab: Remove user backfiller (#38291) This PR removes the user backfiller from Collab. Release Notes: - N/A --- crates/collab/k8s/collab.template.yml | 6 - crates/collab/src/lib.rs | 3 - crates/collab/src/main.rs | 2 - crates/collab/src/tests/test_server.rs | 1 - crates/collab/src/user_backfiller.rs | 165 ------------------------- 5 files changed, 177 deletions(-) delete mode 100644 crates/collab/src/user_backfiller.rs diff --git a/crates/collab/k8s/collab.template.yml b/crates/collab/k8s/collab.template.yml index 214b550ac20499b8b03cfafeefab9b45d51fcc24..1476e5890283c62cee3563a327fcdd5ee84842e7 100644 --- a/crates/collab/k8s/collab.template.yml +++ b/crates/collab/k8s/collab.template.yml @@ -226,12 +226,6 @@ spec: secretKeyRef: name: supermaven key: api_key - - name: USER_BACKFILLER_GITHUB_ACCESS_TOKEN - valueFrom: - secretKeyRef: - name: user-backfiller - key: github_access_token - optional: true - name: INVITE_LINK_PREFIX value: ${INVITE_LINK_PREFIX} - name: RUST_BACKTRACE diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 191025df3770db78df3a12bc16d5c8f32d54571c..f1de0cdc7ff79cd25c8ef7b0b2b21d9e0b45d332 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -7,7 +7,6 @@ pub mod llm; pub mod migrations; pub mod rpc; pub mod seed; -pub mod user_backfiller; #[cfg(test)] mod tests; @@ -157,7 +156,6 @@ pub struct Config { pub slack_panics_webhook: Option, pub auto_join_channel_id: Option, pub supermaven_admin_api_key: Option>, - pub user_backfiller_github_access_token: Option>, } impl Config { @@ -211,7 +209,6 @@ impl Config { migrations_path: None, seed_path: None, supermaven_admin_api_key: None, - user_backfiller_github_access_token: None, kinesis_region: None, kinesis_access_key: None, kinesis_secret_key: None, diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index cb6f6cad1dd483c463bcda5d8a4ff914f4bf10aa..6b94459910647c1e48ee69f2b0dd38afd3723821 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -11,7 +11,6 @@ use collab::ServiceMode; use collab::api::CloudflareIpCountryHeader; use collab::llm::db::LlmDatabase; use collab::migrations::run_database_migrations; -use collab::user_backfiller::spawn_user_backfiller; use collab::{ AppState, Config, Result, api::fetch_extensions_from_blob_store_periodically, db, env, executor::Executor, rpc::ResultExt, @@ -114,7 +113,6 @@ async fn main() -> Result<()> { if mode.is_api() { fetch_extensions_from_blob_store_periodically(state.clone()); - spawn_user_backfiller(state.clone()); app = app .merge(collab::api::events::router()) diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index eb7df28478158a10a0c2d52c3560cad391937383..5e99cc192ad080c1a79913c79fbbaae9d8b6d951 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -604,7 +604,6 @@ impl TestServer { migrations_path: None, seed_path: None, supermaven_admin_api_key: None, - user_backfiller_github_access_token: None, kinesis_region: None, kinesis_stream: None, kinesis_access_key: None, diff --git a/crates/collab/src/user_backfiller.rs b/crates/collab/src/user_backfiller.rs deleted file mode 100644 index fdb9ef67c2f1d04bf0a1919045f91d75a14ef834..0000000000000000000000000000000000000000 --- a/crates/collab/src/user_backfiller.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::sync::Arc; - -use anyhow::{Context as _, Result}; -use chrono::{DateTime, Utc}; -use util::ResultExt; - -use crate::db::Database; -use crate::executor::Executor; -use crate::{AppState, Config}; - -pub fn spawn_user_backfiller(app_state: Arc) { - let Some(user_backfiller_github_access_token) = - app_state.config.user_backfiller_github_access_token.clone() - else { - log::info!("no USER_BACKFILLER_GITHUB_ACCESS_TOKEN set; not spawning user backfiller"); - return; - }; - - let executor = app_state.executor.clone(); - executor.spawn_detached({ - let executor = executor.clone(); - async move { - let user_backfiller = UserBackfiller::new( - app_state.config.clone(), - user_backfiller_github_access_token, - app_state.db.clone(), - executor, - ); - - log::info!("backfilling users"); - - user_backfiller - .backfill_github_user_created_at() - .await - .log_err(); - } - }); -} - -const GITHUB_REQUESTS_PER_HOUR_LIMIT: usize = 5_000; -const SLEEP_DURATION_BETWEEN_USERS: std::time::Duration = std::time::Duration::from_millis( - (GITHUB_REQUESTS_PER_HOUR_LIMIT as f64 / 60. / 60. * 1000.) as u64, -); - -struct UserBackfiller { - config: Config, - github_access_token: Arc, - db: Arc, - http_client: reqwest::Client, - executor: Executor, -} - -impl UserBackfiller { - fn new( - config: Config, - github_access_token: Arc, - db: Arc, - executor: Executor, - ) -> Self { - Self { - config, - github_access_token, - db, - http_client: reqwest::Client::new(), - executor, - } - } - - async fn backfill_github_user_created_at(&self) -> Result<()> { - let initial_channel_id = self.config.auto_join_channel_id; - - let users_missing_github_user_created_at = - self.db.get_users_missing_github_user_created_at().await?; - - for user in users_missing_github_user_created_at { - match self - .fetch_github_user(&format!( - "https://api.github.com/user/{}", - user.github_user_id - )) - .await - { - Ok(github_user) => { - self.db - .update_or_create_user_by_github_account( - &user.github_login, - github_user.id, - user.email_address.as_deref(), - user.name.as_deref(), - github_user.created_at, - initial_channel_id, - ) - .await?; - - log::info!("backfilled user: {}", user.github_login); - } - Err(err) => { - log::error!("failed to fetch GitHub user {}: {err}", user.github_login); - } - } - - self.executor.sleep(SLEEP_DURATION_BETWEEN_USERS).await; - } - - Ok(()) - } - - async fn fetch_github_user(&self, url: &str) -> Result { - let response = self - .http_client - .get(url) - .header( - "authorization", - format!("Bearer {}", self.github_access_token), - ) - .header("user-agent", "zed") - .send() - .await - .with_context(|| format!("failed to fetch '{url}'"))?; - - let rate_limit_remaining = response - .headers() - .get("x-ratelimit-remaining") - .and_then(|value| value.to_str().ok()) - .and_then(|value| value.parse::().ok()); - let rate_limit_reset = response - .headers() - .get("x-ratelimit-reset") - .and_then(|value| value.to_str().ok()) - .and_then(|value| value.parse::().ok()) - .and_then(|value| DateTime::from_timestamp(value, 0)); - - 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; - } - } - - response - .error_for_status() - .context("fetching GitHub user")? - .json() - .await - .with_context(|| format!("failed to deserialize GitHub user from '{url}'")) - } -} - -#[derive(serde::Deserialize)] -struct GithubUser { - id: i32, - created_at: DateTime, - #[expect( - unused, - reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" - )] - name: Option, -} From c27d8e0c7a66cfc2ad32d7bd17267e5d85d74956 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Tue, 16 Sep 2025 21:58:24 -0400 Subject: [PATCH 068/721] editor: Don't pull diagnostics on excerpts change in diagnostics editors (#38212) This can lead to an infinite regress when using a language server that supports pull diagnostics, since the excerpts for the diagnostics editor are set based on the project's diagnostics. Closes #36772 Release Notes: - Fixed a bug that could cause duplicated diagnostics with some language servers. --- crates/editor/src/editor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 00ae0bcbac1e49e193739ed8b8af77a8c02dc845..8f69efe30ef9f3be5f95b49162bb57baa43346dc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -20549,7 +20549,9 @@ impl Editor { ) .detach(); } - self.update_lsp_data(false, Some(buffer_id), window, cx); + if self.active_diagnostics != ActiveDiagnostic::All { + self.update_lsp_data(false, Some(buffer_id), window, cx); + } cx.emit(EditorEvent::ExcerptsAdded { buffer: buffer.clone(), predecessor: *predecessor, From 4ee2daeded8da156f2d2319b15e0c3d2a2e16dc7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 08:48:47 +0200 Subject: [PATCH 069/721] markdown: Fix indented codeblocks having incorrect content ranges (#38225) Closes https://github.com/zed-industries/zed/issues/37743 Release Notes: - Fixed agent panel panicking when streaming indented codeblocks from agent output --- crates/markdown/src/markdown.rs | 4 ++-- crates/markdown/src/parser.rs | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 4e1d3ac51e148439e57a4a1c305dabc31cbc2046..c2f8025e32d70cdd9500afdf0a4fc02a334a8521 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -1079,7 +1079,7 @@ impl Element for MarkdownElement { { builder.modify_current_div(|el| { let content_range = parser::extract_code_block_content_range( - parsed_markdown.source()[range.clone()].trim(), + &parsed_markdown.source()[range.clone()], ); let content_range = content_range.start + range.start ..content_range.end + range.start; @@ -1110,7 +1110,7 @@ impl Element for MarkdownElement { { builder.modify_current_div(|el| { let content_range = parser::extract_code_block_content_range( - parsed_markdown.source()[range.clone()].trim(), + &parsed_markdown.source()[range.clone()], ); let content_range = content_range.start + range.start ..content_range.end + range.start; diff --git a/crates/markdown/src/parser.rs b/crates/markdown/src/parser.rs index 3720e5b1ef5f61f0a209ac5617119de61ed05517..d60d34b41e7efc99970f72b15a8ea9c4c79eb6f9 100644 --- a/crates/markdown/src/parser.rs +++ b/crates/markdown/src/parser.rs @@ -67,7 +67,7 @@ pub fn parse_markdown( MarkdownTag::CodeBlock { kind: CodeBlockKind::Indented, metadata: CodeBlockMetadata { - content_range: range.start + 1..range.end + 1, + content_range: range.clone(), line_count: 1, }, } @@ -698,7 +698,28 @@ mod tests { HashSet::from(["rust".into()]), HashSet::new() ) - ) + ); + assert_eq!( + parse_markdown(" fn main() {}"), + ( + vec![ + ( + 4..16, + Start(CodeBlock { + kind: CodeBlockKind::Indented, + metadata: CodeBlockMetadata { + content_range: 4..16, + line_count: 1 + } + }) + ), + (4..16, Text), + (4..16, End(MarkdownTagEnd::CodeBlock)) + ], + HashSet::new(), + HashSet::new() + ) + ); } #[test] From 5d561aa494d764ada3feaf9e8bb7e789fda292d1 Mon Sep 17 00:00:00 2001 From: Kyrilasa <46592077+Kyrilasa@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:10:21 +0200 Subject: [PATCH 070/721] agent_ui: Fix agent panel insertion to use cursor position (#38253) Fix agent panel insertion to use cursor position Closes #38216 Release Notes: - Fixed agent panel text insertion to respect cursor position instead of always appending to the end ## Before [before.webm](https://github.com/user-attachments/assets/684d3cbe-4710-4724-8d2d-ac08f430dea8) ## After [output.webm](https://github.com/user-attachments/assets/d1122d99-4efb-4a24-a408-db128814f98c) --- crates/agent_ui/src/acp/message_editor.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index ab4e8d680925c96e64fdb2e7707bea9c1e177b5c..2734726ddbe1608356bcc34af5c42f479d5a8e8a 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -1099,11 +1099,16 @@ impl MessageEditor { } pub fn insert_selections(&mut self, window: &mut Window, cx: &mut Context) { - let buffer = self.editor.read(cx).buffer().clone(); - let Some(buffer) = buffer.read(cx).as_singleton() else { + let editor = self.editor.read(cx); + let editor_buffer = editor.buffer().read(cx); + let Some(buffer) = editor_buffer.as_singleton() else { return; }; - let anchor = buffer.update(cx, |buffer, _cx| buffer.anchor_before(buffer.len())); + let cursor_anchor = editor.selections.newest_anchor().head(); + let cursor_offset = cursor_anchor.to_offset(&editor_buffer.snapshot(cx)); + let anchor = buffer.update(cx, |buffer, _cx| { + buffer.anchor_before(cursor_offset.min(buffer.len())) + }); let Some(workspace) = self.workspace.upgrade() else { return; }; @@ -1117,13 +1122,7 @@ impl MessageEditor { return; }; self.editor.update(cx, |message_editor, cx| { - message_editor.edit( - [( - multi_buffer::Anchor::max()..multi_buffer::Anchor::max(), - completion.new_text, - )], - cx, - ); + message_editor.edit([(cursor_anchor..cursor_anchor, completion.new_text)], cx); }); if let Some(confirm) = completion.confirm { confirm(CompletionIntent::Complete, window, cx); From 64d362cbcea31990bf1d7d3a65c6d0dfa076eb77 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Wed, 17 Sep 2025 01:25:14 -0600 Subject: [PATCH 071/721] edit prediction: Initial implementation of Tree-sitter index (not yet used) (#38301) Release Notes: - N/A --------- Co-authored-by: Agus Co-authored-by: oleksiy --- Cargo.lock | 8 + Cargo.toml | 1 + crates/edit_prediction_context/Cargo.toml | 16 +- .../src/edit_prediction_context.rs | 7 +- crates/edit_prediction_context/src/excerpt.rs | 21 + crates/edit_prediction_context/src/outline.rs | 130 +++ .../edit_prediction_context/src/reference.rs | 109 +++ .../src/tree_sitter_index.rs | 833 ++++++++++++++++++ crates/gpui/Cargo.toml | 2 +- crates/language/src/buffer.rs | 12 +- crates/language/src/language.rs | 49 +- .../src/syntax_map/syntax_map_tests.rs | 7 +- crates/util/src/util.rs | 9 + 13 files changed, 1186 insertions(+), 18 deletions(-) create mode 100644 crates/edit_prediction_context/src/outline.rs create mode 100644 crates/edit_prediction_context/src/reference.rs create mode 100644 crates/edit_prediction_context/src/tree_sitter_index.rs diff --git a/Cargo.lock b/Cargo.lock index 4709dd34f4ab090d06e49827fc3b3b04eece6659..43a2fc4041fbf76b57e62d335e94c695ef07fc12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5138,11 +5138,19 @@ dependencies = [ name = "edit_prediction_context" version = "0.1.0" dependencies = [ + "anyhow", + "arrayvec", + "collections", + "futures 0.3.31", "gpui", "indoc", "language", "log", "pretty_assertions", + "project", + "serde_json", + "settings", + "slotmap", "text", "tree-sitter", "util", diff --git a/Cargo.toml b/Cargo.toml index d3bbb85f3dbb90e52fc9b95573f6367a303c6479..e69885a835f5f579ac5ac5fa0063f5291a1d01f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -634,6 +634,7 @@ sha2 = "0.10" shellexpand = "2.1.0" shlex = "1.3.0" simplelog = "0.12.2" +slotmap = "1.0.6" smallvec = { version = "1.6", features = ["union"] } smol = "2.0" sqlformat = "0.2" diff --git a/crates/edit_prediction_context/Cargo.toml b/crates/edit_prediction_context/Cargo.toml index 6729dcd39b67c5374d745b1811b73a8b8af4f2aa..ad455b0a4ecb1746debafd23f0503b4365f9a0cf 100644 --- a/crates/edit_prediction_context/Cargo.toml +++ b/crates/edit_prediction_context/Cargo.toml @@ -12,18 +12,28 @@ workspace = true path = "src/edit_prediction_context.rs" [dependencies] +anyhow.workspace = true +arrayvec.workspace = true +collections.workspace = true +gpui.workspace = true language.workspace = true -workspace-hack.workspace = true -tree-sitter.workspace = true -text.workspace = true log.workspace = true +project.workspace = true +slotmap.workspace = true +text.workspace = true +tree-sitter.workspace = true util.workspace = true +workspace-hack.workspace = true [dev-dependencies] +futures.workspace = true gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true language = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true +project = {workspace= true, features = ["test-support"]} +serde_json.workspace = true +settings = {workspace= true, features = ["test-support"]} text = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] } zlog.workspace = true diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs index 7e6cad45a8032cb1afaafd95eb10c80e61cff097..acfb89880c3ed9e7b1ebcacd4b5fa313830165ba 100644 --- a/crates/edit_prediction_context/src/edit_prediction_context.rs +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -1,3 +1,8 @@ mod excerpt; +mod outline; +mod reference; +mod tree_sitter_index; -pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions}; +pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions, EditPredictionExcerptText}; +pub use reference::references_in_excerpt; +pub use tree_sitter_index::{BufferDeclaration, Declaration, FileDeclaration, TreeSitterIndex}; diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index 5a20da76f71834d226707fe36f93d3667158372c..c6caa6a1b7b4076cf739c1ac198656b9fba431a6 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -38,7 +38,28 @@ pub struct EditPredictionExcerpt { pub size: usize, } +#[derive(Clone)] +pub struct EditPredictionExcerptText { + pub body: String, + pub parent_signatures: Vec, +} + impl EditPredictionExcerpt { + pub fn text(&self, buffer: &BufferSnapshot) -> EditPredictionExcerptText { + let body = buffer + .text_for_range(self.range.clone()) + .collect::(); + let parent_signatures = self + .parent_signature_ranges + .iter() + .map(|range| buffer.text_for_range(range.clone()).collect::()) + .collect(); + EditPredictionExcerptText { + body, + parent_signatures, + } + } + /// Selects an excerpt around a buffer position, attempting to choose logical boundaries based /// on TreeSitter structure and approximately targeting a goal ratio of bytesbefore vs after the /// cursor. When `include_parent_signatures` is true, the excerpt also includes the signatures diff --git a/crates/edit_prediction_context/src/outline.rs b/crates/edit_prediction_context/src/outline.rs new file mode 100644 index 0000000000000000000000000000000000000000..492352add1fd4c666eab3b12989f9b801d03570f --- /dev/null +++ b/crates/edit_prediction_context/src/outline.rs @@ -0,0 +1,130 @@ +use language::{BufferSnapshot, LanguageId, SyntaxMapMatches}; +use std::{cmp::Reverse, ops::Range, sync::Arc}; + +// TODO: +// +// * how to handle multiple name captures? for now last one wins +// +// * annotation ranges +// +// * new "signature" capture for outline queries +// +// * Check parent behavior of "int x, y = 0" declarations in a test + +pub struct OutlineDeclaration { + pub parent_index: Option, + pub identifier: Identifier, + pub item_range: Range, + pub signature_range: Range, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Identifier { + pub name: Arc, + pub language_id: LanguageId, +} + +pub fn declarations_in_buffer(buffer: &BufferSnapshot) -> Vec { + declarations_overlapping_range(0..buffer.len(), buffer) +} + +pub fn declarations_overlapping_range( + range: Range, + buffer: &BufferSnapshot, +) -> Vec { + let mut declarations = OutlineIterator::new(range, buffer).collect::>(); + declarations.sort_unstable_by_key(|item| (item.item_range.start, Reverse(item.item_range.end))); + + let mut parent_stack: Vec<(usize, Range)> = Vec::new(); + for (index, declaration) in declarations.iter_mut().enumerate() { + while let Some((top_parent_index, top_parent_range)) = parent_stack.last() { + if declaration.item_range.start >= top_parent_range.end { + parent_stack.pop(); + } else { + declaration.parent_index = Some(*top_parent_index); + break; + } + } + parent_stack.push((index, declaration.item_range.clone())); + } + declarations +} + +/// Iterates outline items without being ordered w.r.t. nested items and without populating +/// `parent`. +pub struct OutlineIterator<'a> { + buffer: &'a BufferSnapshot, + matches: SyntaxMapMatches<'a>, +} + +impl<'a> OutlineIterator<'a> { + pub fn new(range: Range, buffer: &'a BufferSnapshot) -> Self { + let matches = buffer.syntax.matches(range, &buffer.text, |grammar| { + grammar.outline_config.as_ref().map(|c| &c.query) + }); + + Self { buffer, matches } + } +} + +impl<'a> Iterator for OutlineIterator<'a> { + type Item = OutlineDeclaration; + + fn next(&mut self) -> Option { + while let Some(mat) = self.matches.peek() { + let config = self.matches.grammars()[mat.grammar_index] + .outline_config + .as_ref() + .unwrap(); + + let mut name_range = None; + let mut item_range = None; + let mut signature_start = None; + let mut signature_end = None; + + let mut add_to_signature = |range: Range| { + if signature_start.is_none() { + signature_start = Some(range.start); + } + signature_end = Some(range.end); + }; + + for capture in mat.captures { + let range = capture.node.byte_range(); + if capture.index == config.name_capture_ix { + name_range = Some(range.clone()); + add_to_signature(range); + } else if Some(capture.index) == config.context_capture_ix + || Some(capture.index) == config.extra_context_capture_ix + { + add_to_signature(range); + } else if capture.index == config.item_capture_ix { + item_range = Some(range.clone()); + } + } + + let language_id = mat.language.id(); + self.matches.advance(); + + if let Some(name_range) = name_range + && let Some(item_range) = item_range + && let Some(signature_start) = signature_start + && let Some(signature_end) = signature_end + { + let name = self + .buffer + .text_for_range(name_range) + .collect::() + .into(); + + return Some(OutlineDeclaration { + identifier: Identifier { name, language_id }, + item_range: item_range, + signature_range: signature_start..signature_end, + parent_index: None, + }); + } + } + None + } +} diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs new file mode 100644 index 0000000000000000000000000000000000000000..65d34e73bf20f62b24ac2a654af43fc3b83041a9 --- /dev/null +++ b/crates/edit_prediction_context/src/reference.rs @@ -0,0 +1,109 @@ +use language::BufferSnapshot; +use std::collections::HashMap; +use std::ops::Range; + +use crate::{ + excerpt::{EditPredictionExcerpt, EditPredictionExcerptText}, + outline::Identifier, +}; + +#[derive(Debug)] +pub struct Reference { + pub identifier: Identifier, + pub range: Range, + pub region: ReferenceRegion, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ReferenceRegion { + Breadcrumb, + Nearby, +} + +pub fn references_in_excerpt( + excerpt: &EditPredictionExcerpt, + excerpt_text: &EditPredictionExcerptText, + snapshot: &BufferSnapshot, +) -> HashMap> { + let mut references = identifiers_in_range( + excerpt.range.clone(), + excerpt_text.body.as_str(), + ReferenceRegion::Nearby, + snapshot, + ); + + for (range, text) in excerpt + .parent_signature_ranges + .iter() + .zip(excerpt_text.parent_signatures.iter()) + { + references.extend(identifiers_in_range( + range.clone(), + text.as_str(), + ReferenceRegion::Breadcrumb, + snapshot, + )); + } + + let mut identifier_to_references: HashMap> = HashMap::new(); + for reference in references { + identifier_to_references + .entry(reference.identifier.clone()) + .or_insert_with(Vec::new) + .push(reference); + } + identifier_to_references +} + +/// Finds all nodes which have a "variable" match from the highlights query within the offset range. +pub fn identifiers_in_range( + range: Range, + range_text: &str, + reference_region: ReferenceRegion, + buffer: &BufferSnapshot, +) -> Vec { + let mut matches = buffer + .syntax + .matches(range.clone(), &buffer.text, |grammar| { + grammar + .highlights_config + .as_ref() + .map(|config| &config.query) + }); + + let mut references = Vec::new(); + let mut last_added_range = None; + while let Some(mat) = matches.peek() { + let config = matches.grammars()[mat.grammar_index] + .highlights_config + .as_ref(); + + for capture in mat.captures { + if let Some(config) = config { + if config.identifier_capture_indices.contains(&capture.index) { + let node_range = capture.node.byte_range(); + + // sometimes multiple highlight queries match - this deduplicates them + if Some(node_range.clone()) == last_added_range { + continue; + } + + let identifier_text = + &range_text[node_range.start - range.start..node_range.end - range.start]; + references.push(Reference { + identifier: Identifier { + name: identifier_text.into(), + language_id: mat.language.id(), + }, + range: node_range.clone(), + region: reference_region, + }); + last_added_range = Some(node_range); + } + } + } + + matches.advance(); + } + references +} diff --git a/crates/edit_prediction_context/src/tree_sitter_index.rs b/crates/edit_prediction_context/src/tree_sitter_index.rs new file mode 100644 index 0000000000000000000000000000000000000000..4dc00941fe1b8a7a095fffd5605b040001c02eb7 --- /dev/null +++ b/crates/edit_prediction_context/src/tree_sitter_index.rs @@ -0,0 +1,833 @@ +use collections::{HashMap, HashSet}; +use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity}; +use language::{Buffer, BufferEvent, BufferSnapshot}; +use project::buffer_store::{BufferStore, BufferStoreEvent}; +use project::worktree_store::{WorktreeStore, WorktreeStoreEvent}; +use project::{PathChange, Project, ProjectEntryId, ProjectPath}; +use slotmap::SlotMap; +use std::ops::Range; +use std::sync::Arc; +use text::Anchor; +use util::{ResultExt as _, debug_panic, some_or_debug_panic}; + +use crate::outline::{Identifier, OutlineDeclaration, declarations_in_buffer}; + +// TODO: +// +// * Skip for remote projects + +// Potential future improvements: +// +// * Send multiple selected excerpt ranges. Challenge is that excerpt ranges influence which +// references are present and their scores. + +// Potential future optimizations: +// +// * Cache of buffers for files +// +// * Parse files directly instead of loading into a Rope. Make SyntaxMap generic to handle embedded +// languages? Will also need to find line boundaries, but that can be done by scanning characters in +// the flat representation. +// +// * Use something similar to slotmap without key versions. +// +// * Concurrent slotmap +// +// * Use queue for parsing + +slotmap::new_key_type! { + pub struct DeclarationId; +} + +pub struct TreeSitterIndex { + declarations: SlotMap, + identifiers: HashMap>, + files: HashMap, + buffers: HashMap, BufferState>, + project: WeakEntity, +} + +#[derive(Debug, Default)] +struct FileState { + declarations: Vec, + task: Option>, +} + +#[derive(Default)] +struct BufferState { + declarations: Vec, + task: Option>, +} + +#[derive(Debug, Clone)] +pub enum Declaration { + File { + project_entry_id: ProjectEntryId, + declaration: FileDeclaration, + }, + Buffer { + buffer: WeakEntity, + declaration: BufferDeclaration, + }, +} + +impl Declaration { + fn identifier(&self) -> &Identifier { + match self { + Declaration::File { declaration, .. } => &declaration.identifier, + Declaration::Buffer { declaration, .. } => &declaration.identifier, + } + } +} + +#[derive(Debug, Clone)] +pub struct FileDeclaration { + pub parent: Option, + pub identifier: Identifier, + pub item_range: Range, + pub signature_range: Range, + pub signature_text: Arc, +} + +#[derive(Debug, Clone)] +pub struct BufferDeclaration { + pub parent: Option, + pub identifier: Identifier, + pub item_range: Range, + pub signature_range: Range, +} + +impl TreeSitterIndex { + pub fn new(project: &Entity, cx: &mut Context) -> Self { + let mut this = Self { + declarations: SlotMap::with_key(), + identifiers: HashMap::default(), + project: project.downgrade(), + files: HashMap::default(), + buffers: HashMap::default(), + }; + + let worktree_store = project.read(cx).worktree_store(); + cx.subscribe(&worktree_store, Self::handle_worktree_store_event) + .detach(); + + for worktree in worktree_store + .read(cx) + .worktrees() + .map(|w| w.read(cx).snapshot()) + .collect::>() + { + for entry in worktree.files(false, 0) { + this.update_file( + entry.id, + ProjectPath { + worktree_id: worktree.id(), + path: entry.path.clone(), + }, + cx, + ); + } + } + + let buffer_store = project.read(cx).buffer_store().clone(); + for buffer in buffer_store.read(cx).buffers().collect::>() { + this.register_buffer(&buffer, cx); + } + cx.subscribe(&buffer_store, Self::handle_buffer_store_event) + .detach(); + + this + } + + pub fn declaration(&self, id: DeclarationId) -> Option<&Declaration> { + self.declarations.get(id) + } + + pub fn declarations_for_identifier( + &self, + identifier: Identifier, + cx: &App, + ) -> Vec { + // make sure to not have a large stack allocation + assert!(N < 32); + + let Some(declaration_ids) = self.identifiers.get(&identifier) else { + return vec![]; + }; + + let mut result = Vec::with_capacity(N); + let mut included_buffer_entry_ids = arrayvec::ArrayVec::<_, N>::new(); + let mut file_declarations = Vec::new(); + + for declaration_id in declaration_ids { + let declaration = self.declarations.get(*declaration_id); + let Some(declaration) = some_or_debug_panic(declaration) else { + continue; + }; + match declaration { + Declaration::Buffer { buffer, .. } => { + if let Ok(Some(entry_id)) = buffer.read_with(cx, |buffer, cx| { + project::File::from_dyn(buffer.file()).and_then(|f| f.project_entry_id(cx)) + }) { + included_buffer_entry_ids.push(entry_id); + result.push(declaration.clone()); + if result.len() == N { + return result; + } + } + } + Declaration::File { + project_entry_id, .. + } => { + if !included_buffer_entry_ids.contains(project_entry_id) { + file_declarations.push(declaration.clone()); + } + } + } + } + + for declaration in file_declarations { + match declaration { + Declaration::File { + project_entry_id, .. + } => { + if !included_buffer_entry_ids.contains(&project_entry_id) { + result.push(declaration); + + if result.len() == N { + return result; + } + } + } + Declaration::Buffer { .. } => {} + } + } + + result + } + + fn handle_worktree_store_event( + &mut self, + _worktree_store: Entity, + event: &WorktreeStoreEvent, + cx: &mut Context, + ) { + use WorktreeStoreEvent::*; + match event { + WorktreeUpdatedEntries(worktree_id, updated_entries_set) => { + for (path, entry_id, path_change) in updated_entries_set.iter() { + if let PathChange::Removed = path_change { + self.files.remove(entry_id); + } else { + let project_path = ProjectPath { + worktree_id: *worktree_id, + path: path.clone(), + }; + self.update_file(*entry_id, project_path, cx); + } + } + } + WorktreeDeletedEntry(_worktree_id, project_entry_id) => { + // TODO: Is this needed? + self.files.remove(project_entry_id); + } + _ => {} + } + } + + fn handle_buffer_store_event( + &mut self, + _buffer_store: Entity, + event: &BufferStoreEvent, + cx: &mut Context, + ) { + use BufferStoreEvent::*; + match event { + BufferAdded(buffer) => self.register_buffer(buffer, cx), + BufferOpened { .. } + | BufferChangedFilePath { .. } + | BufferDropped { .. } + | SharedBufferClosed { .. } => {} + } + } + + fn register_buffer(&mut self, buffer: &Entity, cx: &mut Context) { + self.buffers + .insert(buffer.downgrade(), BufferState::default()); + let weak_buf = buffer.downgrade(); + cx.observe_release(buffer, move |this, _buffer, _cx| { + this.buffers.remove(&weak_buf); + }) + .detach(); + cx.subscribe(buffer, Self::handle_buffer_event).detach(); + self.update_buffer(buffer.clone(), cx); + } + + fn handle_buffer_event( + &mut self, + buffer: Entity, + event: &BufferEvent, + cx: &mut Context, + ) { + match event { + BufferEvent::Edited => self.update_buffer(buffer, cx), + _ => {} + } + } + + fn update_buffer(&mut self, buffer: Entity, cx: &Context) { + let mut parse_status = buffer.read(cx).parse_status(); + let snapshot_task = cx.spawn({ + let weak_buffer = buffer.downgrade(); + async move |_, cx| { + while *parse_status.borrow() != language::ParseStatus::Idle { + parse_status.changed().await?; + } + weak_buffer.read_with(cx, |buffer, _cx| buffer.snapshot()) + } + }); + + let parse_task = cx.background_spawn(async move { + let snapshot = snapshot_task.await?; + + anyhow::Ok( + declarations_in_buffer(&snapshot) + .into_iter() + .map(|item| { + ( + item.parent_index, + BufferDeclaration::from_outline(item, &snapshot), + ) + }) + .collect::>(), + ) + }); + + let task = cx.spawn({ + let weak_buffer = buffer.downgrade(); + async move |this, cx| { + let Ok(declarations) = parse_task.await else { + return; + }; + + this.update(cx, |this, _cx| { + let buffer_state = this + .buffers + .entry(weak_buffer.clone()) + .or_insert_with(Default::default); + + for old_declaration_id in &buffer_state.declarations { + let Some(declaration) = this.declarations.remove(*old_declaration_id) + else { + debug_panic!("declaration not found"); + continue; + }; + if let Some(identifier_declarations) = + this.identifiers.get_mut(declaration.identifier()) + { + identifier_declarations.remove(old_declaration_id); + } + } + + let mut new_ids = Vec::with_capacity(declarations.len()); + this.declarations.reserve(declarations.len()); + for (parent_index, mut declaration) in declarations { + declaration.parent = parent_index + .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied())); + + let identifier = declaration.identifier.clone(); + let declaration_id = this.declarations.insert(Declaration::Buffer { + buffer: weak_buffer.clone(), + declaration, + }); + new_ids.push(declaration_id); + + this.identifiers + .entry(identifier) + .or_default() + .insert(declaration_id); + } + + buffer_state.declarations = new_ids; + }) + .ok(); + } + }); + + self.buffers + .entry(buffer.downgrade()) + .or_insert_with(Default::default) + .task = Some(task); + } + + fn update_file( + &mut self, + entry_id: ProjectEntryId, + project_path: ProjectPath, + cx: &mut Context, + ) { + let Some(project) = self.project.upgrade() else { + return; + }; + let project = project.read(cx); + let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx) else { + return; + }; + let language_registry = project.languages().clone(); + + let snapshot_task = worktree.update(cx, |worktree, cx| { + let load_task = worktree.load_file(&project_path.path, cx); + cx.spawn(async move |_this, cx| { + let loaded_file = load_task.await?; + let language = language_registry + .language_for_file_path(&project_path.path) + .await + .log_err(); + + let buffer = cx.new(|cx| { + let mut buffer = Buffer::local(loaded_file.text, cx); + buffer.set_language(language, cx); + buffer + })?; + + let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?; + while *parse_status.borrow() != language::ParseStatus::Idle { + parse_status.changed().await?; + } + + buffer.read_with(cx, |buffer, _cx| buffer.snapshot()) + }) + }); + + let parse_task = cx.background_spawn(async move { + let snapshot = snapshot_task.await?; + let declarations = declarations_in_buffer(&snapshot) + .into_iter() + .map(|item| { + ( + item.parent_index, + FileDeclaration::from_outline(item, &snapshot), + ) + }) + .collect::>(); + anyhow::Ok(declarations) + }); + + let task = cx.spawn({ + async move |this, cx| { + // TODO: how to handle errors? + let Ok(declarations) = parse_task.await else { + return; + }; + this.update(cx, |this, _cx| { + let file_state = this.files.entry(entry_id).or_insert_with(Default::default); + + for old_declaration_id in &file_state.declarations { + let Some(declaration) = this.declarations.remove(*old_declaration_id) + else { + debug_panic!("declaration not found"); + continue; + }; + if let Some(identifier_declarations) = + this.identifiers.get_mut(declaration.identifier()) + { + identifier_declarations.remove(old_declaration_id); + } + } + + let mut new_ids = Vec::with_capacity(declarations.len()); + this.declarations.reserve(declarations.len()); + + for (parent_index, mut declaration) in declarations { + declaration.parent = parent_index + .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied())); + + let identifier = declaration.identifier.clone(); + let declaration_id = this.declarations.insert(Declaration::File { + project_entry_id: entry_id, + declaration, + }); + new_ids.push(declaration_id); + + this.identifiers + .entry(identifier) + .or_default() + .insert(declaration_id); + } + + file_state.declarations = new_ids; + }) + .ok(); + } + }); + + self.files + .entry(entry_id) + .or_insert_with(Default::default) + .task = Some(task); + } +} + +impl BufferDeclaration { + pub fn from_outline(declaration: OutlineDeclaration, snapshot: &BufferSnapshot) -> Self { + // use of anchor_before is a guess that the proper behavior is to expand to include + // insertions immediately before the declaration, but not for insertions immediately after + Self { + parent: None, + identifier: declaration.identifier, + item_range: snapshot.anchor_before(declaration.item_range.start) + ..snapshot.anchor_before(declaration.item_range.end), + signature_range: snapshot.anchor_before(declaration.signature_range.start) + ..snapshot.anchor_before(declaration.signature_range.end), + } + } +} + +impl FileDeclaration { + pub fn from_outline( + declaration: OutlineDeclaration, + snapshot: &BufferSnapshot, + ) -> FileDeclaration { + FileDeclaration { + parent: None, + identifier: declaration.identifier, + item_range: declaration.item_range, + signature_text: snapshot + .text_for_range(declaration.signature_range.clone()) + .collect::() + .into(), + signature_range: declaration.signature_range, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{path::Path, sync::Arc}; + + use futures::channel::oneshot; + use gpui::TestAppContext; + use indoc::indoc; + use language::{Language, LanguageConfig, LanguageId, LanguageMatcher, tree_sitter_rust}; + use project::{FakeFs, Project, ProjectItem}; + use serde_json::json; + use settings::SettingsStore; + use text::OffsetRangeExt as _; + use util::path; + + use crate::tree_sitter_index::TreeSitterIndex; + + #[gpui::test] + async fn test_unopen_indexed_files(cx: &mut TestAppContext) { + let (project, index, rust_lang_id) = init_test(cx).await; + let main = Identifier { + name: "main".into(), + language_id: rust_lang_id, + }; + + index.read_with(cx, |index, cx| { + let decls = index.declarations_for_identifier::<8>(main.clone(), cx); + assert_eq!(decls.len(), 2); + + let decl = expect_file_decl("c.rs", &decls[0], &project, cx); + assert_eq!(decl.identifier, main.clone()); + assert_eq!(decl.item_range, 32..279); + + let decl = expect_file_decl("a.rs", &decls[1], &project, cx); + assert_eq!(decl.identifier, main); + assert_eq!(decl.item_range, 0..97); + }); + } + + #[gpui::test] + async fn test_parents_in_file(cx: &mut TestAppContext) { + let (project, index, rust_lang_id) = init_test(cx).await; + let test_process_data = Identifier { + name: "test_process_data".into(), + language_id: rust_lang_id, + }; + + index.read_with(cx, |index, cx| { + let decls = index.declarations_for_identifier::<8>(test_process_data.clone(), cx); + assert_eq!(decls.len(), 1); + + let decl = expect_file_decl("c.rs", &decls[0], &project, cx); + assert_eq!(decl.identifier, test_process_data); + + let parent_id = decl.parent.unwrap(); + let parent = index.declaration(parent_id).unwrap(); + let parent_decl = expect_file_decl("c.rs", &parent, &project, cx); + assert_eq!( + parent_decl.identifier, + Identifier { + name: "tests".into(), + language_id: rust_lang_id + } + ); + assert_eq!(parent_decl.parent, None); + }); + } + + #[gpui::test] + async fn test_parents_in_buffer(cx: &mut TestAppContext) { + let (project, index, rust_lang_id) = init_test(cx).await; + let test_process_data = Identifier { + name: "test_process_data".into(), + language_id: rust_lang_id, + }; + + let buffer = project + .update(cx, |project, cx| { + let project_path = project.find_project_path("c.rs", cx).unwrap(); + project.open_buffer(project_path, cx) + }) + .await + .unwrap(); + + cx.run_until_parked(); + + index.read_with(cx, |index, cx| { + let decls = index.declarations_for_identifier::<8>(test_process_data.clone(), cx); + assert_eq!(decls.len(), 1); + + let decl = expect_buffer_decl("c.rs", &decls[0], cx); + assert_eq!(decl.identifier, test_process_data); + + let parent_id = decl.parent.unwrap(); + let parent = index.declaration(parent_id).unwrap(); + let parent_decl = expect_buffer_decl("c.rs", &parent, cx); + assert_eq!( + parent_decl.identifier, + Identifier { + name: "tests".into(), + language_id: rust_lang_id + } + ); + assert_eq!(parent_decl.parent, None); + }); + + drop(buffer); + } + + #[gpui::test] + async fn test_declarations_limt(cx: &mut TestAppContext) { + let (_, index, rust_lang_id) = init_test(cx).await; + + index.read_with(cx, |index, cx| { + let decls = index.declarations_for_identifier::<1>( + Identifier { + name: "main".into(), + language_id: rust_lang_id, + }, + cx, + ); + assert_eq!(decls.len(), 1); + }); + } + + #[gpui::test] + async fn test_buffer_shadow(cx: &mut TestAppContext) { + let (project, index, rust_lang_id) = init_test(cx).await; + + let main = Identifier { + name: "main".into(), + language_id: rust_lang_id, + }; + + let buffer = project + .update(cx, |project, cx| { + let project_path = project.find_project_path("c.rs", cx).unwrap(); + project.open_buffer(project_path, cx) + }) + .await + .unwrap(); + + cx.run_until_parked(); + + index.read_with(cx, |index, cx| { + let decls = index.declarations_for_identifier::<8>(main.clone(), cx); + assert_eq!(decls.len(), 2); + let decl = expect_buffer_decl("c.rs", &decls[0], cx); + assert_eq!(decl.identifier, main); + assert_eq!(decl.item_range.to_offset(&buffer.read(cx)), 32..279); + + expect_file_decl("a.rs", &decls[1], &project, cx); + }); + + // Drop the buffer and wait for release + let (release_tx, release_rx) = oneshot::channel(); + cx.update(|cx| { + cx.observe_release(&buffer, |_, _| { + release_tx.send(()).ok(); + }) + .detach(); + }); + drop(buffer); + cx.run_until_parked(); + release_rx.await.ok(); + cx.run_until_parked(); + + index.read_with(cx, |index, cx| { + let decls = index.declarations_for_identifier::<8>(main, cx); + assert_eq!(decls.len(), 2); + expect_file_decl("c.rs", &decls[0], &project, cx); + expect_file_decl("a.rs", &decls[1], &project, cx); + }); + } + + fn expect_buffer_decl<'a>( + path: &str, + declaration: &'a Declaration, + cx: &App, + ) -> &'a BufferDeclaration { + if let Declaration::Buffer { + declaration, + buffer, + } = declaration + { + assert_eq!( + buffer + .upgrade() + .unwrap() + .read(cx) + .project_path(cx) + .unwrap() + .path + .as_ref(), + Path::new(path), + ); + declaration + } else { + panic!("Expected a buffer declaration, found {:?}", declaration); + } + } + + fn expect_file_decl<'a>( + path: &str, + declaration: &'a Declaration, + project: &Entity, + cx: &App, + ) -> &'a FileDeclaration { + if let Declaration::File { + declaration, + project_entry_id: file, + } = declaration + { + assert_eq!( + project + .read(cx) + .path_for_entry(*file, cx) + .unwrap() + .path + .as_ref(), + Path::new(path), + ); + declaration + } else { + panic!("Expected a file declaration, found {:?}", declaration); + } + } + + async fn init_test( + cx: &mut TestAppContext, + ) -> (Entity, Entity, LanguageId) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + language::init(cx); + Project::init_settings(cx); + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + json!({ + "a.rs": indoc! {r#" + fn main() { + let x = 1; + let y = 2; + let z = add(x, y); + println!("Result: {}", z); + } + + fn add(a: i32, b: i32) -> i32 { + a + b + } + "#}, + "b.rs": indoc! {" + pub struct Config { + pub name: String, + pub value: i32, + } + + impl Config { + pub fn new(name: String, value: i32) -> Self { + Config { name, value } + } + } + "}, + "c.rs": indoc! {r#" + use std::collections::HashMap; + + fn main() { + let args: Vec = std::env::args().collect(); + let data: Vec = args[1..] + .iter() + .filter_map(|s| s.parse().ok()) + .collect(); + let result = process_data(data); + println!("{:?}", result); + } + + fn process_data(data: Vec) -> HashMap { + let mut counts = HashMap::new(); + for value in data { + *counts.entry(value).or_insert(0) += 1; + } + counts + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_process_data() { + let data = vec![1, 2, 2, 3]; + let result = process_data(data); + assert_eq!(result.get(&2), Some(&2)); + } + } + "#} + }), + ) + .await; + let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + let lang = rust_lang(); + let lang_id = lang.id(); + language_registry.add(Arc::new(lang)); + + let index = cx.new(|cx| TreeSitterIndex::new(&project, cx)); + cx.run_until_parked(); + + (project, index, lang_id) + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) + .with_outline_query(include_str!("../../languages/src/rust/outline.scm")) + .unwrap() + } +} diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 44f819c135298dc991ad6036ad9948b5eaf609a4..ac1bdf85cb478064db42b3dccde8e44adee72fdd 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -115,7 +115,7 @@ seahash = "4.1" semantic_version.workspace = true serde.workspace = true serde_json.workspace = true -slotmap = "1.0.6" +slotmap.workspace = true smallvec.workspace = true smol.workspace = true stacksafe.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 77270807644830a233cbd6f3c47e4912ff47f543..c94153ba00a29b40544b3c685ad4cfb1add3db1b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -145,7 +145,7 @@ struct BufferBranchState { /// state of a buffer. pub struct BufferSnapshot { pub text: text::BufferSnapshot, - pub(crate) syntax: SyntaxSnapshot, + pub syntax: SyntaxSnapshot, file: Option>, diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>, remote_selections: TreeMap, @@ -660,7 +660,10 @@ impl HighlightedTextBuilder { syntax_snapshot: &'a SyntaxSnapshot, ) -> BufferChunks<'a> { let captures = syntax_snapshot.captures(range.clone(), snapshot, |grammar| { - grammar.highlights_query.as_ref() + grammar + .highlights_config + .as_ref() + .map(|config| &config.query) }); let highlight_maps = captures @@ -3246,7 +3249,10 @@ impl BufferSnapshot { fn get_highlights(&self, range: Range) -> (SyntaxMapCaptures<'_>, Vec) { let captures = self.syntax.captures(range, &self.text, |grammar| { - grammar.highlights_query.as_ref() + grammar + .highlights_config + .as_ref() + .map(|config| &config.query) }); let highlight_maps = captures .grammars() diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 77e8ee0232819a830e48d1d12a778ef19026d7b6..3c951e50ff231a72e284da743bb3e5d409eb9c5e 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -81,7 +81,9 @@ pub use language_registry::{ }; pub use lsp::{LanguageServerId, LanguageServerName}; pub use outline::*; -pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer, ToTreeSitterPoint, TreeSitterOptions}; +pub use syntax_map::{ + OwnedSyntaxLayer, SyntaxLayer, SyntaxMapMatches, ToTreeSitterPoint, TreeSitterOptions, +}; pub use text::{AnchorRangeExt, LineEnding}; pub use tree_sitter::{Node, Parser, Tree, TreeCursor}; @@ -1154,7 +1156,7 @@ pub struct Grammar { id: GrammarId, pub ts_language: tree_sitter::Language, pub(crate) error_query: Option, - pub(crate) highlights_query: Option, + pub highlights_config: Option, pub(crate) brackets_config: Option, pub(crate) redactions_config: Option, pub(crate) runnable_config: Option, @@ -1168,6 +1170,11 @@ pub struct Grammar { pub(crate) highlight_map: Mutex, } +pub struct HighlightsConfig { + pub query: Query, + pub identifier_capture_indices: Vec, +} + struct IndentConfig { query: Query, indent_capture_ix: u32, @@ -1332,7 +1339,7 @@ impl Language { grammar: ts_language.map(|ts_language| { Arc::new(Grammar { id: GrammarId::new(), - highlights_query: None, + highlights_config: None, brackets_config: None, outline_config: None, text_object_config: None, @@ -1430,7 +1437,29 @@ impl Language { pub fn with_highlights_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut()?; - grammar.highlights_query = Some(Query::new(&grammar.ts_language, source)?); + let query = Query::new(&grammar.ts_language, source)?; + + let mut identifier_capture_indices = Vec::new(); + for name in [ + "variable", + "constant", + "constructor", + "function", + "function.method", + "function.method.call", + "function.special", + "property", + "type", + "type.interface", + ] { + identifier_capture_indices.extend(query.capture_index_for_name(name)); + } + + grammar.highlights_config = Some(HighlightsConfig { + query, + identifier_capture_indices, + }); + Ok(self) } @@ -1856,7 +1885,10 @@ impl Language { let tree = grammar.parse_text(text, None); let captures = SyntaxSnapshot::single_tree_captures(range.clone(), text, &tree, self, |grammar| { - grammar.highlights_query.as_ref() + grammar + .highlights_config + .as_ref() + .map(|config| &config.query) }); let highlight_maps = vec![grammar.highlight_map()]; let mut offset = 0; @@ -1885,10 +1917,10 @@ impl Language { pub fn set_theme(&self, theme: &SyntaxTheme) { if let Some(grammar) = self.grammar.as_ref() - && let Some(highlights_query) = &grammar.highlights_query + && let Some(highlights_config) = &grammar.highlights_config { *grammar.highlight_map.lock() = - HighlightMap::new(highlights_query.capture_names(), theme); + HighlightMap::new(highlights_config.query.capture_names(), theme); } } @@ -2103,8 +2135,9 @@ impl Grammar { pub fn highlight_id_for_name(&self, name: &str) -> Option { let capture_id = self - .highlights_query + .highlights_config .as_ref()? + .query .capture_index_for_name(name)?; Some(self.highlight_map.lock().get(capture_id)) } diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index 622731b7814ce16bfcc026b6723e80d5ba4dda7a..6b19d651e241ad71229c6c7fc429883a44367304 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -1409,12 +1409,15 @@ fn assert_capture_ranges( ) { let mut actual_ranges = Vec::>::new(); let captures = syntax_map.captures(0..buffer.len(), buffer, |grammar| { - grammar.highlights_query.as_ref() + grammar + .highlights_config + .as_ref() + .map(|config| &config.query) }); let queries = captures .grammars() .iter() - .map(|grammar| grammar.highlights_query.as_ref().unwrap()) + .map(|grammar| &grammar.highlights_config.as_ref().unwrap().query) .collect::>(); for capture in captures { let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 90f5be1c92875ac0b9b2d3e7352ae858371b3686..ee18093784e4b0f2b78db9240d918685b6f01b6f 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1095,6 +1095,15 @@ impl From> for ConnectionResult { } } +#[track_caller] +pub fn some_or_debug_panic(option: Option) -> Option { + #[cfg(debug_assertions)] + if option.is_none() { + panic!("Unexpected None"); + } + option +} + #[cfg(test)] mod tests { use super::*; From 531f9ee2361b467d2379ff144fc13a6f48a3adde Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 10:11:51 +0200 Subject: [PATCH 072/721] Give most spawned threads names (#38302) Release Notes: - N/A --- crates/cli/src/main.rs | 97 ++++--- crates/crashes/src/crashes.rs | 21 +- crates/denoise/src/lib.rs | 9 +- crates/fs/src/mac_watcher.rs | 10 +- crates/gpui/src/platform/linux/dispatcher.rs | 88 +++--- .../gpui/src/platform/linux/x11/clipboard.rs | 18 +- crates/gpui/src/platform/windows/platform.rs | 45 ++-- .../src/livekit_client/playback.rs | 252 +++++++++--------- crates/sqlez/src/thread_safe_connection.rs | 13 +- crates/zed/src/zed/mac_only_instance.rs | 27 +- crates/zed/src/zed/windows_only_instance.rs | 56 ++-- 11 files changed, 345 insertions(+), 291 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index d4b4a350f61b5bd1249b33ff3925dd281e9d529c..d0d30f72d7aea7d7f6cf0355caf12b1f2a36eedb 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -339,59 +339,70 @@ fn main() -> Result<()> { "Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development" ); - let sender: JoinHandle> = thread::spawn({ - let exit_status = exit_status.clone(); - let user_data_dir_for_thread = user_data_dir.clone(); - move || { - let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; - let (tx, rx) = (handshake.requests, handshake.responses); - - #[cfg(target_os = "windows")] - let wsl = args.wsl; - #[cfg(not(target_os = "windows"))] - let wsl = None; - - tx.send(CliRequest::Open { - paths, - urls, - diff_paths, - wsl, - wait: args.wait, - open_new_workspace, - env, - user_data_dir: user_data_dir_for_thread, - })?; - - while let Ok(response) = rx.recv() { - match response { - CliResponse::Ping => {} - CliResponse::Stdout { message } => println!("{message}"), - CliResponse::Stderr { message } => eprintln!("{message}"), - CliResponse::Exit { status } => { - exit_status.lock().replace(status); - return Ok(()); + let sender: JoinHandle> = thread::Builder::new() + .name("CliReceiver".to_string()) + .spawn({ + let exit_status = exit_status.clone(); + let user_data_dir_for_thread = user_data_dir.clone(); + move || { + let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; + let (tx, rx) = (handshake.requests, handshake.responses); + + #[cfg(target_os = "windows")] + let wsl = args.wsl; + #[cfg(not(target_os = "windows"))] + let wsl = None; + + tx.send(CliRequest::Open { + paths, + urls, + diff_paths, + wsl, + wait: args.wait, + open_new_workspace, + env, + user_data_dir: user_data_dir_for_thread, + })?; + + while let Ok(response) = rx.recv() { + match response { + CliResponse::Ping => {} + CliResponse::Stdout { message } => println!("{message}"), + CliResponse::Stderr { message } => eprintln!("{message}"), + CliResponse::Exit { status } => { + exit_status.lock().replace(status); + return Ok(()); + } } } - } - Ok(()) - } - }); + Ok(()) + } + }) + .unwrap(); let stdin_pipe_handle: Option>> = stdin_tmp_file.map(|mut tmp_file| { - thread::spawn(move || { - let mut stdin = std::io::stdin().lock(); - if !io::IsTerminal::is_terminal(&stdin) { - io::copy(&mut stdin, &mut tmp_file)?; - } - Ok(()) - }) + thread::Builder::new() + .name("CliStdin".to_string()) + .spawn(move || { + let mut stdin = std::io::stdin().lock(); + if !io::IsTerminal::is_terminal(&stdin) { + io::copy(&mut stdin, &mut tmp_file)?; + } + Ok(()) + }) + .unwrap() }); let anonymous_fd_pipe_handles: Vec<_> = anonymous_fd_tmp_files .into_iter() - .map(|(mut file, mut tmp_file)| thread::spawn(move || io::copy(&mut file, &mut tmp_file))) + .map(|(mut file, mut tmp_file)| { + thread::Builder::new() + .name("CliAnonymousFd".to_string()) + .spawn(move || io::copy(&mut file, &mut tmp_file)) + .unwrap() + }) .collect(); if args.foreground { diff --git a/crates/crashes/src/crashes.rs b/crates/crashes/src/crashes.rs index 98db4bfc73f157994f3f7286c0764cfb0778e4a4..8312638e2a811767ee245f53c356eca15ef852f1 100644 --- a/crates/crashes/src/crashes.rs +++ b/crates/crashes/src/crashes.rs @@ -321,16 +321,19 @@ pub fn crash_server(socket: &Path) { let shutdown = Arc::new(AtomicBool::new(false)); let has_connection = Arc::new(AtomicBool::new(false)); - std::thread::spawn({ - let shutdown = shutdown.clone(); - let has_connection = has_connection.clone(); - move || { - std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT); - if !has_connection.load(Ordering::SeqCst) { - shutdown.store(true, Ordering::SeqCst); + thread::Builder::new() + .name("CrashServerTimeout".to_owned()) + .spawn({ + let shutdown = shutdown.clone(); + let has_connection = has_connection.clone(); + move || { + std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT); + if !has_connection.load(Ordering::SeqCst) { + shutdown.store(true, Ordering::SeqCst); + } } - } - }); + }) + .unwrap(); server .run( diff --git a/crates/denoise/src/lib.rs b/crates/denoise/src/lib.rs index 3673b28c1c837ee265199b9be2d9aea4a422decf..1422c81a4b915d571d35585447165c04d3695b73 100644 --- a/crates/denoise/src/lib.rs +++ b/crates/denoise/src/lib.rs @@ -79,9 +79,12 @@ impl Denoiser { let (input_tx, input_rx) = mpsc::channel(); let (denoised_tx, denoised_rx) = mpsc::channel(); - thread::spawn(move || { - run_neural_denoiser(denoised_tx, input_rx); - }); + thread::Builder::new() + .name("NeuralDenoiser".to_owned()) + .spawn(move || { + run_neural_denoiser(denoised_tx, input_rx); + }) + .unwrap(); Ok(Self { inner: source, diff --git a/crates/fs/src/mac_watcher.rs b/crates/fs/src/mac_watcher.rs index 7bd176639f1dccef2da4c4ae8dcb317d0be602cb..698014de9716f6505ccd23cd344a62815d9ba0f7 100644 --- a/crates/fs/src/mac_watcher.rs +++ b/crates/fs/src/mac_watcher.rs @@ -6,6 +6,7 @@ use parking_lot::Mutex; use std::{ path::{Path, PathBuf}, sync::Weak, + thread, time::Duration, }; @@ -48,9 +49,12 @@ impl Watcher for MacWatcher { let (stream, handle) = EventStream::new(&[path], self.latency); let tx = self.events_tx.clone(); - std::thread::spawn(move || { - stream.run(move |events| smol::block_on(tx.send(events)).is_ok()); - }); + thread::Builder::new() + .name("MacWatcher".to_owned()) + .spawn(move || { + stream.run(move |events| smol::block_on(tx.send(events)).is_ok()); + }) + .unwrap(); handles.insert(path.into(), handle); Ok(()) diff --git a/crates/gpui/src/platform/linux/dispatcher.rs b/crates/gpui/src/platform/linux/dispatcher.rs index 3d32dbd2fdece5259f48e52550f6983b6a8c5b1d..2f6cd83756054bdbca2c764b046b0c37f51d3515 100644 --- a/crates/gpui/src/platform/linux/dispatcher.rs +++ b/crates/gpui/src/platform/linux/dispatcher.rs @@ -37,51 +37,57 @@ impl LinuxDispatcher { let mut background_threads = (0..thread_count) .map(|i| { let receiver = background_receiver.clone(); - std::thread::spawn(move || { - for runnable in receiver { - let start = Instant::now(); - - runnable.run(); - - log::trace!( - "background thread {}: ran runnable. took: {:?}", - i, - start.elapsed() - ); - } - }) + std::thread::Builder::new() + .name(format!("Worker-{i}")) + .spawn(move || { + for runnable in receiver { + let start = Instant::now(); + + runnable.run(); + + log::trace!( + "background thread {}: ran runnable. took: {:?}", + i, + start.elapsed() + ); + } + }) + .unwrap() }) .collect::>(); let (timer_sender, timer_channel) = calloop::channel::channel::(); - let timer_thread = std::thread::spawn(|| { - let mut event_loop: EventLoop<()> = - EventLoop::try_new().expect("Failed to initialize timer loop!"); - - let handle = event_loop.handle(); - let timer_handle = event_loop.handle(); - handle - .insert_source(timer_channel, move |e, _, _| { - if let channel::Event::Msg(timer) = e { - // This has to be in an option to satisfy the borrow checker. The callback below should only be scheduled once. - let mut runnable = Some(timer.runnable); - timer_handle - .insert_source( - calloop::timer::Timer::from_duration(timer.duration), - move |_, _, _| { - if let Some(runnable) = runnable.take() { - runnable.run(); - } - TimeoutAction::Drop - }, - ) - .expect("Failed to start timer"); - } - }) - .expect("Failed to start timer thread"); - - event_loop.run(None, &mut (), |_| {}).log_err(); - }); + let timer_thread = std::thread::Builder::new() + .name("Timer".to_owned()) + .spawn(|| { + let mut event_loop: EventLoop<()> = + EventLoop::try_new().expect("Failed to initialize timer loop!"); + + let handle = event_loop.handle(); + let timer_handle = event_loop.handle(); + handle + .insert_source(timer_channel, move |e, _, _| { + if let channel::Event::Msg(timer) = e { + // This has to be in an option to satisfy the borrow checker. The callback below should only be scheduled once. + let mut runnable = Some(timer.runnable); + timer_handle + .insert_source( + calloop::timer::Timer::from_duration(timer.duration), + move |_, _, _| { + if let Some(runnable) = runnable.take() { + runnable.run(); + } + TimeoutAction::Drop + }, + ) + .expect("Failed to start timer"); + } + }) + .expect("Failed to start timer thread"); + + event_loop.run(None, &mut (), |_| {}).log_err(); + }) + .unwrap(); background_threads.push(timer_thread); diff --git a/crates/gpui/src/platform/linux/x11/clipboard.rs b/crates/gpui/src/platform/linux/x11/clipboard.rs index a6f96d38c4254da5a2f92261700126962c16e91c..65ad16e82bf103c4ef08e79c692196d3fae58777 100644 --- a/crates/gpui/src/platform/linux/x11/clipboard.rs +++ b/crates/gpui/src/platform/linux/x11/clipboard.rs @@ -957,15 +957,17 @@ impl Clipboard { } // At this point we know that the clipboard does not exist. let ctx = Arc::new(Inner::new()?); - let join_handle; - { - let ctx = Arc::clone(&ctx); - join_handle = std::thread::spawn(move || { - if let Err(error) = serve_requests(ctx) { - log::error!("Worker thread errored with: {}", error); + let join_handle = std::thread::Builder::new() + .name("Clipboard".to_owned()) + .spawn({ + let ctx = Arc::clone(&ctx); + move || { + if let Err(error) = serve_requests(ctx) { + log::error!("Worker thread errored with: {}", error); + } } - }); - } + }) + .unwrap(); *global_cb = Some(GlobalClipboard { inner: Arc::clone(&ctx), server_handle: join_handle, diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index b1e2c123e881cecadcdcb6582056c0e5fba64c14..2eb1862f36a26592e18dc2e44875e08319361cc8 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -243,29 +243,32 @@ impl WindowsPlatform { let validation_number = self.inner.validation_number; let all_windows = Arc::downgrade(&self.raw_window_handles); let text_system = Arc::downgrade(&self.text_system); - std::thread::spawn(move || { - let vsync_provider = VSyncProvider::new(); - loop { - vsync_provider.wait_for_vsync(); - if check_device_lost(&directx_device.device) { - handle_gpu_device_lost( - &mut directx_device, - platform_window.as_raw(), - validation_number, - &all_windows, - &text_system, - ); - } - let Some(all_windows) = all_windows.upgrade() else { - break; - }; - for hwnd in all_windows.read().iter() { - unsafe { - let _ = RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE); + std::thread::Builder::new() + .name("VSyncProvider".to_owned()) + .spawn(move || { + let vsync_provider = VSyncProvider::new(); + loop { + vsync_provider.wait_for_vsync(); + if check_device_lost(&directx_device.device) { + handle_gpu_device_lost( + &mut directx_device, + platform_window.as_raw(), + validation_number, + &all_windows, + &text_system, + ); + } + let Some(all_windows) = all_windows.upgrade() else { + break; + }; + for hwnd in all_windows.read().iter() { + unsafe { + let _ = RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE); + } } } - } - }); + }) + .unwrap(); } } diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index 7c866113103a883e7e7a2d9d3f5651d833d7e637..df8b5ea54fb1ce11bf871faa912757bbff1fd7f9 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -188,12 +188,15 @@ impl AudioStack { let voip_parts = audio::VoipParts::new(cx)?; // Audio needs to run real-time and should never be paused. That is why we are using a // normal std::thread and not a background task - thread::spawn(move || { - // microphone is non send on mac - let microphone = audio::Audio::open_microphone(voip_parts)?; - send_to_livekit(frame_tx, microphone); - Ok::<(), anyhow::Error>(()) - }); + thread::Builder::new() + .name("AudioCapture".to_string()) + .spawn(move || { + // microphone is non send on mac + let microphone = audio::Audio::open_microphone(voip_parts)?; + send_to_livekit(frame_tx, microphone); + Ok::<(), anyhow::Error>(()) + }) + .unwrap(); Task::ready(Ok(())) } else { self.executor.spawn(async move { @@ -229,57 +232,60 @@ impl AudioStack { let mut resampler = audio_resampler::AudioResampler::default(); let mut buf = Vec::new(); - thread::spawn(move || { - let output_stream = output_device.build_output_stream( - &output_config.config(), - { - move |mut data, _info| { - while data.len() > 0 { - if data.len() <= buf.len() { - let rest = buf.split_off(data.len()); - data.copy_from_slice(&buf); - buf = rest; - return; - } - if buf.len() > 0 { - let (prefix, suffix) = data.split_at_mut(buf.len()); - prefix.copy_from_slice(&buf); - data = suffix; - } + thread::Builder::new() + .name("AudioPlayback".to_owned()) + .spawn(move || { + let output_stream = output_device.build_output_stream( + &output_config.config(), + { + move |mut data, _info| { + while data.len() > 0 { + if data.len() <= buf.len() { + let rest = buf.split_off(data.len()); + data.copy_from_slice(&buf); + buf = rest; + return; + } + if buf.len() > 0 { + let (prefix, suffix) = data.split_at_mut(buf.len()); + prefix.copy_from_slice(&buf); + data = suffix; + } - let mut mixer = mixer.lock(); - let mixed = mixer.mix(output_config.channels() as usize); - let sampled = resampler.remix_and_resample( - mixed, - sample_rate / 100, - num_channels, - sample_rate, - output_config.channels() as u32, - output_config.sample_rate().0, - ); - buf = sampled.to_vec(); - apm.lock() - .process_reverse_stream( - &mut buf, - output_config.sample_rate().0 as i32, - output_config.channels() as i32, - ) - .ok(); + let mut mixer = mixer.lock(); + let mixed = mixer.mix(output_config.channels() as usize); + let sampled = resampler.remix_and_resample( + mixed, + sample_rate / 100, + num_channels, + sample_rate, + output_config.channels() as u32, + output_config.sample_rate().0, + ); + buf = sampled.to_vec(); + apm.lock() + .process_reverse_stream( + &mut buf, + output_config.sample_rate().0 as i32, + output_config.channels() as i32, + ) + .ok(); + } } - } - }, - |error| log::error!("error playing audio track: {:?}", error), - Some(Duration::from_millis(100)), - ); + }, + |error| log::error!("error playing audio track: {:?}", error), + Some(Duration::from_millis(100)), + ); - let Some(output_stream) = output_stream.log_err() else { - return; - }; + let Some(output_stream) = output_stream.log_err() else { + return; + }; - output_stream.play().log_err(); - // Block forever to keep the output stream alive - end_on_drop_rx.recv().ok(); - }); + output_stream.play().log_err(); + // Block forever to keep the output stream alive + end_on_drop_rx.recv().ok(); + }) + .unwrap(); device_change_listener.next().await; drop(end_on_drop_tx) @@ -300,77 +306,81 @@ impl AudioStack { let frame_tx = frame_tx.clone(); let mut resampler = audio_resampler::AudioResampler::default(); - thread::spawn(move || { - maybe!({ - if let Some(name) = device.name().ok() { - log::info!("Using microphone: {}", name) - } else { - log::info!("Using microphone: "); - } - - let ten_ms_buffer_size = - (config.channels() as u32 * config.sample_rate().0 / 100) as usize; - let mut buf: Vec = Vec::with_capacity(ten_ms_buffer_size); - - let stream = device - .build_input_stream_raw( - &config.config(), - config.sample_format(), - move |data, _: &_| { - let data = - crate::get_sample_data(config.sample_format(), data).log_err(); - let Some(data) = data else { - return; - }; - let mut data = data.as_slice(); + thread::Builder::new() + .name("AudioCapture".to_owned()) + .spawn(move || { + maybe!({ + if let Some(name) = device.name().ok() { + log::info!("Using microphone: {}", name) + } else { + log::info!("Using microphone: "); + } - while data.len() > 0 { - let remainder = (buf.capacity() - buf.len()).min(data.len()); - buf.extend_from_slice(&data[..remainder]); - data = &data[remainder..]; - - if buf.capacity() == buf.len() { - let mut sampled = resampler - .remix_and_resample( - buf.as_slice(), - config.sample_rate().0 / 100, - config.channels() as u32, - config.sample_rate().0, - num_channels, - sample_rate, - ) - .to_owned(); - apm.lock() - .process_stream( - &mut sampled, - sample_rate as i32, - num_channels as i32, - ) - .log_err(); - buf.clear(); - frame_tx - .unbounded_send(AudioFrame { - data: Cow::Owned(sampled), - sample_rate, - num_channels, - samples_per_channel: sample_rate / 100, - }) - .ok(); + let ten_ms_buffer_size = + (config.channels() as u32 * config.sample_rate().0 / 100) as usize; + let mut buf: Vec = Vec::with_capacity(ten_ms_buffer_size); + + let stream = device + .build_input_stream_raw( + &config.config(), + config.sample_format(), + move |data, _: &_| { + let data = crate::get_sample_data(config.sample_format(), data) + .log_err(); + let Some(data) = data else { + return; + }; + let mut data = data.as_slice(); + + while data.len() > 0 { + let remainder = + (buf.capacity() - buf.len()).min(data.len()); + buf.extend_from_slice(&data[..remainder]); + data = &data[remainder..]; + + if buf.capacity() == buf.len() { + let mut sampled = resampler + .remix_and_resample( + buf.as_slice(), + config.sample_rate().0 / 100, + config.channels() as u32, + config.sample_rate().0, + num_channels, + sample_rate, + ) + .to_owned(); + apm.lock() + .process_stream( + &mut sampled, + sample_rate as i32, + num_channels as i32, + ) + .log_err(); + buf.clear(); + frame_tx + .unbounded_send(AudioFrame { + data: Cow::Owned(sampled), + sample_rate, + num_channels, + samples_per_channel: sample_rate / 100, + }) + .ok(); + } } - } - }, - |err| log::error!("error capturing audio track: {:?}", err), - Some(Duration::from_millis(100)), - ) - .context("failed to build input stream")?; - - stream.play()?; - // Keep the thread alive and holding onto the `stream` - end_on_drop_rx.recv().ok(); - anyhow::Ok(Some(())) + }, + |err| log::error!("error capturing audio track: {:?}", err), + Some(Duration::from_millis(100)), + ) + .context("failed to build input stream")?; + + stream.play()?; + // Keep the thread alive and holding onto the `stream` + end_on_drop_rx.recv().ok(); + anyhow::Ok(Some(())) + }) + .log_err(); }) - .log_err(); - }); + .unwrap(); device_change_listener.next().await; drop(end_on_drop_tx) diff --git a/crates/sqlez/src/thread_safe_connection.rs b/crates/sqlez/src/thread_safe_connection.rs index 482905ac817bf94fcb64cb858b784c94283b686c..966f14a9c2f244780da7190aebac88e95c7ac068 100644 --- a/crates/sqlez/src/thread_safe_connection.rs +++ b/crates/sqlez/src/thread_safe_connection.rs @@ -249,11 +249,14 @@ pub fn background_thread_queue() -> WriteQueueConstructor { Box::new(|| { let (sender, receiver) = channel::(); - thread::spawn(move || { - while let Ok(write) = receiver.recv() { - write() - } - }); + thread::Builder::new() + .name("sqlezWorker".to_string()) + .spawn(move || { + while let Ok(write) = receiver.recv() { + write() + } + }) + .unwrap(); let sender = UnboundedSyncSender::new(sender); Box::new(move |queued_write| { diff --git a/crates/zed/src/zed/mac_only_instance.rs b/crates/zed/src/zed/mac_only_instance.rs index cb9641e9dfe55660e301faa46d47e1a4b8511466..b7898fae176d3a68f0664a6ed4dddc0a59b87cec 100644 --- a/crates/zed/src/zed/mac_only_instance.rs +++ b/crates/zed/src/zed/mac_only_instance.rs @@ -107,18 +107,21 @@ pub fn ensure_only_instance() -> IsOnlyInstance { } }; - thread::spawn(move || { - for stream in listener.incoming() { - let mut stream = match stream { - Ok(stream) => stream, - Err(_) => return, - }; - - _ = stream.set_nodelay(true); - _ = stream.set_read_timeout(Some(SEND_TIMEOUT)); - _ = stream.write_all(instance_handshake().as_bytes()); - } - }); + thread::Builder::new() + .name("EnsureSingleton".to_string()) + .spawn(move || { + for stream in listener.incoming() { + let mut stream = match stream { + Ok(stream) => stream, + Err(_) => return, + }; + + _ = stream.set_nodelay(true); + _ = stream.set_read_timeout(Some(SEND_TIMEOUT)); + _ = stream.write_all(instance_handshake().as_bytes()); + } + }) + .unwrap(); IsOnlyInstance::Yes } diff --git a/crates/zed/src/zed/windows_only_instance.rs b/crates/zed/src/zed/windows_only_instance.rs index 1dd51b5ffbd7c11cce0346142834581c022f512d..d377f06ede778b47dbac3257069d2b1c647935ae 100644 --- a/crates/zed/src/zed/windows_only_instance.rs +++ b/crates/zed/src/zed/windows_only_instance.rs @@ -42,14 +42,17 @@ pub fn handle_single_instance(opener: OpenListener, args: &Args) -> bool { let is_first_instance = is_first_instance(); if is_first_instance { // We are the first instance, listen for messages sent from other instances - std::thread::spawn(move || { - with_pipe(|url| { - opener.open(RawOpenRequest { - urls: vec![url], - ..Default::default() + std::thread::Builder::new() + .name("EnsureSingleton".to_owned()) + .spawn(move || { + with_pipe(|url| { + opener.open(RawOpenRequest { + urls: vec![url], + ..Default::default() + }) }) }) - }); + .unwrap(); } else if !args.foreground { // We are not the first instance, send args to the first instance send_args_to_instance(args).log_err(); @@ -161,28 +164,31 @@ fn send_args_to_instance(args: &Args) -> anyhow::Result<()> { }; let exit_status = Arc::new(Mutex::new(None)); - let sender: JoinHandle> = std::thread::spawn({ - let exit_status = exit_status.clone(); - move || { - let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; - let (tx, rx) = (handshake.requests, handshake.responses); - - tx.send(request)?; - - while let Ok(response) = rx.recv() { - match response { - CliResponse::Ping => {} - CliResponse::Stdout { message } => log::info!("{message}"), - CliResponse::Stderr { message } => log::error!("{message}"), - CliResponse::Exit { status } => { - exit_status.lock().replace(status); - return Ok(()); + let sender: JoinHandle> = std::thread::Builder::new() + .name("CliReceiver".to_owned()) + .spawn({ + let exit_status = exit_status.clone(); + move || { + let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; + let (tx, rx) = (handshake.requests, handshake.responses); + + tx.send(request)?; + + while let Ok(response) = rx.recv() { + match response { + CliResponse::Ping => {} + CliResponse::Stdout { message } => log::info!("{message}"), + CliResponse::Stderr { message } => log::error!("{message}"), + CliResponse::Exit { status } => { + exit_status.lock().replace(status); + return Ok(()); + } } } + Ok(()) } - Ok(()) - } - }); + }) + .unwrap(); write_message_to_instance_pipe(url.as_bytes())?; sender.join().unwrap()?; From a2de91827defe2b3a58cbb874706ceab42da36ac Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 10:39:24 +0200 Subject: [PATCH 073/721] agent_ui: Fix panic on editor changes in inline_assistant (#38303) Fixes ZED-13P Release Notes: - N/A --- crates/agent_ui/src/inline_assistant.rs | 31 ++++++++------------- crates/gpui/src/platform/mac/metal_atlas.rs | 7 +++-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 4ac88e6daa3d3623580e206c2759f27b218d1bac..79e092b709dd2778c89a79e1d6ce36802c853eb6 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -744,19 +744,14 @@ impl InlineAssistant { .update(cx, |editor, cx| { let scroll_top = editor.scroll_position(cx).y; let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.); - let prompt_row = editor + editor_assists.scroll_lock = editor .row_for_block(decorations.prompt_block_id, cx) - .unwrap() - .0 as f32; - - if (scroll_top..scroll_bottom).contains(&prompt_row) { - editor_assists.scroll_lock = Some(InlineAssistScrollLock { + .map(|row| row.0 as f32) + .filter(|prompt_row| (scroll_top..scroll_bottom).contains(&prompt_row)) + .map(|prompt_row| InlineAssistScrollLock { assist_id, distance_from_top: prompt_row - scroll_top, }); - } else { - editor_assists.scroll_lock = None; - } }) .ok(); } @@ -917,14 +912,12 @@ impl InlineAssistant { editor.update(cx, |editor, cx| { let scroll_position = editor.scroll_position(cx); - let target_scroll_top = editor - .row_for_block(decorations.prompt_block_id, cx) - .unwrap() - .0 as f32 + let target_scroll_top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32 - scroll_lock.distance_from_top; if target_scroll_top != scroll_position.y { editor.set_scroll_position(point(scroll_position.x, target_scroll_top), window, cx); } + Some(()) }); } @@ -968,14 +961,14 @@ impl InlineAssistant { if let Some(decorations) = assist.decorations.as_ref() { let distance_from_top = editor.update(cx, |editor, cx| { let scroll_top = editor.scroll_position(cx).y; - let prompt_row = editor - .row_for_block(decorations.prompt_block_id, cx) - .unwrap() - .0 as f32; - prompt_row - scroll_top + let prompt_row = + editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32; + Some(prompt_row - scroll_top) }); - if distance_from_top != scroll_lock.distance_from_top { + if distance_from_top.is_none_or(|distance_from_top| { + distance_from_top != scroll_lock.distance_from_top + }) { editor_assists.scroll_lock = None; } } diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 5d2d8e63e06a1ea6251c1fd2edf461eeeedec612..8282530c5efdc13ca95a1f04c0f6ef1a23c8366c 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -167,11 +167,14 @@ impl MetalAtlasState { if let Some(ix) = index { texture_list.textures[ix] = Some(atlas_texture); - texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap() + texture_list.textures.get_mut(ix) } else { texture_list.textures.push(Some(atlas_texture)); - texture_list.textures.last_mut().unwrap().as_mut().unwrap() + texture_list.textures.last_mut() } + .unwrap() + .as_mut() + .unwrap() } fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture { From d48d6a745409a8998998ed59c28493a1aa733ebb Mon Sep 17 00:00:00 2001 From: localcc Date: Wed, 17 Sep 2025 11:26:53 +0200 Subject: [PATCH 074/721] Fix empty nodes crash (#38259) The crash occured because we raced against the platform windowing backend to render a frame, and if we lost the race there would be no frame on a window that we return, which breaks most of gpui Release Notes: - N/A --- crates/gpui/src/app.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8b0b404d1dffbf8a27de1f29437ce9cc2ba63f0f..e6c3e3b8deea9b82514b5ac932c4f204fa081e14 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -958,6 +958,14 @@ impl App { cx.window_update_stack.pop(); window.root.replace(root_view.into()); window.defer(cx, |window: &mut Window, cx| window.appearance_changed(cx)); + + // allow a window to draw at least once before returning + // this didn't cause any issues on non windows platforms as it seems we always won the race to on_request_frame + // on windows we quite frequently lose the race and return a window that has never rendered, which leads to a crash + // where DispatchTree::root_node_id asserts on empty nodes + let clear = window.draw(cx); + clear.clear(); + cx.window_handles.insert(id, window.handle); cx.windows.get_mut(id).unwrap().replace(window); Ok(handle) From c5ac1e6218d74182257d5e0e5c1f20154e77d46f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 11:36:20 +0200 Subject: [PATCH 075/721] editor: Fix `select_larget_syntax_node` overflowing in multibuffers (#38308) Fixes ZED-18Z Release Notes: - N/A --- crates/editor/src/editor.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8f69efe30ef9f3be5f95b49162bb57baa43346dc..49a09385d521e2b79c01acc4b2ff2ad9db3be936 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -14988,15 +14988,13 @@ impl Editor { let mut new_range = old_range.clone(); while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone()) { - if !node.is_named() { - new_range = node.start_byte()..node.end_byte(); - continue; - } - new_range = match containing_range { MultiOrSingleBufferOffsetRange::Single(_) => break, MultiOrSingleBufferOffsetRange::Multi(range) => range, }; + if !node.is_named() { + continue; + } if !display_map.intersects_fold(new_range.start) && !display_map.intersects_fold(new_range.end) { From 83d9f07547f596d945db5d2a65ad6e35318c51b6 Mon Sep 17 00:00:00 2001 From: localcc Date: Wed, 17 Sep 2025 11:44:16 +0200 Subject: [PATCH 076/721] Add WSL opening UI (#38260) This PR adds an option to open WSL machines from the UI. - [x] Open wsl from open remote - [ ] Open local folder in wsl action - [ ] Open wsl shortcut (shortcuts to open remote) Release Notes: - N/A --- Cargo.lock | 54 +- crates/recent_projects/Cargo.toml | 3 + .../recent_projects/src/remote_connections.rs | 83 +- crates/recent_projects/src/remote_servers.rs | 1503 ++++++++++++----- 4 files changed, 1216 insertions(+), 427 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43a2fc4041fbf76b57e62d335e94c695ef07fc12..ee14efd2d800aeabb29a75959f9a929d06b78f81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2977,7 +2977,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -13790,6 +13790,7 @@ dependencies = [ "theme", "ui", "util", + "windows-registry 0.6.0", "workspace", "workspace-hack", "zed_actions", @@ -19613,7 +19614,7 @@ dependencies = [ "windows-collections", "windows-core 0.61.0", "windows-future", - "windows-link", + "windows-link 0.1.1", "windows-numerics", ] @@ -19683,7 +19684,7 @@ checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", - "windows-link", + "windows-link 0.1.1", "windows-result 0.3.2", "windows-strings 0.4.0", ] @@ -19695,7 +19696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" dependencies = [ "windows-core 0.61.0", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -19770,6 +19771,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -19777,7 +19784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.0", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -19797,11 +19804,22 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e" dependencies = [ - "windows-link", + "windows-link 0.1.1", "windows-result 0.3.2", "windows-strings 0.4.0", ] +[[package]] +name = "windows-registry" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f91f87ce112ffb7275000ea98eb1940912c21c1567c9312fde20261f3eadd29" +dependencies = [ + "windows-link 0.2.0", + "windows-result 0.4.0", + "windows-strings 0.5.0", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -19826,7 +19844,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-link", + "windows-link 0.1.1", +] + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link 0.2.0", ] [[package]] @@ -19845,7 +19872,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -19854,7 +19881,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ - "windows-link", + "windows-link 0.1.1", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link 0.2.0", ] [[package]] diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index d48beeaab6bfbe54e3cac6d7f836248cc0ff2f3e..91879c5d4175ad66428a255655f3c8bd4a5059e3 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -44,6 +44,9 @@ workspace.workspace = true zed_actions.workspace = true workspace-hack.workspace = true +[target.'cfg(target_os = "windows")'.dependencies] +windows-registry = "0.6.0" + [dev-dependencies] dap.workspace = true editor = { workspace = true, features = ["test-support"] } diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 3e6810239c80c72d74624bcc243157290fcd93fa..72e2844d501f8f8860d62964d22430af80bab4b6 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -17,7 +17,7 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use release_channel::ReleaseChannel; use remote::{ ConnectionIdentifier, RemoteClient, RemoteConnectionOptions, RemotePlatform, - SshConnectionOptions, SshPortForwardOption, + SshConnectionOptions, SshPortForwardOption, WslConnectionOptions, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -33,6 +33,7 @@ use workspace::{AppState, ModalView, Workspace}; #[derive(Deserialize)] pub struct SshSettings { pub ssh_connections: Option>, + pub wsl_connections: Option>, /// Whether to read ~/.ssh/config for ssh connection sources. #[serde(default = "default_true")] pub read_ssh_config: bool, @@ -43,6 +44,10 @@ impl SshSettings { self.ssh_connections.clone().into_iter().flatten() } + pub fn wsl_connections(&self) -> impl Iterator + use<> { + self.wsl_connections.clone().into_iter().flatten() + } + pub fn fill_connection_options_from_settings(&self, options: &mut SshConnectionOptions) { for conn in self.ssh_connections() { if conn.host == options.host @@ -116,6 +121,51 @@ impl From for SshConnectionOptions { } } +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct WslConnection { + pub distro_name: SharedString, + #[serde(default)] + pub user: Option, + #[serde(default)] + pub projects: BTreeSet, +} + +impl From for WslConnectionOptions { + fn from(val: WslConnection) -> Self { + WslConnectionOptions { + distro_name: val.distro_name.into(), + user: val.user, + } + } +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +pub enum Connection { + Ssh(SshConnection), + Wsl(WslConnection), +} + +impl From for RemoteConnectionOptions { + fn from(val: Connection) -> Self { + match val { + Connection::Ssh(conn) => RemoteConnectionOptions::Ssh(conn.into()), + Connection::Wsl(conn) => RemoteConnectionOptions::Wsl(conn.into()), + } + } +} + +impl From for Connection { + fn from(val: SshConnection) -> Self { + Connection::Ssh(val) + } +} + +impl From for Connection { + fn from(val: WslConnection) -> Self { + Connection::Wsl(val) + } +} + #[derive(Clone, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema)] pub struct SshProject { pub paths: Vec, @@ -125,6 +175,7 @@ pub struct SshProject { #[settings_key(None)] pub struct RemoteSettingsContent { pub ssh_connections: Option>, + pub wsl_connections: Option>, pub read_ssh_config: Option, } @@ -561,6 +612,36 @@ pub fn connect_over_ssh( ) } +pub fn connect( + unique_identifier: ConnectionIdentifier, + connection_options: RemoteConnectionOptions, + ui: Entity, + window: &mut Window, + cx: &mut App, +) -> Task>>> { + let window = window.window_handle(); + let known_password = match &connection_options { + RemoteConnectionOptions::Ssh(ssh_connection_options) => { + ssh_connection_options.password.clone() + } + _ => None, + }; + let (tx, rx) = oneshot::channel(); + ui.update(cx, |ui, _cx| ui.set_cancellation_tx(tx)); + + remote::RemoteClient::new( + unique_identifier, + connection_options, + rx, + Arc::new(RemoteClientDelegate { + window, + ui: ui.downgrade(), + known_password, + }), + cx, + ) +} + pub async fn open_remote_project( connection_options: RemoteConnectionOptions, paths: Vec, diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 39032642b887350730c16a12d696253c256cfd72..d7e7505851a2b0cd2f86c807d6850937096dac7c 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1,7 +1,8 @@ use crate::{ remote_connections::{ - RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent, SshConnection, - SshConnectionHeader, SshProject, SshSettings, connect_over_ssh, open_remote_project, + Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent, + SshConnection, SshConnectionHeader, SshProject, SshSettings, connect, connect_over_ssh, + open_remote_project, }, ssh_config::parse_ssh_config_hosts, }; @@ -13,11 +14,12 @@ use gpui::{ FocusHandle, Focusable, PromptLevel, ScrollHandle, Subscription, Task, WeakEntity, Window, canvas, }; +use log::info; use paths::{global_ssh_config_file, user_ssh_config_file}; use picker::Picker; use project::{Fs, Project}; use remote::{ - RemoteClient, RemoteConnectionOptions, SshConnectionOptions, + RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions, remote_client::ConnectionIdentifier, }; use settings::{Settings, SettingsStore, update_settings_file, watch_config_file}; @@ -79,27 +81,253 @@ impl CreateRemoteServer { } } +#[cfg(target_os = "windows")] +struct AddWslDistro { + picker: Entity>, + connection_prompt: Option>, + _creating: Option>, +} + +#[cfg(target_os = "windows")] +impl AddWslDistro { + fn new(window: &mut Window, cx: &mut Context) -> Self { + let delegate = WslPickerDelegate::new(); + let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)); + + cx.subscribe_in( + &picker, + window, + |this, _, _: &WslDistroSelected, window, cx| { + this.confirm(&menu::Confirm, window, cx); + }, + ) + .detach(); + + cx.subscribe_in( + &picker, + window, + |this, _, _: &WslPickerDismissed, window, cx| { + this.cancel(&menu::Cancel, window, cx); + }, + ) + .detach(); + + AddWslDistro { + picker, + connection_prompt: None, + _creating: None, + } + } +} + +#[cfg(target_os = "windows")] +#[derive(Clone, Debug)] +pub struct WslDistroSelected(pub String); + +#[cfg(target_os = "windows")] +#[derive(Clone, Debug)] +pub struct WslPickerDismissed; + +#[cfg(target_os = "windows")] +struct WslPickerDelegate { + selected_index: usize, + distro_list: Option>, + matches: Vec, +} + +#[cfg(target_os = "windows")] +impl WslPickerDelegate { + fn new() -> Self { + WslPickerDelegate { + selected_index: 0, + distro_list: None, + matches: Vec::new(), + } + } + + pub fn selected_distro(&self) -> Option { + self.matches + .get(self.selected_index) + .map(|m| m.string.clone()) + } +} + +#[cfg(target_os = "windows")] +impl WslPickerDelegate { + fn fetch_distros() -> anyhow::Result> { + use anyhow::Context; + use windows_registry::CURRENT_USER; + + let lxss_key = CURRENT_USER + .open("Software\\Microsoft\\Windows\\CurrentVersion\\Lxss") + .context("failed to get lxss wsl key")?; + + let distros = lxss_key + .keys() + .context("failed to get wsl distros")? + .filter_map(|key| { + lxss_key + .open(&key) + .context("failed to open subkey for distro") + .log_err() + }) + .filter_map(|distro| distro.get_string("DistributionName").ok()) + .collect::>(); + + Ok(distros) + } +} + +#[cfg(target_os = "windows")] +impl EventEmitter for Picker {} + +#[cfg(target_os = "windows")] +impl EventEmitter for Picker {} + +#[cfg(target_os = "windows")] +impl picker::PickerDelegate for WslPickerDelegate { + type ListItem = ListItem; + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index( + &mut self, + ix: usize, + _window: &mut Window, + cx: &mut Context>, + ) { + self.selected_index = ix; + cx.notify(); + } + + fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { + Arc::from("Enter WSL distro name") + } + + fn update_matches( + &mut self, + query: String, + _window: &mut Window, + cx: &mut Context>, + ) -> Task<()> { + use fuzzy::StringMatchCandidate; + + let needs_fetch = self.distro_list.is_none(); + if needs_fetch { + let distros = Self::fetch_distros().log_err(); + self.distro_list = distros; + } + + if let Some(distro_list) = &self.distro_list { + use ordered_float::OrderedFloat; + + let candidates = distro_list + .iter() + .enumerate() + .map(|(id, distro)| StringMatchCandidate::new(id, distro)) + .collect::>(); + + let query = query.trim_start(); + let smart_case = query.chars().any(|c| c.is_uppercase()); + self.matches = smol::block_on(fuzzy::match_strings( + candidates.as_slice(), + query, + smart_case, + true, + 100, + &Default::default(), + cx.background_executor().clone(), + )); + self.matches.sort_unstable_by_key(|m| m.candidate_id); + + self.selected_index = self + .matches + .iter() + .enumerate() + .rev() + .max_by_key(|(_, m)| OrderedFloat(m.score)) + .map(|(index, _)| index) + .unwrap_or(0); + } + + Task::ready(()) + } + + fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context>) { + if let Some(distro) = self.matches.get(self.selected_index) { + cx.emit(WslDistroSelected(distro.string.clone())); + } + } + + fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { + cx.emit(WslPickerDismissed); + } + + fn render_match( + &self, + ix: usize, + selected: bool, + _: &mut Window, + _: &mut Context>, + ) -> Option { + use ui::HighlightedLabel; + + let matched = self.matches.get(ix)?; + Some( + ListItem::new(ix) + .toggle_state(selected) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .child( + h_flex() + .flex_grow() + .gap_3() + .child(Icon::new(IconName::Server)) + .child(v_flex().child(HighlightedLabel::new( + matched.string.clone(), + matched.positions.clone(), + ))), + ), + ) + } +} + +enum ProjectPickerData { + Ssh { + connection_string: SharedString, + nickname: Option, + }, + Wsl { + distro_name: SharedString, + }, +} + struct ProjectPicker { - connection_string: SharedString, - nickname: Option, + data: ProjectPickerData, picker: Entity>, _path_task: Shared>>, } struct EditNicknameState { - index: usize, + index: SshServerIndex, editor: Entity, } impl EditNicknameState { - fn new(index: usize, window: &mut Window, cx: &mut App) -> Self { + fn new(index: SshServerIndex, window: &mut Window, cx: &mut App) -> Self { let this = Self { index, editor: cx.new(|cx| Editor::single_line(window, cx)), }; let starting_text = SshSettings::get_global(cx) .ssh_connections() - .nth(index) + .nth(index.0) .and_then(|state| state.nickname) .filter(|text| !text.is_empty()); this.editor.update(cx, |this, cx| { @@ -122,8 +350,8 @@ impl Focusable for ProjectPicker { impl ProjectPicker { fn new( create_new_window: bool, - ix: usize, - connection: SshConnectionOptions, + index: ServerIndex, + connection: RemoteConnectionOptions, project: Entity, home_dir: RemotePathBuf, path_style: PathStyle, @@ -142,8 +370,16 @@ impl ProjectPicker { picker.set_query(home_dir.to_string(), window, cx); picker }); - let connection_string = connection.connection_string().into(); - let nickname = connection.nickname.clone().map(|nick| nick.into()); + + let data = match &connection { + RemoteConnectionOptions::Ssh(connection) => ProjectPickerData::Ssh { + connection_string: connection.connection_string().into(), + nickname: connection.nickname.clone().map(|nick| nick.into()), + }, + RemoteConnectionOptions::Wsl(connection) => ProjectPickerData::Wsl { + distro_name: connection.distro_name.clone().into(), + }, + }; let _path_task = cx .spawn_in(window, { let workspace = workspace; @@ -178,13 +414,24 @@ impl ProjectPicker { .iter() .map(|path| path.to_string_lossy().to_string()) .collect(); - move |setting, _| { - if let Some(server) = setting - .ssh_connections - .as_mut() - .and_then(|connections| connections.get_mut(ix)) - { - server.projects.insert(SshProject { paths }); + move |setting, _| match index { + ServerIndex::Ssh(index) => { + if let Some(server) = setting + .ssh_connections + .as_mut() + .and_then(|connections| connections.get_mut(index.0)) + { + server.projects.insert(SshProject { paths }); + }; + } + ServerIndex::Wsl(index) => { + if let Some(server) = setting + .wsl_connections + .as_mut() + .and_then(|connections| connections.get_mut(index.0)) + { + server.projects.insert(SshProject { paths }); + }; } } }); @@ -204,12 +451,7 @@ impl ProjectPicker { .log_err()?; open_remote_project_with_existing_connection( - RemoteConnectionOptions::Ssh(connection), - project, - paths, - app_state, - window, - cx, + connection, project, paths, app_state, window, cx, ) .await .log_err(); @@ -225,8 +467,7 @@ impl ProjectPicker { cx.new(|_| Self { _path_task, picker, - connection_string, - nickname, + data, }) } } @@ -234,14 +475,23 @@ impl ProjectPicker { impl gpui::Render for ProjectPicker { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() - .child( - SshConnectionHeader { - connection_string: self.connection_string.clone(), + .child(match &self.data { + ProjectPickerData::Ssh { + connection_string, + nickname, + } => SshConnectionHeader { + connection_string: connection_string.clone(), paths: Default::default(), - nickname: self.nickname.clone(), + nickname: nickname.clone(), } .render(window, cx), - ) + ProjectPickerData::Wsl { distro_name } => SshConnectionHeader { + connection_string: distro_name.clone(), + paths: Default::default(), + nickname: None, + } + .render(window, cx), + }) .child( div() .border_t_1() @@ -251,13 +501,48 @@ impl gpui::Render for ProjectPicker { } } +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +struct SshServerIndex(usize); +impl std::fmt::Display for SshServerIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +struct WslServerIndex(usize); +impl std::fmt::Display for WslServerIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +enum ServerIndex { + Ssh(SshServerIndex), + Wsl(WslServerIndex), +} +impl From for ServerIndex { + fn from(index: SshServerIndex) -> Self { + Self::Ssh(index) + } +} +impl From for ServerIndex { + fn from(index: WslServerIndex) -> Self { + Self::Wsl(index) + } +} + #[derive(Clone)] enum RemoteEntry { Project { open_folder: NavigableEntry, projects: Vec<(NavigableEntry, SshProject)>, configure: NavigableEntry, - connection: SshConnection, + connection: Connection, + index: ServerIndex, }, SshConfig { open_folder: NavigableEntry, @@ -270,13 +555,16 @@ impl RemoteEntry { matches!(self, Self::Project { .. }) } - fn connection(&self) -> Cow<'_, SshConnection> { + fn connection(&self) -> Cow<'_, Connection> { match self { Self::Project { connection, .. } => Cow::Borrowed(connection), - Self::SshConfig { host, .. } => Cow::Owned(SshConnection { - host: host.clone(), - ..SshConnection::default() - }), + Self::SshConfig { host, .. } => Cow::Owned( + SshConnection { + host: host.clone(), + ..SshConnection::default() + } + .into(), + ), } } } @@ -285,6 +573,7 @@ impl RemoteEntry { struct DefaultState { scroll_handle: ScrollHandle, add_new_server: NavigableEntry, + add_new_wsl: NavigableEntry, servers: Vec, } @@ -292,13 +581,15 @@ impl DefaultState { fn new(ssh_config_servers: &BTreeSet, cx: &mut App) -> Self { let handle = ScrollHandle::new(); let add_new_server = NavigableEntry::new(&handle, cx); + let add_new_wsl = NavigableEntry::new(&handle, cx); let ssh_settings = SshSettings::get_global(cx); let read_ssh_config = ssh_settings.read_ssh_config; - let mut servers: Vec = ssh_settings + let ssh_servers = ssh_settings .ssh_connections() - .map(|connection| { + .enumerate() + .map(|(index, connection)| { let open_folder = NavigableEntry::new(&handle, cx); let configure = NavigableEntry::new(&handle, cx); let projects = connection @@ -310,16 +601,42 @@ impl DefaultState { open_folder, configure, projects, - connection, + index: ServerIndex::Ssh(SshServerIndex(index)), + connection: connection.into(), } - }) - .collect(); + }); + + let wsl_servers = ssh_settings + .wsl_connections() + .enumerate() + .map(|(index, connection)| { + let open_folder = NavigableEntry::new(&handle, cx); + let configure = NavigableEntry::new(&handle, cx); + let projects = connection + .projects + .iter() + .map(|project| (NavigableEntry::new(&handle, cx), project.clone())) + .collect(); + RemoteEntry::Project { + open_folder, + configure, + projects, + index: ServerIndex::Wsl(WslServerIndex(index)), + connection: connection.into(), + } + }); + + let mut servers = ssh_servers.chain(wsl_servers).collect::>(); if read_ssh_config { let mut extra_servers_from_config = ssh_config_servers.clone(); for server in &servers { - if let RemoteEntry::Project { connection, .. } = server { - extra_servers_from_config.remove(&connection.host); + if let RemoteEntry::Project { + connection: Connection::Ssh(ssh_options), + .. + } = server + { + extra_servers_from_config.remove(&SharedString::new(ssh_options.host.clone())); } } servers.extend(extra_servers_from_config.into_iter().map(|host| { @@ -333,23 +650,43 @@ impl DefaultState { Self { scroll_handle: handle, add_new_server, + add_new_wsl, servers, } } } #[derive(Clone)] -struct ViewServerOptionsState { - server_index: usize, - connection: SshConnection, - entries: [NavigableEntry; 4], +enum ViewServerOptionsState { + Ssh { + connection: SshConnectionOptions, + server_index: SshServerIndex, + entries: [NavigableEntry; 4], + }, + Wsl { + connection: WslConnectionOptions, + server_index: WslServerIndex, + entries: [NavigableEntry; 2], + }, } + +impl ViewServerOptionsState { + fn entries(&self) -> &[NavigableEntry] { + match self { + Self::Ssh { entries, .. } => entries, + Self::Wsl { entries, .. } => entries, + } + } +} + enum Mode { Default(DefaultState), ViewServerOptions(ViewServerOptionsState), EditNickname(EditNicknameState), ProjectPicker(Entity), CreateRemoteServer(CreateRemoteServer), + #[cfg(target_os = "windows")] + AddWslDistro(AddWslDistro), } impl Mode { @@ -357,6 +694,7 @@ impl Mode { Self::Default(DefaultState::new(ssh_config_servers, cx)) } } + impl RemoteServerProjects { pub fn new( create_new_window: bool, @@ -405,10 +743,10 @@ impl RemoteServerProjects { } } - pub fn project_picker( + fn project_picker( create_new_window: bool, - ix: usize, - connection_options: remote::SshConnectionOptions, + index: ServerIndex, + connection_options: remote::RemoteConnectionOptions, project: Entity, home_dir: RemotePathBuf, path_style: PathStyle, @@ -420,7 +758,7 @@ impl RemoteServerProjects { let mut this = Self::new(create_new_window, fs, window, workspace.clone(), cx); this.mode = Mode::ProjectPicker(ProjectPicker::new( create_new_window, - ix, + index, connection_options, project, home_dir, @@ -480,6 +818,7 @@ impl RemoteServerProjects { match connection.await { Some(Some(client)) => this .update_in(cx, |this, window, cx| { + info!("ssh server created"); telemetry::event!("SSH Server Created"); this.retained_connections.push(client); this.add_ssh_server(connection_options, cx); @@ -517,25 +856,100 @@ impl RemoteServerProjects { }); } + #[cfg(target_os = "windows")] + fn connect_wsl_distro( + &mut self, + picker: Entity>, + distro: String, + window: &mut Window, + cx: &mut Context, + ) { + let connection_options = WslConnectionOptions { + distro_name: distro, + user: None, + }; + + let prompt = cx.new(|cx| { + RemoteConnectionPrompt::new(connection_options.distro_name.clone(), None, window, cx) + }); + let connection = connect( + ConnectionIdentifier::setup(), + connection_options.clone().into(), + prompt.clone(), + window, + cx, + ) + .prompt_err("Failed to connect", window, cx, |_, _, _| None); + + let wsl_picker = picker.clone(); + let creating = cx.spawn_in(window, async move |this, cx| { + match connection.await { + Some(Some(client)) => this + .update_in(cx, |this, window, cx| { + telemetry::event!("WSL Distro Added"); + this.retained_connections.push(client); + this.add_wsl_distro(connection_options, cx); + this.mode = Mode::default_mode(&BTreeSet::new(), cx); + this.focus_handle(cx).focus(window); + cx.notify() + }) + .log_err(), + _ => this + .update(cx, |this, cx| { + this.mode = Mode::AddWslDistro(AddWslDistro { + picker: wsl_picker, + connection_prompt: None, + _creating: None, + }); + cx.notify() + }) + .log_err(), + }; + () + }); + + self.mode = Mode::AddWslDistro(AddWslDistro { + picker, + connection_prompt: Some(prompt), + _creating: Some(creating), + }); + } + fn view_server_options( &mut self, - (server_index, connection): (usize, SshConnection), + (server_index, connection): (ServerIndex, RemoteConnectionOptions), window: &mut Window, cx: &mut Context, ) { - self.mode = Mode::ViewServerOptions(ViewServerOptionsState { - server_index, - connection, - entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)), + self.mode = Mode::ViewServerOptions(match (server_index, connection) { + (ServerIndex::Ssh(server_index), RemoteConnectionOptions::Ssh(connection)) => { + ViewServerOptionsState::Ssh { + connection, + server_index, + entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)), + } + } + (ServerIndex::Wsl(server_index), RemoteConnectionOptions::Wsl(connection)) => { + ViewServerOptionsState::Wsl { + connection, + server_index, + entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)), + } + } + _ => { + log::error!("server index and connection options mismatch"); + self.mode = Mode::default_mode(&BTreeSet::default(), cx); + return; + } }); self.focus_handle(cx).focus(window); cx.notify(); } - fn create_ssh_project( + fn create_remote_project( &mut self, - ix: usize, - ssh_connection: SshConnection, + index: ServerIndex, + connection_options: RemoteConnectionOptions, window: &mut Window, cx: &mut Context, ) { @@ -544,17 +958,11 @@ impl RemoteServerProjects { }; let create_new_window = self.create_new_window; - let connection_options: SshConnectionOptions = ssh_connection.into(); workspace.update(cx, |_, cx| { cx.defer_in(window, move |workspace, window, cx| { let app_state = workspace.app_state().clone(); workspace.toggle_modal(window, cx, |window, cx| { - RemoteConnectionModal::new( - &RemoteConnectionOptions::Ssh(connection_options.clone()), - Vec::new(), - window, - cx, - ) + RemoteConnectionModal::new(&connection_options, Vec::new(), window, cx) }); let prompt = workspace .active_modal::(cx) @@ -563,7 +971,7 @@ impl RemoteServerProjects { .prompt .clone(); - let connect = connect_over_ssh( + let connect = connect( ConnectionIdentifier::setup(), connection_options.clone(), prompt, @@ -624,7 +1032,7 @@ impl RemoteServerProjects { workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::project_picker( create_new_window, - ix, + index, connection_options, project, home_dir, @@ -662,7 +1070,7 @@ impl RemoteServerProjects { let index = state.index; self.update_settings_file(cx, move |setting, _| { if let Some(connections) = setting.ssh_connections.as_mut() - && let Some(connection) = connections.get_mut(index) + && let Some(connection) = connections.get_mut(index.0) { connection.nickname = text; } @@ -670,6 +1078,12 @@ impl RemoteServerProjects { self.mode = Mode::default_mode(&self.ssh_config_servers, cx); self.focus_handle.focus(window); } + #[cfg(target_os = "windows")] + Mode::AddWslDistro(state) => { + let delegate = &state.picker.read(cx).delegate; + let distro = delegate.selected_distro().unwrap(); + self.connect_wsl_distro(state.picker.clone(), distro, window, cx); + } } } @@ -702,11 +1116,19 @@ impl RemoteServerProjects { cx: &mut Context, ) -> impl IntoElement { let connection = ssh_server.connection().into_owned(); - let (main_label, aux_label) = if let Some(nickname) = connection.nickname.clone() { - let aux_label = SharedString::from(format!("({})", connection.host)); - (nickname.into(), Some(aux_label)) - } else { - (connection.host.clone(), None) + + let (main_label, aux_label, is_wsl) = match &connection { + Connection::Ssh(connection) => { + if let Some(nickname) = connection.nickname.clone() { + let aux_label = SharedString::from(format!("({})", connection.host)); + (nickname.into(), Some(aux_label), false) + } else { + (connection.host.clone(), None, false) + } + } + Connection::Wsl(wsl_connection_options) => { + (wsl_connection_options.distro_name.clone(), None, true) + } }; v_flex() .w_full() @@ -720,11 +1142,23 @@ impl RemoteServerProjects { .gap_1() .overflow_hidden() .child( - div().max_w_96().overflow_hidden().text_ellipsis().child( - Label::new(main_label) - .size(LabelSize::Small) - .color(Color::Muted), - ), + h_flex() + .gap_1() + .max_w_96() + .overflow_hidden() + .text_ellipsis() + .when(is_wsl, |this| { + this.child( + Label::new("WSL:") + .size(LabelSize::Small) + .color(Color::Muted), + ) + }) + .child( + Label::new(main_label) + .size(LabelSize::Small) + .color(Color::Muted), + ), ) .children( aux_label.map(|label| { @@ -738,98 +1172,114 @@ impl RemoteServerProjects { projects, configure, connection, - } => List::new() - .empty_message("No projects.") - .children(projects.iter().enumerate().map(|(pix, p)| { - v_flex().gap_0p5().child(self.render_ssh_project( - ix, - ssh_server.clone(), - pix, - p, - window, - cx, - )) - })) - .child( - h_flex() - .id(("new-remote-project-container", ix)) - .track_focus(&open_folder.focus_handle) - .anchor_scroll(open_folder.scroll_anchor.clone()) - .on_action(cx.listener({ - let ssh_connection = connection.clone(); - move |this, _: &menu::Confirm, window, cx| { - this.create_ssh_project(ix, ssh_connection.clone(), window, cx); - } - })) - .child( - ListItem::new(("new-remote-project", ix)) - .toggle_state( - open_folder.focus_handle.contains_focused(window, cx), - ) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Plus).color(Color::Muted)) - .child(Label::new("Open Folder")) - .on_click(cx.listener({ - let ssh_connection = connection.clone(); - move |this, _, window, cx| { - this.create_ssh_project( - ix, - ssh_connection.clone(), - window, - cx, - ); - } - })), - ), - ) - .child( - h_flex() - .id(("server-options-container", ix)) - .track_focus(&configure.focus_handle) - .anchor_scroll(configure.scroll_anchor.clone()) - .on_action(cx.listener({ - let ssh_connection = connection.clone(); - move |this, _: &menu::Confirm, window, cx| { - this.view_server_options( - (ix, ssh_connection.clone()), - window, - cx, - ); - } - })) - .child( - ListItem::new(("server-options", ix)) - .toggle_state( - configure.focus_handle.contains_focused(window, cx), - ) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Settings).color(Color::Muted)) - .child(Label::new("View Server Options")) - .on_click(cx.listener({ - let ssh_connection = connection.clone(); - move |this, _, window, cx| { - this.view_server_options( - (ix, ssh_connection.clone()), - window, - cx, - ); - } - })), - ), - ), + index, + } => { + let index = *index; + List::new() + .empty_message("No projects.") + .children(projects.iter().enumerate().map(|(pix, p)| { + v_flex().gap_0p5().child(self.render_ssh_project( + index, + ssh_server.clone(), + pix, + p, + window, + cx, + )) + })) + .child( + h_flex() + .id(("new-remote-project-container", ix)) + .track_focus(&open_folder.focus_handle) + .anchor_scroll(open_folder.scroll_anchor.clone()) + .on_action(cx.listener({ + let connection = connection.clone(); + move |this, _: &menu::Confirm, window, cx| { + this.create_remote_project( + index, + connection.clone().into(), + window, + cx, + ); + } + })) + .child( + ListItem::new(("new-remote-project", ix)) + .toggle_state( + open_folder.focus_handle.contains_focused(window, cx), + ) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Plus).color(Color::Muted)) + .child(Label::new("Open Folder")) + .on_click(cx.listener({ + let connection = connection.clone(); + move |this, _, window, cx| { + this.create_remote_project( + index, + connection.clone().into(), + window, + cx, + ); + } + })), + ), + ) + .child( + h_flex() + .id(("server-options-container", ix)) + .track_focus(&configure.focus_handle) + .anchor_scroll(configure.scroll_anchor.clone()) + .on_action(cx.listener({ + let connection = connection.clone(); + move |this, _: &menu::Confirm, window, cx| { + this.view_server_options( + (index, connection.clone().into()), + window, + cx, + ); + } + })) + .child( + ListItem::new(("server-options", ix)) + .toggle_state( + configure.focus_handle.contains_focused(window, cx), + ) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot( + Icon::new(IconName::Settings).color(Color::Muted), + ) + .child(Label::new("View Server Options")) + .on_click(cx.listener({ + let ssh_connection = connection.clone(); + move |this, _, window, cx| { + this.view_server_options( + (index, ssh_connection.clone().into()), + window, + cx, + ); + } + })), + ), + ) + } RemoteEntry::SshConfig { open_folder, host } => List::new().child( h_flex() .id(("new-remote-project-container", ix)) .track_focus(&open_folder.focus_handle) .anchor_scroll(open_folder.scroll_anchor.clone()) .on_action(cx.listener({ - let ssh_connection = connection.clone(); + let connection = connection.clone(); let host = host.clone(); move |this, _: &menu::Confirm, window, cx| { let new_ix = this.create_host_from_ssh_config(&host, cx); - this.create_ssh_project(new_ix, ssh_connection.clone(), window, cx); + this.create_remote_project( + new_ix.into(), + connection.clone().into(), + window, + cx, + ); } })) .child( @@ -840,13 +1290,12 @@ impl RemoteServerProjects { .start_slot(Icon::new(IconName::Plus).color(Color::Muted)) .child(Label::new("Open Folder")) .on_click(cx.listener({ - let ssh_connection = connection; let host = host.clone(); move |this, _, window, cx| { let new_ix = this.create_host_from_ssh_config(&host, cx); - this.create_ssh_project( - new_ix, - ssh_connection.clone(), + this.create_remote_project( + new_ix.into(), + connection.clone().into(), window, cx, ); @@ -859,7 +1308,7 @@ impl RemoteServerProjects { fn render_ssh_project( &mut self, - server_ix: usize, + server_ix: ServerIndex, server: RemoteEntry, ix: usize, (navigation, project): &(NavigableEntry, SshProject), @@ -868,7 +1317,13 @@ impl RemoteServerProjects { ) -> impl IntoElement { let create_new_window = self.create_new_window; let is_from_zed = server.is_from_zed(); - let element_id_base = SharedString::from(format!("remote-project-{server_ix}")); + let element_id_base = SharedString::from(format!( + "remote-project-{}", + match server_ix { + ServerIndex::Ssh(index) => format!("ssh-{index}"), + ServerIndex::Wsl(index) => format!("wsl-{index}"), + } + )); let container_element_id_base = SharedString::from(format!("remote-project-container-{element_id_base}")); @@ -896,7 +1351,7 @@ impl RemoteServerProjects { cx.spawn_in(window, async move |_, cx| { let result = open_remote_project( - RemoteConnectionOptions::Ssh(server.into()), + server.into(), project.paths.into_iter().map(PathBuf::from).collect(), app_state, OpenOptions { @@ -953,25 +1408,31 @@ impl RemoteServerProjects { let secondary_confirm = e.modifiers().platform; callback(this, secondary_confirm, window, cx) })) - .when(is_from_zed, |server_list_item| { - server_list_item.end_hover_slot::(Some( - div() - .mr_2() - .child({ - let project = project.clone(); - // Right-margin to offset it from the Scrollbar - IconButton::new("remove-remote-project", IconName::Trash) - .icon_size(IconSize::Small) - .shape(IconButtonShape::Square) - .size(ButtonSize::Large) - .tooltip(Tooltip::text("Delete Remote Project")) - .on_click(cx.listener(move |this, _, _, cx| { - this.delete_ssh_project(server_ix, &project, cx) - })) - }) - .into_any_element(), - )) - }), + .when( + is_from_zed && matches!(server_ix, ServerIndex::Ssh(_)), + |server_list_item| { + let ServerIndex::Ssh(server_ix) = server_ix else { + unreachable!() + }; + server_list_item.end_hover_slot::(Some( + div() + .mr_2() + .child({ + let project = project.clone(); + // Right-margin to offset it from the Scrollbar + IconButton::new("remove-remote-project", IconName::Trash) + .icon_size(IconSize::Small) + .shape(IconButtonShape::Square) + .size(ButtonSize::Large) + .tooltip(Tooltip::text("Delete Remote Project")) + .on_click(cx.listener(move |this, _, _, cx| { + this.delete_ssh_project(server_ix, &project, cx) + })) + }) + .into_any_element(), + )) + }, + ), ) } @@ -990,27 +1451,58 @@ impl RemoteServerProjects { update_settings_file::(fs, cx, move |setting, cx| f(setting, cx)); } - fn delete_ssh_server(&mut self, server: usize, cx: &mut Context) { + fn delete_ssh_server(&mut self, server: SshServerIndex, cx: &mut Context) { self.update_settings_file(cx, move |setting, _| { if let Some(connections) = setting.ssh_connections.as_mut() { - connections.remove(server); + connections.remove(server.0); } }); } - fn delete_ssh_project(&mut self, server: usize, project: &SshProject, cx: &mut Context) { + fn delete_ssh_project( + &mut self, + server: SshServerIndex, + project: &SshProject, + cx: &mut Context, + ) { let project = project.clone(); self.update_settings_file(cx, move |setting, _| { if let Some(server) = setting .ssh_connections .as_mut() - .and_then(|connections| connections.get_mut(server)) + .and_then(|connections| connections.get_mut(server.0)) { server.projects.remove(&project); } }); } + #[cfg(target_os = "windows")] + fn add_wsl_distro( + &mut self, + connection_options: remote::WslConnectionOptions, + cx: &mut Context, + ) { + self.update_settings_file(cx, move |setting, _| { + setting + .wsl_connections + .get_or_insert(Default::default()) + .push(crate::remote_connections::WslConnection { + distro_name: SharedString::from(connection_options.distro_name), + user: connection_options.user, + projects: BTreeSet::new(), + }) + }); + } + + fn delete_wsl_distro(&mut self, server: WslServerIndex, cx: &mut Context) { + self.update_settings_file(cx, move |setting, _| { + if let Some(connections) = setting.wsl_connections.as_mut() { + connections.remove(server.0); + } + }); + } + fn add_ssh_server( &mut self, connection_options: remote::SshConnectionOptions, @@ -1108,222 +1600,94 @@ impl RemoteServerProjects { ) } + #[cfg(target_os = "windows")] + fn render_add_wsl_distro( + &self, + state: &AddWslDistro, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let connection_prompt = state.connection_prompt.clone(); + + state.picker.update(cx, |picker, cx| { + picker.focus_handle(cx).focus(window); + }); + + v_flex() + .id("add-wsl-distro") + .overflow_hidden() + .size_full() + .flex_1() + .map(|this| { + if let Some(connection_prompt) = connection_prompt { + this.child(connection_prompt) + } else { + this.child(state.picker.clone()) + } + }) + } + fn render_view_options( &mut self, - ViewServerOptionsState { - server_index, - connection, - entries, - }: ViewServerOptionsState, + options: ViewServerOptionsState, window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let connection_string = connection.host.clone(); + let last_entry = options.entries().last().unwrap(); let mut view = Navigable::new( div() .track_focus(&self.focus_handle(cx)) .size_full() - .child( - SshConnectionHeader { - connection_string: connection_string.clone(), + .child(match &options { + ViewServerOptionsState::Ssh { connection, .. } => SshConnectionHeader { + connection_string: connection.host.clone().into(), paths: Default::default(), nickname: connection.nickname.clone().map(|s| s.into()), } - .render(window, cx), - ) + .render(window, cx) + .into_any_element(), + ViewServerOptionsState::Wsl { connection, .. } => SshConnectionHeader { + connection_string: connection.distro_name.clone().into(), + paths: Default::default(), + nickname: None, + } + .render(window, cx) + .into_any_element(), + }) .child( v_flex() .pb_1() .child(ListSeparator) - .child({ - let label = if connection.nickname.is_some() { - "Edit Nickname" - } else { - "Add Nickname to Server" - }; - div() - .id("ssh-options-add-nickname") - .track_focus(&entries[0].focus_handle) - .on_action(cx.listener( - move |this, _: &menu::Confirm, window, cx| { - this.mode = Mode::EditNickname(EditNicknameState::new( - server_index, - window, - cx, - )); - cx.notify(); - }, - )) - .child( - ListItem::new("add-nickname") - .toggle_state( - entries[0].focus_handle.contains_focused(window, cx), - ) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Pencil).color(Color::Muted)) - .child(Label::new(label)) - .on_click(cx.listener(move |this, _, window, cx| { - this.mode = Mode::EditNickname(EditNicknameState::new( - server_index, - window, - cx, - )); - cx.notify(); - })), - ) - }) - .child({ - let workspace = self.workspace.clone(); - fn callback( - workspace: WeakEntity, - connection_string: SharedString, - cx: &mut App, - ) { - cx.write_to_clipboard(ClipboardItem::new_string( - connection_string.to_string(), - )); - workspace - .update(cx, |this, cx| { - struct SshServerAddressCopiedToClipboard; - let notification = format!( - "Copied server address ({}) to clipboard", - connection_string - ); - - this.show_toast( - Toast::new( - NotificationId::composite::< - SshServerAddressCopiedToClipboard, - >( - connection_string.clone() - ), - notification, - ) - .autohide(), - cx, - ); - }) - .ok(); - } - div() - .id("ssh-options-copy-server-address") - .track_focus(&entries[1].focus_handle) - .on_action({ - let connection_string = connection_string.clone(); - let workspace = self.workspace.clone(); - move |_: &menu::Confirm, _, cx| { - callback(workspace.clone(), connection_string.clone(), cx); - } - }) - .child( - ListItem::new("copy-server-address") - .toggle_state( - entries[1].focus_handle.contains_focused(window, cx), - ) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Copy).color(Color::Muted)) - .child(Label::new("Copy Server Address")) - .end_hover_slot( - Label::new(connection_string.clone()) - .color(Color::Muted), - ) - .on_click({ - let connection_string = connection_string.clone(); - move |_, _, cx| { - callback( - workspace.clone(), - connection_string.clone(), - cx, - ); - } - }), - ) - }) - .child({ - fn remove_ssh_server( - remote_servers: Entity, - index: usize, - connection_string: SharedString, - window: &mut Window, - cx: &mut App, - ) { - let prompt_message = - format!("Remove server `{}`?", connection_string); - - let confirmation = window.prompt( - PromptLevel::Warning, - &prompt_message, - None, - &["Yes, remove it", "No, keep it"], - cx, - ); - - cx.spawn(async move |cx| { - if confirmation.await.ok() == Some(0) { - remote_servers - .update(cx, |this, cx| { - this.delete_ssh_server(index, cx); - }) - .ok(); - remote_servers - .update(cx, |this, cx| { - this.mode = Mode::default_mode( - &this.ssh_config_servers, - cx, - ); - cx.notify(); - }) - .ok(); - } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - } - div() - .id("ssh-options-copy-server-address") - .track_focus(&entries[2].focus_handle) - .on_action(cx.listener({ - let connection_string = connection_string.clone(); - move |_, _: &menu::Confirm, window, cx| { - remove_ssh_server( - cx.entity(), - server_index, - connection_string.clone(), - window, - cx, - ); - cx.focus_self(window); - } - })) - .child( - ListItem::new("remove-server") - .toggle_state( - entries[2].focus_handle.contains_focused(window, cx), - ) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Trash).color(Color::Error)) - .child(Label::new("Remove Server").color(Color::Error)) - .on_click(cx.listener(move |_, _, window, cx| { - remove_ssh_server( - cx.entity(), - server_index, - connection_string.clone(), - window, - cx, - ); - cx.focus_self(window); - })), - ) + .map(|this| match &options { + ViewServerOptionsState::Ssh { + connection, + entries, + server_index, + } => this.child(self.render_edit_ssh( + connection, + *server_index, + entries, + window, + cx, + )), + ViewServerOptionsState::Wsl { + connection, + entries, + server_index, + } => this.child(self.render_edit_wsl( + connection, + *server_index, + entries, + window, + cx, + )), }) .child(ListSeparator) .child({ div() .id("ssh-options-copy-server-address") - .track_focus(&entries[3].focus_handle) + .track_focus(&last_entry.focus_handle) .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| { this.mode = Mode::default_mode(&this.ssh_config_servers, cx); cx.focus_self(window); @@ -1332,7 +1696,7 @@ impl RemoteServerProjects { .child( ListItem::new("go-back") .toggle_state( - entries[3].focus_handle.contains_focused(window, cx), + last_entry.focus_handle.contains_focused(window, cx), ) .inset(true) .spacing(ui::ListItemSpacing::Sparse) @@ -1351,13 +1715,253 @@ impl RemoteServerProjects { ) .into_any_element(), ); - for entry in entries { - view = view.entry(entry); + + for entry in options.entries() { + view = view.entry(entry.clone()); } view.render(window, cx).into_any_element() } + fn render_edit_wsl( + &self, + connection: &WslConnectionOptions, + index: WslServerIndex, + entries: &[NavigableEntry], + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let distro_name = SharedString::new(connection.distro_name.clone()); + + v_flex().child({ + fn remove_wsl_distro( + remote_servers: Entity, + index: WslServerIndex, + distro_name: SharedString, + window: &mut Window, + cx: &mut App, + ) { + let prompt_message = format!("Remove WSL distro `{}`?", distro_name); + + let confirmation = window.prompt( + PromptLevel::Warning, + &prompt_message, + None, + &["Yes, remove it", "No, keep it"], + cx, + ); + + cx.spawn(async move |cx| { + if confirmation.await.ok() == Some(0) { + remote_servers + .update(cx, |this, cx| { + this.delete_wsl_distro(index, cx); + }) + .ok(); + remote_servers + .update(cx, |this, cx| { + this.mode = Mode::default_mode(&this.ssh_config_servers, cx); + cx.notify(); + }) + .ok(); + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + div() + .id("wsl-options-remove-distro") + .track_focus(&entries[0].focus_handle) + .on_action(cx.listener({ + let distro_name = distro_name.clone(); + move |_, _: &menu::Confirm, window, cx| { + remove_wsl_distro(cx.entity(), index, distro_name.clone(), window, cx); + cx.focus_self(window); + } + })) + .child( + ListItem::new("remove-distro") + .toggle_state(entries[0].focus_handle.contains_focused(window, cx)) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Trash).color(Color::Error)) + .child(Label::new("Remove Distro").color(Color::Error)) + .on_click(cx.listener(move |_, _, window, cx| { + remove_wsl_distro(cx.entity(), index, distro_name.clone(), window, cx); + cx.focus_self(window); + })), + ) + }) + } + + fn render_edit_ssh( + &self, + connection: &SshConnectionOptions, + index: SshServerIndex, + entries: &[NavigableEntry], + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let connection_string = SharedString::new(connection.host.clone()); + + v_flex() + .child({ + let label = if connection.nickname.is_some() { + "Edit Nickname" + } else { + "Add Nickname to Server" + }; + div() + .id("ssh-options-add-nickname") + .track_focus(&entries[0].focus_handle) + .on_action(cx.listener(move |this, _: &menu::Confirm, window, cx| { + this.mode = Mode::EditNickname(EditNicknameState::new(index, window, cx)); + cx.notify(); + })) + .child( + ListItem::new("add-nickname") + .toggle_state(entries[0].focus_handle.contains_focused(window, cx)) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Pencil).color(Color::Muted)) + .child(Label::new(label)) + .on_click(cx.listener(move |this, _, window, cx| { + this.mode = + Mode::EditNickname(EditNicknameState::new(index, window, cx)); + cx.notify(); + })), + ) + }) + .child({ + let workspace = self.workspace.clone(); + fn callback( + workspace: WeakEntity, + connection_string: SharedString, + cx: &mut App, + ) { + cx.write_to_clipboard(ClipboardItem::new_string(connection_string.to_string())); + workspace + .update(cx, |this, cx| { + struct SshServerAddressCopiedToClipboard; + let notification = format!( + "Copied server address ({}) to clipboard", + connection_string + ); + + this.show_toast( + Toast::new( + NotificationId::composite::( + connection_string.clone(), + ), + notification, + ) + .autohide(), + cx, + ); + }) + .ok(); + } + div() + .id("ssh-options-copy-server-address") + .track_focus(&entries[1].focus_handle) + .on_action({ + let connection_string = connection_string.clone(); + let workspace = self.workspace.clone(); + move |_: &menu::Confirm, _, cx| { + callback(workspace.clone(), connection_string.clone(), cx); + } + }) + .child( + ListItem::new("copy-server-address") + .toggle_state(entries[1].focus_handle.contains_focused(window, cx)) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Copy).color(Color::Muted)) + .child(Label::new("Copy Server Address")) + .end_hover_slot( + Label::new(connection_string.clone()).color(Color::Muted), + ) + .on_click({ + let connection_string = connection_string.clone(); + move |_, _, cx| { + callback(workspace.clone(), connection_string.clone(), cx); + } + }), + ) + }) + .child({ + fn remove_ssh_server( + remote_servers: Entity, + index: SshServerIndex, + connection_string: SharedString, + window: &mut Window, + cx: &mut App, + ) { + let prompt_message = format!("Remove server `{}`?", connection_string); + + let confirmation = window.prompt( + PromptLevel::Warning, + &prompt_message, + None, + &["Yes, remove it", "No, keep it"], + cx, + ); + + cx.spawn(async move |cx| { + if confirmation.await.ok() == Some(0) { + remote_servers + .update(cx, |this, cx| { + this.delete_ssh_server(index, cx); + }) + .ok(); + remote_servers + .update(cx, |this, cx| { + this.mode = Mode::default_mode(&this.ssh_config_servers, cx); + cx.notify(); + }) + .ok(); + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + div() + .id("ssh-options-copy-server-address") + .track_focus(&entries[2].focus_handle) + .on_action(cx.listener({ + let connection_string = connection_string.clone(); + move |_, _: &menu::Confirm, window, cx| { + remove_ssh_server( + cx.entity(), + index, + connection_string.clone(), + window, + cx, + ); + cx.focus_self(window); + } + })) + .child( + ListItem::new("remove-server") + .toggle_state(entries[2].focus_handle.contains_focused(window, cx)) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Trash).color(Color::Error)) + .child(Label::new("Remove Server").color(Color::Error)) + .on_click(cx.listener(move |_, _, window, cx| { + remove_ssh_server( + cx.entity(), + index, + connection_string.clone(), + window, + cx, + ); + cx.focus_self(window); + })), + ) + }) + } + fn render_edit_nickname( &self, state: &EditNicknameState, @@ -1366,7 +1970,7 @@ impl RemoteServerProjects { ) -> impl IntoElement { let Some(connection) = SshSettings::get_global(cx) .ssh_connections() - .nth(state.index) + .nth(state.index.0) else { return v_flex() .id("ssh-edit-nickname") @@ -1405,20 +2009,43 @@ impl RemoteServerProjects { let ssh_settings = SshSettings::get_global(cx); let mut should_rebuild = false; - if ssh_settings - .ssh_connections - .as_ref() - .is_some_and(|connections| { - state - .servers - .iter() - .filter_map(|server| match server { - RemoteEntry::Project { connection, .. } => Some(connection), - RemoteEntry::SshConfig { .. } => None, - }) - .ne(connections.iter()) - }) - { + let ssh_connections_changed = + ssh_settings + .ssh_connections + .as_ref() + .is_some_and(|connections| { + state + .servers + .iter() + .filter_map(|server| match server { + RemoteEntry::Project { + connection: Connection::Ssh(connection), + .. + } => Some(connection), + _ => None, + }) + .ne(connections.iter()) + }); + + let wsl_connections_changed = + ssh_settings + .wsl_connections + .as_ref() + .is_some_and(|connections| { + state + .servers + .iter() + .filter_map(|server| match server { + RemoteEntry::Project { + connection: Connection::Wsl(connection), + .. + } => Some(connection), + _ => None, + }) + .ne(connections.iter()) + }); + + if ssh_connections_changed || wsl_connections_changed { should_rebuild = true; }; @@ -1433,7 +2060,11 @@ impl RemoteServerProjects { .collect(); let mut expected_ssh_hosts = self.ssh_config_servers.clone(); for server in &state.servers { - if let RemoteEntry::Project { connection, .. } = server { + if let RemoteEntry::Project { + connection: Connection::Ssh(connection), + .. + } = server + { expected_ssh_hosts.remove(&connection.host); } } @@ -1477,14 +2108,47 @@ impl RemoteServerProjects { cx.notify(); })); + #[cfg(target_os = "windows")] + let wsl_connect_button = div() + .id("wsl-connect-new-server") + .track_focus(&state.add_new_wsl.focus_handle) + .anchor_scroll(state.add_new_wsl.scroll_anchor.clone()) + .child( + ListItem::new("wsl-add-new-server") + .toggle_state(state.add_new_wsl.focus_handle.contains_focused(window, cx)) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Plus).color(Color::Muted)) + .child(Label::new("Add WSL Distro")) + .on_click(cx.listener(|this, _, window, cx| { + let state = AddWslDistro::new(window, cx); + this.mode = Mode::AddWslDistro(state); + + cx.notify(); + })), + ) + .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| { + let state = AddWslDistro::new(window, cx); + this.mode = Mode::AddWslDistro(state); + + cx.notify(); + })); + + let modal_section = v_flex() + .track_focus(&self.focus_handle(cx)) + .id("ssh-server-list") + .overflow_y_scroll() + .track_scroll(&state.scroll_handle) + .size_full() + .child(connect_button); + + #[cfg(target_os = "windows")] + let modal_section = modal_section.child(wsl_connect_button); + #[cfg(not(target_os = "windows"))] + let modal_section = modal_section; + let mut modal_section = Navigable::new( - v_flex() - .track_focus(&self.focus_handle(cx)) - .id("ssh-server-list") - .overflow_y_scroll() - .track_scroll(&state.scroll_handle) - .size_full() - .child(connect_button) + modal_section .child( List::new() .empty_message( @@ -1504,7 +2168,8 @@ impl RemoteServerProjects { ) .into_any_element(), ) - .entry(state.add_new_server.clone()); + .entry(state.add_new_server.clone()) + .entry(state.add_new_wsl.clone()); for server in &state.servers { match server { @@ -1587,7 +2252,7 @@ impl RemoteServerProjects { &mut self, ssh_config_host: &SharedString, cx: &mut Context<'_, Self>, - ) -> usize { + ) -> SshServerIndex { let new_ix = Arc::new(AtomicUsize::new(0)); let update_new_ix = new_ix.clone(); @@ -1609,7 +2274,7 @@ impl RemoteServerProjects { cx, ); self.mode = Mode::default_mode(&self.ssh_config_servers, cx); - new_ix.load(atomic::Ordering::Acquire) + SshServerIndex(new_ix.load(atomic::Ordering::Acquire)) } } @@ -1719,6 +2384,10 @@ impl Render for RemoteServerProjects { Mode::EditNickname(state) => self .render_edit_nickname(state, window, cx) .into_any_element(), + #[cfg(target_os = "windows")] + Mode::AddWslDistro(state) => self + .render_add_wsl_distro(state, window, cx) + .into_any_element(), }) } } From 28800c2a3b5571348f67ffaabc5e9bcc0c17185b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 12:09:15 +0200 Subject: [PATCH 077/721] languages: Fix panic in python lsp adapters assuming settings shape (#38309) Fixes ZED-1EV Fixes ZED-S0 Fixes ZED-Q9 Release Notes: - N/A --- crates/languages/src/python.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index d989f4d6dc22d2e58752d6d635b62091d8bd63d6..e0273250c91af8f437bff435d4a0f2f0c6467951 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -461,7 +461,7 @@ impl LspAdapter for PyrightLspAdapter { pet_core::python_environment::PythonEnvironment, >(toolchain.as_json.clone()) { - if user_settings.is_null() { + if !user_settings.is_object() { user_settings = Value::Object(serde_json::Map::default()); } let object = user_settings.as_object_mut().unwrap(); @@ -492,9 +492,13 @@ impl LspAdapter for PyrightLspAdapter { // Get or create the python section let python = object .entry("python") - .or_insert(Value::Object(serde_json::Map::default())) - .as_object_mut() - .unwrap(); + .and_modify(|v| { + if !v.is_object() { + *v = Value::Object(serde_json::Map::default()); + } + }) + .or_insert(Value::Object(serde_json::Map::default())); + let python = python.as_object_mut().unwrap(); // Set both pythonPath and defaultInterpreterPath for compatibility python.insert( @@ -1465,7 +1469,7 @@ impl LspAdapter for PyLspAdapter { // If user did not explicitly modify their python venv, use one from picker. if let Some(toolchain) = toolchain { - if user_settings.is_null() { + if !user_settings.is_object() { user_settings = Value::Object(serde_json::Map::default()); } let object = user_settings.as_object_mut().unwrap(); @@ -1787,7 +1791,7 @@ impl LspAdapter for BasedPyrightLspAdapter { pet_core::python_environment::PythonEnvironment, >(toolchain.as_json.clone()) { - if user_settings.is_null() { + if !user_settings.is_object() { user_settings = Value::Object(serde_json::Map::default()); } let object = user_settings.as_object_mut().unwrap(); From d74b8bcf4c1716b935e6f527908b6d2630d693ef Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 17 Sep 2025 15:46:53 +0530 Subject: [PATCH 078/721] docs: Fix macOS development docs typo (#38311) Release Notes: - N/A --- docs/src/development/macos.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/development/macos.md b/docs/src/development/macos.md index 851e2efdd7cdf15b9617445fe065149da8a5721f..f3adf2e44b06647f07d5b2069f70e9d23e2856b0 100644 --- a/docs/src/development/macos.md +++ b/docs/src/development/macos.md @@ -118,8 +118,8 @@ cargo run This error seems to be caused by OS resource constraints. Installing and running tests with `cargo-nextest` should resolve the issue. -- `cargo install cargo-nexttest --locked` -- `cargo nexttest run --workspace --no-fail-fast` +- `cargo install cargo-nextest --locked` +- `cargo nextest run --workspace --no-fail-fast` ## Tips & Tricks From 5ca3b998f34d66dbd08c9ec23479a731c33d57f3 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 12:28:05 +0200 Subject: [PATCH 079/721] fs: Do panic when failing to query `modified` timestamps (#38312) Fixes ZED-1EW Release Notes: - N/A --- crates/fs/src/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index bd7c94c1d71dd64b5c6caec6f2ffaa4517ac2db7..198299617619363fa9d486042d1b803c3ede6f88 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -693,7 +693,7 @@ impl Fs for RealFs { Ok(Some(Metadata { inode, - mtime: MTime(metadata.modified().unwrap()), + mtime: MTime(metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH)), len: metadata.len(), is_symlink, is_dir: metadata.file_type().is_dir(), From 399118f461840b102ee93eb03224f44602b9814f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 12:38:49 +0200 Subject: [PATCH 080/721] denoise: Fix LICENSE-GPL symlink (#38313) Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/denoise/LICENSE-GPL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/denoise/LICENSE-GPL b/crates/denoise/LICENSE-GPL index e0f9dbd5d63fef1630c297edc4ceba4790be6f02..89e542f750cd3860a0598eff0dc34b56d7336dc4 120000 --- a/crates/denoise/LICENSE-GPL +++ b/crates/denoise/LICENSE-GPL @@ -1 +1 @@ -LICENSE-GPL \ No newline at end of file +../../LICENSE-GPL \ No newline at end of file From 574b943081c1645c0c0f2eed57f99d24e113d353 Mon Sep 17 00:00:00 2001 From: localcc Date: Wed, 17 Sep 2025 12:47:09 +0200 Subject: [PATCH 081/721] Add wsl specific icon (#38316) Release Notes: - N/A --- assets/icons/linux.svg | 11 +++++++++ crates/icons/src/icons.rs | 1 + crates/recent_projects/src/recent_projects.rs | 11 +++++---- .../recent_projects/src/remote_connections.rs | 24 ++++++++++++++----- crates/recent_projects/src/remote_servers.rs | 16 +++++++++++-- crates/title_bar/src/title_bar.rs | 13 +++++----- 6 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 assets/icons/linux.svg diff --git a/assets/icons/linux.svg b/assets/icons/linux.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc76742a3f236650cb8c514c8263ec2c3b2d4521 --- /dev/null +++ b/assets/icons/linux.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index f3609f7ea8706f33eb07eaaf456731e14c85555a..0f05e58c27c48c37043fe90f64b4f03968b22752 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -263,6 +263,7 @@ pub enum IconName { ZedPredictError, ZedPredictUp, ZedXCopilot, + Linux, } impl IconName { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index aa0ce7661b29123c25fdf20cbde5f53e6525d2d6..2fc57a52fcb55f62b213cd7bb842009384b6ec91 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -417,10 +417,13 @@ impl PickerDelegate for RecentProjectsDelegate { SerializedWorkspaceLocation::Local => Icon::new(IconName::Screen) .color(Color::Muted) .into_any_element(), - SerializedWorkspaceLocation::Remote(_) => { - Icon::new(IconName::Server) - .color(Color::Muted) - .into_any_element() + SerializedWorkspaceLocation::Remote(options) => { + Icon::new(match options { + RemoteConnectionOptions::Ssh { .. } => IconName::Server, + RemoteConnectionOptions::Wsl { .. } => IconName::Linux, + }) + .color(Color::Muted) + .into_any_element() } }) }) diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 72e2844d501f8f8860d62964d22430af80bab4b6..8b47cbfc0f031f6f7013d9f105dd496223a67cec 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -192,6 +192,7 @@ impl Settings for SshSettings { pub struct RemoteConnectionPrompt { connection_string: SharedString, nickname: Option, + is_wsl: bool, status_message: Option, prompt: Option<(Entity, oneshot::Sender)>, cancellation: Option>, @@ -216,12 +217,14 @@ impl RemoteConnectionPrompt { pub(crate) fn new( connection_string: String, nickname: Option, + is_wsl: bool, window: &mut Window, cx: &mut Context, ) -> Self { Self { connection_string: connection_string.into(), nickname: nickname.map(|nickname| nickname.into()), + is_wsl, editor: cx.new(|cx| Editor::single_line(window, cx)), status_message: None, cancellation: None, @@ -350,15 +353,16 @@ impl RemoteConnectionModal { window: &mut Window, cx: &mut Context, ) -> Self { - let (connection_string, nickname) = match connection_options { + let (connection_string, nickname, is_wsl) = match connection_options { RemoteConnectionOptions::Ssh(options) => { - (options.connection_string(), options.nickname.clone()) + (options.connection_string(), options.nickname.clone(), false) } - RemoteConnectionOptions::Wsl(options) => (options.distro_name.clone(), None), + RemoteConnectionOptions::Wsl(options) => (options.distro_name.clone(), None, true), }; Self { - prompt: cx - .new(|cx| RemoteConnectionPrompt::new(connection_string, nickname, window, cx)), + prompt: cx.new(|cx| { + RemoteConnectionPrompt::new(connection_string, nickname, is_wsl, window, cx) + }), finished: false, paths, } @@ -389,6 +393,7 @@ pub(crate) struct SshConnectionHeader { pub(crate) connection_string: SharedString, pub(crate) paths: Vec, pub(crate) nickname: Option, + pub(crate) is_wsl: bool, } impl RenderOnce for SshConnectionHeader { @@ -404,6 +409,11 @@ impl RenderOnce for SshConnectionHeader { (self.connection_string, None) }; + let icon = match self.is_wsl { + true => IconName::Linux, + false => IconName::Server, + }; + h_flex() .px(DynamicSpacing::Base12.rems(cx)) .pt(DynamicSpacing::Base08.rems(cx)) @@ -411,7 +421,7 @@ impl RenderOnce for SshConnectionHeader { .rounded_t_sm() .w_full() .gap_1p5() - .child(Icon::new(IconName::Server).size(IconSize::Small)) + .child(Icon::new(icon).size(IconSize::Small)) .child( h_flex() .gap_1() @@ -443,6 +453,7 @@ impl Render for RemoteConnectionModal { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { let nickname = self.prompt.read(cx).nickname.clone(); let connection_string = self.prompt.read(cx).connection_string.clone(); + let is_wsl = self.prompt.read(cx).is_wsl; let theme = cx.theme().clone(); let body_color = theme.colors().editor_background; @@ -461,6 +472,7 @@ impl Render for RemoteConnectionModal { paths: self.paths.clone(), connection_string, nickname, + is_wsl, } .render(window, cx), ) diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index d7e7505851a2b0cd2f86c807d6850937096dac7c..1ef9ab671f35dc477d1113e1b924c1272e13de2f 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -288,7 +288,7 @@ impl picker::PickerDelegate for WslPickerDelegate { h_flex() .flex_grow() .gap_3() - .child(Icon::new(IconName::Server)) + .child(Icon::new(IconName::Linux)) .child(v_flex().child(HighlightedLabel::new( matched.string.clone(), matched.positions.clone(), @@ -483,12 +483,14 @@ impl gpui::Render for ProjectPicker { connection_string: connection_string.clone(), paths: Default::default(), nickname: nickname.clone(), + is_wsl: false, } .render(window, cx), ProjectPickerData::Wsl { distro_name } => SshConnectionHeader { connection_string: distro_name.clone(), paths: Default::default(), nickname: None, + is_wsl: true, } .render(window, cx), }) @@ -799,6 +801,7 @@ impl RemoteServerProjects { RemoteConnectionPrompt::new( connection_options.connection_string(), connection_options.nickname.clone(), + false, window, cx, ) @@ -870,7 +873,13 @@ impl RemoteServerProjects { }; let prompt = cx.new(|cx| { - RemoteConnectionPrompt::new(connection_options.distro_name.clone(), None, window, cx) + RemoteConnectionPrompt::new( + connection_options.distro_name.clone(), + None, + true, + window, + cx, + ) }); let connection = connect( ConnectionIdentifier::setup(), @@ -1644,6 +1653,7 @@ impl RemoteServerProjects { connection_string: connection.host.clone().into(), paths: Default::default(), nickname: connection.nickname.clone().map(|s| s.into()), + is_wsl: false, } .render(window, cx) .into_any_element(), @@ -1651,6 +1661,7 @@ impl RemoteServerProjects { connection_string: connection.distro_name.clone().into(), paths: Default::default(), nickname: None, + is_wsl: true, } .render(window, cx) .into_any_element(), @@ -1988,6 +1999,7 @@ impl RemoteServerProjects { connection_string, paths: Default::default(), nickname, + is_wsl: false, } .render(window, cx), ) diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 129b5645641a01ba22d6993621b92a17664f5c8a..9f00b0ffeffe6b9744ffa67a0f52795e31e5737f 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -349,10 +349,11 @@ impl TitleBar { let options = self.project.read(cx).remote_connection_options(cx)?; let host: SharedString = options.display_name().into(); - let nickname = if let RemoteConnectionOptions::Ssh(options) = options { - options.nickname.map(|nick| nick.into()) - } else { - None + let (nickname, icon) = match options { + RemoteConnectionOptions::Ssh(options) => { + (options.nickname.map(|nick| nick.into()), IconName::Server) + } + RemoteConnectionOptions::Wsl(_) => (None, IconName::Linux), }; let nickname = nickname.unwrap_or_else(|| host.clone()); @@ -390,9 +391,7 @@ impl TitleBar { .max_w_32() .child( IconWithIndicator::new( - Icon::new(IconName::Server) - .size(IconSize::Small) - .color(icon_color), + Icon::new(icon).size(IconSize::Small).color(icon_color), Some(Indicator::dot().color(indicator_color)), ) .indicator_border_color(Some(cx.theme().colors().title_bar_background)) From a5c29176a360dd410e68bc1f0810687838e86c65 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 14:02:39 +0200 Subject: [PATCH 082/721] editor: Fix incorrect offset passed to acp completion provider (#38321) Might fix | ZED-15G Release Notes: - N/A *or* Added/Fixed/Improved ... --- .../src/context_picker/completion_provider.rs | 19 ++++++++------- crates/editor/src/editor.rs | 23 ++++++++++++------- crates/text/src/text.rs | 2 +- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index c9cd69bf8e49b2e4f20148640cd029caea51264f..01a7a51316eee4709eaf9c17c8840e3cd637a62b 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -743,15 +743,15 @@ impl CompletionProvider for ContextPickerCompletionProvider { _window: &mut Window, cx: &mut Context, ) -> Task>> { - let state = buffer.update(cx, |buffer, _cx| { - let position = buffer_position.to_point(buffer); - let line_start = Point::new(position.row, 0); - let offset_to_line = buffer.point_to_offset(line_start); - let mut lines = buffer.text_for_range(line_start..position).lines(); - let line = lines.next()?; - MentionCompletion::try_parse(line, offset_to_line) - }); - let Some(state) = state else { + let snapshot = buffer.read(cx).snapshot(); + let position = buffer_position.to_point(&snapshot); + let line_start = Point::new(position.row, 0); + let offset_to_line = snapshot.point_to_offset(line_start); + let mut lines = snapshot.text_for_range(line_start..position).lines(); + let Some(line) = lines.next() else { + return Task::ready(Ok(Vec::new())); + }; + let Some(state) = MentionCompletion::try_parse(line, offset_to_line) else { return Task::ready(Ok(Vec::new())); }; @@ -761,7 +761,6 @@ impl CompletionProvider for ContextPickerCompletionProvider { return Task::ready(Ok(Vec::new())); }; - let snapshot = buffer.read(cx).snapshot(); let source_range = snapshot.anchor_before(state.source_range.start) ..snapshot.anchor_after(state.source_range.end); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 49a09385d521e2b79c01acc4b2ff2ad9db3be936..bdf1ae16474c647abd7ecb0593fb272b37ef9c54 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5473,16 +5473,18 @@ impl Editor { if position.diff_base_anchor.is_some() { return; } - let (buffer, buffer_position) = - if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) { - output - } else { - return; - }; + let buffer_position = multibuffer_snapshot.anchor_before(position); + let Some(buffer) = buffer_position + .buffer_id + .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)) + else { + return; + }; let buffer_snapshot = buffer.read(cx).snapshot(); let query: Option> = - Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into()); + Self::completion_query(&multibuffer_snapshot, buffer_position) + .map(|query| query.into()); drop(multibuffer_snapshot); @@ -5568,6 +5570,11 @@ impl Editor { } }; + let Anchor { + excerpt_id: buffer_excerpt_id, + text_anchor: buffer_position, + .. + } = buffer_position; let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) = buffer_snapshot.surrounding_word(buffer_position, false) { @@ -5623,7 +5630,7 @@ impl Editor { let (mut words, provider_responses) = match &provider { Some(provider) => { let provider_responses = provider.completions( - position.excerpt_id, + buffer_excerpt_id, &buffer, buffer_position, completion_context, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index db282e5c30de562441f5076157a8db4a269aea9d..590c30c8a73c13180e4d09dda1b3a071ef46ad7f 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -3078,7 +3078,7 @@ impl ToOffset for usize { fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { assert!( *self <= snapshot.len(), - "offset {} is out of range, max allowed is {}", + "offset {} is out of range, snapshot length is {}", self, snapshot.len() ); From 86834887da048b504373acc184135011aa3390b2 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 17 Sep 2025 18:13:57 +0530 Subject: [PATCH 083/721] editor: Fix completions menu flashes on every keystroke in TSX files with emmet (#38320) Closes https://github.com/zed-industries/zed/issues/37774 Bug in https://github.com/zed-industries/zed/pull/32927 Instead of using trigger characters to clear cached completions items, now we check if the query is empty to clear it. Turns out Emmet defines whole [alphanumeric as trigger characters](https://github.com/olrtg/emmet-language-server/blob/279be108725fb391c167690b697ce154fd32657b/index.ts#L116) which causes flickering. Clear on trigger characters was introduced to get rid of cached completions like in the case of "Parent.Foo.Bar", where "." is one of the trigger characters. This works still since "." is not part of `completion_query_characters` and hence we use it as a boundary while building the current query. i.e in this case, the query would be empty after typing ".", clearing cached completions. Release Notes: - Fixed issue where completions menu flashed on every keystroke in TSX files with emmet extension installed. --- crates/editor/src/editor.rs | 61 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdf1ae16474c647abd7ecb0593fb272b37ef9c54..999e66efdcf7a39774cb29a6dba28473cd84fa81 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5488,6 +5488,18 @@ impl Editor { drop(multibuffer_snapshot); + // Hide the current completions menu when query is empty. Without this, cached + // completions from before the trigger char may be reused (#32774). + if query.is_none() { + let menu_is_open = matches!( + self.context_menu.borrow().as_ref(), + Some(CodeContextMenu::Completions(_)) + ); + if menu_is_open { + self.hide_context_menu(window, cx); + } + } + let mut ignore_word_threshold = false; let provider = match requested_source { Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(), @@ -5509,37 +5521,6 @@ impl Editor { .as_ref() .is_none_or(|provider| provider.filter_completions()); - let trigger_kind = match trigger { - Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => { - CompletionTriggerKind::TRIGGER_CHARACTER - } - _ => CompletionTriggerKind::INVOKED, - }; - let completion_context = CompletionContext { - trigger_character: trigger.and_then(|trigger| { - if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER { - Some(String::from(trigger)) - } else { - None - } - }), - trigger_kind, - }; - - // Hide the current completions menu when a trigger char is typed. Without this, cached - // completions from before the trigger char may be reused (#32774). Snippet choices could - // involve trigger chars, so this is skipped in that case. - if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty() - { - let menu_is_open = matches!( - self.context_menu.borrow().as_ref(), - Some(CodeContextMenu::Completions(_)) - ); - if menu_is_open { - self.hide_context_menu(window, cx); - } - } - if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() { if filter_completions { menu.filter(query.clone(), provider.clone(), window, cx); @@ -5570,11 +5551,29 @@ impl Editor { } }; + let trigger_kind = match trigger { + Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => { + CompletionTriggerKind::TRIGGER_CHARACTER + } + _ => CompletionTriggerKind::INVOKED, + }; + let completion_context = CompletionContext { + trigger_character: trigger.and_then(|trigger| { + if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER { + Some(String::from(trigger)) + } else { + None + } + }), + trigger_kind, + }; + let Anchor { excerpt_id: buffer_excerpt_id, text_anchor: buffer_position, .. } = buffer_position; + let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) = buffer_snapshot.surrounding_word(buffer_position, false) { From c54e294965df8e05b76773f04e879e68a0cc3687 Mon Sep 17 00:00:00 2001 From: itsaphel Date: Wed, 17 Sep 2025 14:08:29 +0100 Subject: [PATCH 084/721] Autosave files on close, when setting is `afterDelay` (#36929) Closes https://github.com/zed-industries/zed/issues/12149 Closes #35524 Release Notes: - Improved autosave behavior, to prevent a confirmation dialog when quickly closing files and using the `afterDelay` setting --------- Co-authored-by: MrSubidubi --- crates/search/src/project_search.rs | 8 +---- crates/workspace/src/pane.rs | 6 ++-- crates/workspace/src/workspace.rs | 36 ++++++++++++++++++++-- crates/workspace/src/workspace_settings.rs | 11 +++++++ docs/src/configuring-zed.md | 2 ++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index eaad5dad65b75c1fffcabb400eb6a2dea1fb1811..33ccd095687c448abc5d8b685da22e89ab59cbc8 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1071,18 +1071,12 @@ impl ProjectSearchView { window: &mut Window, cx: &mut Context, ) -> Task> { - use workspace::AutosaveSetting; - let project = self.entity.read(cx).project.clone(); let can_autosave = self.results_editor.can_autosave(cx); let autosave_setting = self.results_editor.workspace_settings(cx).autosave; - let will_autosave = can_autosave - && matches!( - autosave_setting, - AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ); + let will_autosave = can_autosave && autosave_setting.should_save_on_close(); let is_dirty = self.is_dirty(cx); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4ce8a890237f97db5596f55f607a603a2695ab7b..0418be7a2fbc858c8623400e65fed0f96e2cdb61 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2082,10 +2082,8 @@ impl Pane { } else if is_dirty && (can_save || can_save_as) { if save_intent == SaveIntent::Close { let will_autosave = cx.update(|_window, cx| { - matches!( - item.workspace_settings(cx).autosave, - AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ) && item.can_autosave(cx) + item.can_autosave(cx) + && item.workspace_settings(cx).autosave.should_save_on_close() })?; if !will_autosave { let item_id = item.item_id(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 23d611964c3cd1b0284521c9444b6952748a5db9..5772695310e1258bee2a62953ad4bcc3620cd4ee 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -8737,6 +8737,36 @@ mod tests { cx.executor().advance_clock(Duration::from_millis(250)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + // Autosave after delay, should save earlier than delay if tab is closed + item.update(cx, |item, cx| { + item.is_dirty = true; + cx.emit(ItemEvent::Edit); + }); + cx.executor().advance_clock(Duration::from_millis(250)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + + // // Ensure auto save with delay saves the item on close, even if the timer hasn't yet run out. + pane.update_in(cx, |pane, window, cx| { + pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id) + }) + .await + .unwrap(); + assert!(!cx.has_pending_prompt()); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + + // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. + workspace.update_in(cx, |workspace, window, cx| { + workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx); + }); + item.update_in(cx, |item, _window, cx| { + item.is_dirty = true; + for project_item in &mut item.project_items { + project_item.update(cx, |project_item, _| project_item.is_dirty = true); + } + }); + cx.run_until_parked(); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { @@ -8756,7 +8786,7 @@ mod tests { .await .unwrap(); assert!(!cx.has_pending_prompt()); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. workspace.update_in(cx, |workspace, window, cx| { @@ -8770,7 +8800,7 @@ mod tests { window.blur(); }); cx.run_until_parked(); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); // Ensure autosave is prevented for deleted files also when closing the buffer. let _close_items = pane.update_in(cx, |pane, window, cx| { @@ -8778,7 +8808,7 @@ mod tests { }); cx.run_until_parked(); assert!(cx.has_pending_prompt()); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); } #[gpui::test] diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 8868f3190575ac4b861e0619732890f477d83b69..a86f81f442b0437494d5cc2915a0a0b7d15ca7f3 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -254,6 +254,17 @@ pub enum AutosaveSetting { OnWindowChange, } +impl AutosaveSetting { + pub fn should_save_on_close(&self) -> bool { + matches!( + &self, + AutosaveSetting::OnFocusChange + | AutosaveSetting::OnWindowChange + | AutosaveSetting::AfterDelay { .. } + ) + } +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum PaneSplitDirectionHorizontal { diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index a7b89dc5207e0acea422401b0ce77946c7d484c6..6c25d62e291f91f3faf4dc77e5a6dab3b8637ca8 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -246,6 +246,8 @@ Define extensions which should be installed (`true`) or never installed (`false` } ``` +Note that a save will be triggered when an unsaved tab is closed, even if this is earlier than the configured inactivity period. + ## Autoscroll on Clicks - Description: Whether to scroll when clicking near the edge of the visible text area. From 405a8eaf78e2cac41b5b13aff6befcda7ef5bd02 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 15:47:00 +0200 Subject: [PATCH 085/721] editor: Fix `BlockMapWriter::blocks_intersecting_buffer_range` creating invalid indexing ranges (#38325) Fixes ZED-113 Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/display_map/block_map.rs | 30 +++++++++------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 03d04e7010248293604d10c2f3e553430e74c9c6..2d16e6af8b469ff6e94b1b9fc7d11f7186e7b3c3 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1264,36 +1264,30 @@ impl BlockMapWriter<'_> { range: Range, inclusive: bool, ) -> &[Arc] { + if range.is_empty() && !inclusive { + return &[]; + } let wrap_snapshot = self.0.wrap_snapshot.borrow(); let buffer = wrap_snapshot.buffer_snapshot(); let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| { let block_end = block.end().to_offset(buffer); - block_end.cmp(&range.start).then_with(|| { - if inclusive || (range.is_empty() && block.start().to_offset(buffer) == block_end) { - Ordering::Greater - } else { - Ordering::Less - } - }) + block_end.cmp(&range.start).then(Ordering::Greater) }) { Ok(ix) | Err(ix) => ix, }; - let end_block_ix = match self.0.custom_blocks.binary_search_by(|block| { - block - .start() - .to_offset(buffer) - .cmp(&range.end) - .then(if inclusive { - Ordering::Less - } else { - Ordering::Greater - }) + let end_block_ix = match self.0.custom_blocks[start_block_ix..].binary_search_by(|block| { + let block_start = block.start().to_offset(buffer); + block_start.cmp(&range.end).then(if inclusive { + Ordering::Less + } else { + Ordering::Greater + }) }) { Ok(ix) | Err(ix) => ix, }; - &self.0.custom_blocks[start_block_ix..end_block_ix] + &self.0.custom_blocks[start_block_ix..][..end_block_ix] } } From 0f6dd84c98bff0c486a6957091c8097dfa493fee Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 17 Sep 2025 10:45:25 -0400 Subject: [PATCH 086/721] Bump Zed to v0.206 (#38327) Release Notes: -N/A --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee14efd2d800aeabb29a75959f9a929d06b78f81..cdf4109334af2e6f98a028970d124c7b9221371d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21105,7 +21105,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.205.0" +version = "0.206.0" dependencies = [ "acp_tools", "activity_indicator", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e53dedafd2f27699645662155f154f1220c6cd85..1c5dc3ddddb11279ae0d79717aadc18f51350675 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -2,7 +2,7 @@ description = "The fast, collaborative code editor." edition.workspace = true name = "zed" -version = "0.205.0" +version = "0.206.0" publish.workspace = true license = "GPL-3.0-or-later" authors = ["Zed Team "] From f3b8c619e357c658e2ec42b054582eef0741d82a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 17 Sep 2025 17:10:05 +0200 Subject: [PATCH 087/721] editor: Fix `unwrap_syntax_node` panicking by not setting selections (#38329) Fixes ZED-11T Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/agent_ui/src/text_thread_editor.rs | 4 +- crates/editor/src/editor.rs | 90 +++++++++++-------- crates/editor/src/element.rs | 12 ++- crates/editor/src/items.rs | 8 +- crates/editor/src/jsx_tag_auto_close.rs | 2 +- crates/editor/src/lsp_ext.rs | 2 +- crates/editor/src/mouse_context_menu.rs | 7 +- crates/editor/src/rust_analyzer_ext.rs | 6 +- crates/editor/src/selections_collection.rs | 42 +++++---- crates/editor/src/test/editor_test_context.rs | 2 +- crates/git_ui/src/text_diff_view.rs | 2 +- crates/vim/src/command.rs | 4 +- crates/vim/src/normal/mark.rs | 2 +- crates/vim/src/surrounds.rs | 2 +- crates/vim/src/vim.rs | 10 +-- crates/vim/src/visual.rs | 2 +- 16 files changed, 112 insertions(+), 85 deletions(-) diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index d979db5e0468b696d32ed755aec1ef47e2fd3df3..df904b2416cc9b66371a3557b04ff97246f25a41 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -477,7 +477,7 @@ impl TextThreadEditor { return; } - let selections = self.editor.read(cx).selections.disjoint_anchors(); + let selections = self.editor.read(cx).selections.disjoint_anchors_arc(); let mut commands_by_range = HashMap::default(); let workspace = self.workspace.clone(); self.context.update(cx, |context, cx| { @@ -1823,7 +1823,7 @@ impl TextThreadEditor { fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context) { self.context.update(cx, |context, cx| { - let selections = self.editor.read(cx).selections.disjoint_anchors(); + let selections = self.editor.read(cx).selections.disjoint_anchors_arc(); for selection in selections.as_ref() { let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx); let range = selection diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 999e66efdcf7a39774cb29a6dba28473cd84fa81..8492ba60e7d302ab82509a3ece849ce59a6c2a2b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2414,14 +2414,10 @@ impl Editor { pub fn is_range_selected(&mut self, range: &Range, cx: &mut Context) -> bool { if self .selections - .pending - .as_ref() + .pending_anchor() .is_some_and(|pending_selection| { let snapshot = self.buffer().read(cx).snapshot(cx); - pending_selection - .selection - .range() - .includes(range, &snapshot) + pending_selection.range().includes(range, &snapshot) }) { return true; @@ -3054,7 +3050,7 @@ impl Editor { } } - let selection_anchors = self.selections.disjoint_anchors(); + let selection_anchors = self.selections.disjoint_anchors_arc(); if self.focus_handle.is_focused(window) && self.leader_id.is_none() { self.buffer.update(cx, |buffer, cx| { @@ -3170,7 +3166,7 @@ impl Editor { self.blink_manager.update(cx, BlinkManager::pause_blinking); cx.emit(EditorEvent::SelectionsChanged { local }); - let selections = &self.selections.disjoint; + let selections = &self.selections.disjoint_anchors_arc(); if selections.len() == 1 { cx.emit(SearchEvent::ActiveMatchChanged) } @@ -3282,14 +3278,14 @@ impl Editor { other: Entity, cx: &mut Context, ) -> gpui::Subscription { - let other_selections = other.read(cx).selections.disjoint.to_vec(); + let other_selections = other.read(cx).selections.disjoint_anchors().to_vec(); self.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(); + let other_selections = other.read(cx).selections.disjoint_anchors().to_vec(); if other_selections.is_empty() { return; } @@ -3301,7 +3297,7 @@ impl Editor { let this_subscription = cx.subscribe_self::(move |this, this_evt, cx| { if let EditorEvent::SelectionsChanged { local: true } = this_evt { - let these_selections = this.selections.disjoint.to_vec(); + let these_selections = this.selections.disjoint_anchors().to_vec(); if these_selections.is_empty() { return; } @@ -3339,7 +3335,7 @@ impl Editor { effects, old_cursor_position: self.selections.newest_anchor().head(), history_entry: SelectionHistoryEntry { - selections: self.selections.disjoint_anchors(), + selections: self.selections.disjoint_anchors_arc(), select_next_state: self.select_next_state.clone(), select_prev_state: self.select_prev_state.clone(), add_selections_state: self.add_selections_state.clone(), @@ -3499,6 +3495,7 @@ impl Editor { let mut pending_selection = self .selections .pending_anchor() + .cloned() .expect("extend_selection not called with pending selection"); if position >= tail { pending_selection.start = tail_anchor; @@ -3520,7 +3517,7 @@ impl Editor { }; self.change_selections(effects, window, cx, |s| { - s.set_pending(pending_selection, pending_mode) + s.set_pending(pending_selection.clone(), pending_mode) }); } @@ -3595,7 +3592,7 @@ impl Editor { Some(selected_points[0].id) } else { let clicked_point_already_selected = - self.selections.disjoint.iter().find(|selection| { + self.selections.disjoint_anchors().iter().find(|selection| { selection.start.to_point(buffer) == start.to_point(buffer) || selection.end.to_point(buffer) == end.to_point(buffer) }); @@ -3700,7 +3697,7 @@ impl Editor { if self.columnar_selection_state.is_some() { self.select_columns(position, goal_column, &display_map, window, cx); - } else if let Some(mut pending) = self.selections.pending_anchor() { + } else if let Some(mut pending) = self.selections.pending_anchor().cloned() { let buffer = &display_map.buffer_snapshot; let head; let tail; @@ -3776,7 +3773,7 @@ impl Editor { } self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.set_pending(pending, mode); + s.set_pending(pending.clone(), mode); }); } else { log::error!("update_selection dispatched with no pending selection"); @@ -3885,7 +3882,8 @@ impl Editor { }; pending_nonempty_selection - || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1) + || (self.columnar_selection_state.is_some() + && self.selections.disjoint_anchors().len() > 1) } pub fn has_pending_selection(&self) -> bool { @@ -6065,7 +6063,7 @@ impl Editor { editor.refresh_edit_prediction(true, false, window, cx); }); - self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot); + self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot); let show_new_completions_on_confirm = completion .confirm @@ -7472,7 +7470,7 @@ impl Editor { s.select_anchor_ranges([last_edit_end..last_edit_end]); }); - let selections = self.selections.disjoint_anchors(); + let selections = self.selections.disjoint_anchors_arc(); if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) { let has_new_transaction = transaction_id_prev != Some(transaction_id_now); if has_new_transaction { @@ -7717,7 +7715,7 @@ impl Editor { let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else { return; }; - if self.selections.pending.is_none() { + if self.selections.pending_anchor().is_none() { return; } @@ -10517,7 +10515,7 @@ impl Editor { fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool { let snapshot = self.buffer.read(cx).snapshot(cx); - for selection in self.selections.disjoint_anchors().iter() { + for selection in self.selections.disjoint_anchors_arc().iter() { if snapshot .language_at(selection.start) .and_then(|lang| lang.config().wrap_characters.as_ref()) @@ -10851,7 +10849,7 @@ impl Editor { let snapshot = self.snapshot(window, cx); let cursors = self .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|selection| { let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot); @@ -14542,7 +14540,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) -> Result<()> { - let selections = self.selections.disjoint_anchors(); + let selections = self.selections.disjoint_anchors_arc(); match selections.first() { Some(first) if selections.len() >= 2 => { self.change_selections(Default::default(), window, cx, |s| { @@ -14566,7 +14564,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) -> Result<()> { - let selections = self.selections.disjoint_anchors(); + let selections = self.selections.disjoint_anchors_arc(); match selections.last() { Some(last) if selections.len() >= 2 => { self.change_selections(Default::default(), window, cx, |s| { @@ -15123,11 +15121,9 @@ impl Editor { let full_edits = selections .into_iter() .filter_map(|selection| { - // Only requires two branches once if-let-chains stabilize (#53667) - let child = if !selection.is_empty() { - selection.range() - } else if let Some((_, ancestor_range)) = - buffer.syntax_ancestor(selection.start..selection.end) + let child = if selection.is_empty() + && let Some((_, ancestor_range)) = + buffer.syntax_ancestor(selection.start..selection.end) { match ancestor_range { MultiOrSingleBufferOffsetRange::Single(range) => range, @@ -15155,6 +15151,9 @@ impl Editor { Some((selection.id, parent, text)) }) .collect::>(); + if full_edits.is_empty() { + return; + } self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |buffer, cx| { @@ -15663,7 +15662,7 @@ impl Editor { cx: &mut Context, ) { - let selections = self.selections.disjoint_anchors(); + let selections = self.selections.disjoint_anchors_arc(); let lines = if lines == 0 { EditorSettings::get_global(cx).expand_excerpt_lines @@ -17122,7 +17121,7 @@ impl Editor { .transaction(transaction_id_prev) .map(|t| t.0.clone()) }) - .unwrap_or_else(|| self.selections.disjoint_anchors()); + .unwrap_or_else(|| self.selections.disjoint_anchors_arc()); let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse(); let format = project.update(cx, |project, cx| { @@ -17662,7 +17661,7 @@ impl Editor { .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) { self.selection_history - .insert_transaction(tx_id, self.selections.disjoint_anchors()); + .insert_transaction(tx_id, self.selections.disjoint_anchors_arc()); cx.emit(EditorEvent::TransactionBegun { transaction_id: tx_id, }); @@ -17684,7 +17683,7 @@ impl Editor { if let Some((_, end_selections)) = self.selection_history.transaction_mut(transaction_id) { - *end_selections = Some(self.selections.disjoint_anchors()); + *end_selections = Some(self.selections.disjoint_anchors_arc()); } else { log::error!("unexpectedly ended a transaction that wasn't started by this editor"); } @@ -18354,7 +18353,12 @@ impl Editor { _window: &mut Window, cx: &mut Context, ) { - let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect(); + let ranges: Vec<_> = self + .selections + .disjoint_anchors() + .iter() + .map(|s| s.range()) + .collect(); self.toggle_diff_hunks_in_ranges(ranges, cx); } @@ -18392,7 +18396,12 @@ impl Editor { cx: &mut Context, ) { let snapshot = self.buffer.read(cx).snapshot(cx); - let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect(); + let ranges: Vec<_> = self + .selections + .disjoint_anchors() + .iter() + .map(|s| s.range()) + .collect(); let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot); self.stage_or_unstage_diff_hunks(stage, ranges, cx); } @@ -18556,7 +18565,12 @@ impl Editor { } pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context) { - let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect(); + let ranges: Vec<_> = self + .selections + .disjoint_anchors() + .iter() + .map(|s| s.range()) + .collect(); self.buffer .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx)) } @@ -21276,7 +21290,7 @@ impl Editor { buffer.finalize_last_transaction(cx); if self.leader_id.is_none() { buffer.set_active_selections( - &self.selections.disjoint_anchors(), + &self.selections.disjoint_anchors_arc(), self.selections.line_mode, self.cursor_shape, cx, @@ -23572,7 +23586,7 @@ impl EntityInputHandler for Editor { let marked_ranges = { let snapshot = this.buffer.read(cx).read(cx); this.selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|selection| { selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 230c3160fb616984f71ce12b7b3b6b8d459d6356..2212507e38b2e577eed1cf140eea362425152623 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1060,7 +1060,7 @@ impl EditorElement { ); if mouse_down_time.elapsed() >= drag_and_drop_delay { let drop_cursor = Selection { - id: post_inc(&mut editor.selections.next_selection_id), + id: post_inc(&mut editor.selections.next_selection_id()), start: drop_anchor, end: drop_anchor, reversed: false, @@ -1547,9 +1547,13 @@ impl EditorElement { // Local cursors if !skip_local { let color = cx.theme().players().local().cursor; - editor.selections.disjoint.iter().for_each(|selection| { - add_cursor(selection.head(), color); - }); + editor + .selections + .disjoint_anchors() + .iter() + .for_each(|selection| { + add_cursor(selection.head(), color); + }); if let Some(ref selection) = editor.selections.pending_anchor() { add_cursor(selection.head(), color); } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 253d0c27518107dc1cad3733cefbfef5bc12b807..bf21d6b461e6fdc082fdd1431f13b8daae730824 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -187,7 +187,7 @@ impl FollowableItem for Editor { } else if self.focus_handle.is_focused(window) { self.buffer.update(cx, |buffer, cx| { buffer.set_active_selections( - &self.selections.disjoint_anchors(), + &self.selections.disjoint_anchors_arc(), self.selections.line_mode, self.cursor_shape, cx, @@ -231,7 +231,7 @@ impl FollowableItem for Editor { scroll_y: scroll_anchor.offset.y, selections: self .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|s| serialize_selection(s, &snapshot)) .collect(), @@ -310,7 +310,7 @@ impl FollowableItem for Editor { let snapshot = self.buffer.read(cx).snapshot(cx); update.selections = self .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|s| serialize_selection(s, &snapshot)) .collect(); @@ -1675,7 +1675,7 @@ impl SearchableItem for Editor { cx: &mut Context, ) -> usize { let buffer = self.buffer().read(cx).snapshot(cx); - let current_index_position = if self.selections.disjoint_anchors().len() == 1 { + let current_index_position = if self.selections.disjoint_anchors_arc().len() == 1 { self.selections.newest_anchor().head() } else { matches[current_index].start diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index e6c518beae3ecf3741b5f74be6087628f5231c8c..c1c6ccf2f549042a3defc84d8628f7f614244d44 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -507,7 +507,7 @@ pub(crate) fn handle_from( { let selections = this - .read_with(cx, |this, _| this.selections.disjoint_anchors()) + .read_with(cx, |this, _| this.selections.disjoint_anchors_arc()) .ok()?; for selection in selections.iter() { let Some(selection_buffer_offset_head) = diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index 18ad2d71c835e5ec7e3bbd540de21f7e38425c39..0c4760f5684acf450b793a1deac54be983dcafd0 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -35,7 +35,7 @@ where let project = editor.project.clone()?; editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .filter_map(|selection| Some((selection.head(), selection.head().buffer_id?))) .unique_by(|(_, buffer_id)| *buffer_id) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 3bc334c54c2f58e6dda2b404039369907c275422..78b12945afd1c2fcd359181afb030fc235c60a18 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -130,12 +130,9 @@ fn display_ranges<'a>( display_map: &'a DisplaySnapshot, selections: &'a SelectionsCollection, ) -> impl Iterator> + 'a { - let pending = selections - .pending - .as_ref() - .map(|pending| &pending.selection); + let pending = selections.pending_anchor(); selections - .disjoint + .disjoint_anchors() .iter() .chain(pending) .map(move |s| s.start.to_display_point(display_map)..s.end.to_display_point(display_map)) diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index f4059ca03d2ad70106aa958b4fe0c545cb4988ea..ffa0c017c0eb157df776cc49e0dba51e617e3379 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -319,7 +319,7 @@ fn cancel_flycheck_action( }; let buffer_id = editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .find_map(|selection| { let buffer_id = selection.start.buffer_id.or(selection.end.buffer_id)?; @@ -344,7 +344,7 @@ fn run_flycheck_action( }; let buffer_id = editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .find_map(|selection| { let buffer_id = selection.start.buffer_id.or(selection.end.buffer_id)?; @@ -369,7 +369,7 @@ fn clear_flycheck_action( }; let buffer_id = editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .find_map(|selection| { let buffer_id = selection.start.buffer_id.or(selection.end.buffer_id)?; diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 0a02390b641e1020aff8d9cf0167b44485baf489..e562be10e92344c1c892878ab674cba39beb74c2 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -28,13 +28,13 @@ pub struct PendingSelection { pub struct SelectionsCollection { display_map: Entity, buffer: Entity, - pub next_selection_id: usize, + next_selection_id: usize, pub line_mode: bool, /// The non-pending, non-overlapping selections. /// The [SelectionsCollection::pending] selection could possibly overlap these - pub disjoint: Arc<[Selection]>, + disjoint: Arc<[Selection]>, /// A pending selection, such as when the mouse is being dragged - pub pending: Option, + pending: Option, } impl SelectionsCollection { @@ -84,20 +84,27 @@ impl SelectionsCollection { /// The non-pending, non-overlapping selections. There could be a pending selection that /// overlaps these if the mouse is being dragged, etc. This could also be empty if there is a /// pending selection. Returned as selections over Anchors. - pub fn disjoint_anchors(&self) -> Arc<[Selection]> { + pub fn disjoint_anchors_arc(&self) -> Arc<[Selection]> { self.disjoint.clone() } + /// The non-pending, non-overlapping selections. There could be a pending selection that + /// overlaps these if the mouse is being dragged, etc. This could also be empty if there is a + /// pending selection. Returned as selections over Anchors. + pub fn disjoint_anchors(&self) -> &[Selection] { + &self.disjoint + } + pub fn disjoint_anchor_ranges(&self) -> impl Iterator> { // Mapping the Arc slice would borrow it, whereas indexing captures it. - let disjoint = self.disjoint_anchors(); + let disjoint = self.disjoint_anchors_arc(); (0..disjoint.len()).map(move |ix| disjoint[ix].range()) } /// Non-overlapping selections using anchors, including the pending selection. pub fn all_anchors(&self, cx: &mut App) -> Arc<[Selection]> { if self.pending.is_none() { - self.disjoint_anchors() + self.disjoint_anchors_arc() } else { let all_offset_selections = self.all::(cx); let buffer = self.buffer(cx); @@ -108,10 +115,12 @@ impl SelectionsCollection { } } - pub fn pending_anchor(&self) -> Option> { - self.pending - .as_ref() - .map(|pending| pending.selection.clone()) + pub fn pending_anchor(&self) -> Option<&Selection> { + self.pending.as_ref().map(|pending| &pending.selection) + } + + pub fn pending_anchor_mut(&mut self) -> Option<&mut Selection> { + self.pending.as_mut().map(|pending| &mut pending.selection) } pub fn pending>( @@ -120,7 +129,7 @@ impl SelectionsCollection { ) -> Option> { let map = self.display_map(cx); - resolve_selections(self.pending_anchor().as_ref(), &map).next() + resolve_selections(self.pending_anchor(), &map).next() } pub(crate) fn pending_mode(&self) -> Option { @@ -234,8 +243,7 @@ impl SelectionsCollection { let map = self.display_map(cx); let disjoint_anchors = &self.disjoint; let mut disjoint = resolve_selections_display(disjoint_anchors.iter(), &map).peekable(); - let mut pending_opt = - resolve_selections_display(self.pending_anchor().as_ref(), &map).next(); + let mut pending_opt = resolve_selections_display(self.pending_anchor(), &map).next(); let selections = iter::from_fn(move || { if let Some(pending) = pending_opt.as_mut() { while let Some(next_selection) = disjoint.peek() { @@ -343,9 +351,9 @@ impl SelectionsCollection { #[cfg(any(test, feature = "test-support"))] pub fn display_ranges(&self, cx: &mut App) -> Vec> { let display_map = self.display_map(cx); - self.disjoint_anchors() + self.disjoint_anchors_arc() .iter() - .chain(self.pending_anchor().as_ref()) + .chain(self.pending_anchor()) .map(|s| { if s.reversed { s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) @@ -412,6 +420,10 @@ impl SelectionsCollection { ); (mutable_collection.selections_changed, result) } + + pub fn next_selection_id(&self) -> usize { + self.next_selection_id + } } pub struct MutableSelectionsCollection<'a> { diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 8c54c265edf7a19af9d17e982a5f4cb6a0079cc3..fbf7a312fe56600ad78e13c278c85e29b8ca5aa5 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -396,7 +396,7 @@ impl EditorTestContext { let (multibuffer_snapshot, selections, excerpts) = self.update_editor(|editor, _, cx| { let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); - let selections = editor.selections.disjoint_anchors(); + let selections = editor.selections.disjoint_anchors_arc(); let excerpts = multibuffer_snapshot .excerpts() .map(|(e_id, snapshot, range)| (e_id, snapshot.clone(), range)) diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index ebf32d1b994814fa277201176b555efed5e85e66..bd46a067dc8e6c3aeec4de878709024f66a819f2 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -416,7 +416,7 @@ impl Item for TextDiffView { pub fn selection_location_text(editor: &Editor, cx: &App) -> Option { let buffer = editor.buffer().read(cx); let buffer_snapshot = buffer.snapshot(cx); - let first_selection = editor.selections.disjoint.first()?; + let first_selection = editor.selections.disjoint_anchors().first()?; let selection_start = first_selection.start.to_point(&buffer_snapshot); let selection_end = first_selection.end.to_point(&buffer_snapshot); diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 5fee0b95f11d94e8a448a8a11a43cc158786d190..53855c2c929ed44085b27bd22f80eba21e2e831d 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -463,7 +463,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { .collect(); vim.switch_mode(Mode::Normal, true, window, cx); let initial_selections = - vim.update_editor(cx, |_, editor, _| editor.selections.disjoint_anchors()); + vim.update_editor(cx, |_, editor, _| editor.selections.disjoint_anchors_arc()); if let Some(range) = &action.range { let result = vim.update_editor(cx, |vim, editor, cx| { let range = range.buffer_range(vim, editor, window, cx)?; @@ -515,7 +515,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { .buffer() .update(cx, |multi, cx| multi.last_transaction_id(cx)) { - let last_sel = editor.selections.disjoint_anchors(); + let last_sel = editor.selections.disjoint_anchors_arc(); editor.modify_transaction_selection_history(tx_id, |old| { old.0 = first_sel; old.1 = Some(last_sel); diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index 619769d41adc690014a2872eff9a18d6f0250ae6..acc4ef8d3c311892e864589fb998ffced7e47867 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -22,7 +22,7 @@ impl Vim { self.update_editor(cx, |vim, editor, cx| { let anchors = editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|s| s.head()) .collect::>(); diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 7c36ebe6747488376d2264e4984175fb536fed4f..8b3359c8f08046cf995db077a9a5ff0d36a97b95 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -326,7 +326,7 @@ impl Vim { let stable_anchors = editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|selection| { let start = selection.start.bias_left(&display_map.buffer_snapshot); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 7faff54e73b179bdfa944798a9c87fafa245f732..5fb1f90a08e7a4346619c089cfe93c826bedf156 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -1075,16 +1075,16 @@ impl Vim { } let snapshot = s.display_map(); - if let Some(pending) = s.pending.as_mut() - && pending.selection.reversed + if let Some(pending) = s.pending_anchor_mut() + && pending.reversed && mode.is_visual() && !last_mode.is_visual() { - let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot); + let mut end = pending.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); + pending.end = snapshot.buffer_snapshot.anchor_before(end); } s.move_with(|map, selection| { @@ -1332,7 +1332,7 @@ impl Vim { self.update_editor(cx, |_, editor, _| { editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|selection| selection.tail()..selection.head()) .collect() diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 5fbc04fbee9570db95cc95a4ce023e8e82c3183c..35bc1eba2c900cd7c8f370629e0585584bc92d59 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -748,7 +748,7 @@ impl Vim { // after the change let stable_anchors = editor .selections - .disjoint_anchors() + .disjoint_anchors_arc() .iter() .map(|selection| { let start = selection.start.bias_left(&display_map.buffer_snapshot); From 4a7784cf67fc677360bfa08e14841416a5131202 Mon Sep 17 00:00:00 2001 From: localcc Date: Wed, 17 Sep 2025 17:39:47 +0200 Subject: [PATCH 088/721] Allow opening a local folder inside WSL (#38335) This PR adds an option to allow opening local folders inside WSL containers. (wsl_actions::OpenFolderInWsl). It is accessible via the command palette and should be available to keybind. - [x] Open wsl from open remote - [x] Open local folder in wsl action - [ ] Open wsl shortcut (shortcuts to open remote) Release Notes: - N/A --- Cargo.lock | 1 + crates/recent_projects/Cargo.toml | 1 + crates/recent_projects/src/recent_projects.rs | 59 ++++ crates/recent_projects/src/remote_servers.rs | 184 +---------- crates/recent_projects/src/wsl_picker.rs | 295 ++++++++++++++++++ crates/util/src/paths.rs | 21 ++ crates/zed_actions/src/lib.rs | 16 + 7 files changed, 397 insertions(+), 180 deletions(-) create mode 100644 crates/recent_projects/src/wsl_picker.rs diff --git a/Cargo.lock b/Cargo.lock index cdf4109334af2e6f98a028970d124c7b9221371d..298ef0a4b260d57166f46674817f7717a25c185c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13770,6 +13770,7 @@ dependencies = [ "futures 0.3.31", "fuzzy", "gpui", + "indoc", "language", "log", "markdown", diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 91879c5d4175ad66428a255655f3c8bd4a5059e3..2ba6293ad2cf63c7ca664dba43f53d7facc70a57 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -43,6 +43,7 @@ util.workspace = true workspace.workspace = true zed_actions.workspace = true workspace-hack.workspace = true +indoc.workspace = true [target.'cfg(target_os = "windows")'.dependencies] windows-registry = "0.6.0" diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 2fc57a52fcb55f62b213cd7bb842009384b6ec91..35ef024743475fc2036600724224f9f3c06bca4a 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -3,6 +3,9 @@ mod remote_connections; mod remote_servers; mod ssh_config; +#[cfg(target_os = "windows")] +mod wsl_picker; + use remote::RemoteConnectionOptions; pub use remote_connections::open_remote_project; @@ -31,6 +34,62 @@ use zed_actions::{OpenRecent, OpenRemote}; pub fn init(cx: &mut App) { SshSettings::register(cx); + + #[cfg(target_os = "windows")] + cx.on_action(|open_wsl: &zed_actions::wsl_actions::OpenFolderInWsl, cx| { + let create_new_window = open_wsl.create_new_window; + with_active_or_new_workspace(cx, move |workspace, window, cx| { + use gpui::PathPromptOptions; + use project::DirectoryLister; + + let paths = workspace.prompt_for_open_path( + PathPromptOptions { + files: true, + directories: true, + multiple: false, + prompt: None, + }, + DirectoryLister::Local( + workspace.project().clone(), + workspace.app_state().fs.clone(), + ), + window, + cx, + ); + + cx.spawn_in(window, async move |workspace, cx| { + use util::paths::SanitizedPath; + + let Some(paths) = paths.await.log_err().flatten() else { + return; + }; + + let paths = paths + .into_iter() + .filter_map(|path| SanitizedPath::new(&path).local_to_wsl()) + .collect::>(); + + if paths.is_empty() { + let message = indoc::indoc! { r#" + Invalid path specified when trying to open a folder inside WSL. + + Please note that Zed currently does not support opening network share folders inside wsl. + "#}; + + let _ = cx.prompt(gpui::PromptLevel::Critical, "Invalid path", Some(&message), &["Ok"]).await; + return; + } + + workspace.update_in(cx, |workspace, window, cx| { + workspace.toggle_modal(window, cx, |window, cx| { + crate::wsl_picker::WslOpenModal::new(paths, create_new_window, window, cx) + }); + }).log_err(); + }) + .detach(); + }); + }); + cx.on_action(|open_recent: &OpenRecent, cx| { let create_new_window = open_recent.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 1ef9ab671f35dc477d1113e1b924c1272e13de2f..f7b9001444dea0371009a2ca878d12ab808a8823 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -83,7 +83,7 @@ impl CreateRemoteServer { #[cfg(target_os = "windows")] struct AddWslDistro { - picker: Entity>, + picker: Entity>, connection_prompt: Option>, _creating: Option>, } @@ -91,6 +91,8 @@ struct AddWslDistro { #[cfg(target_os = "windows")] impl AddWslDistro { fn new(window: &mut Window, cx: &mut Context) -> Self { + use crate::wsl_picker::{WslDistroSelected, WslPickerDelegate, WslPickerDismissed}; + let delegate = WslPickerDelegate::new(); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)); @@ -120,184 +122,6 @@ impl AddWslDistro { } } -#[cfg(target_os = "windows")] -#[derive(Clone, Debug)] -pub struct WslDistroSelected(pub String); - -#[cfg(target_os = "windows")] -#[derive(Clone, Debug)] -pub struct WslPickerDismissed; - -#[cfg(target_os = "windows")] -struct WslPickerDelegate { - selected_index: usize, - distro_list: Option>, - matches: Vec, -} - -#[cfg(target_os = "windows")] -impl WslPickerDelegate { - fn new() -> Self { - WslPickerDelegate { - selected_index: 0, - distro_list: None, - matches: Vec::new(), - } - } - - pub fn selected_distro(&self) -> Option { - self.matches - .get(self.selected_index) - .map(|m| m.string.clone()) - } -} - -#[cfg(target_os = "windows")] -impl WslPickerDelegate { - fn fetch_distros() -> anyhow::Result> { - use anyhow::Context; - use windows_registry::CURRENT_USER; - - let lxss_key = CURRENT_USER - .open("Software\\Microsoft\\Windows\\CurrentVersion\\Lxss") - .context("failed to get lxss wsl key")?; - - let distros = lxss_key - .keys() - .context("failed to get wsl distros")? - .filter_map(|key| { - lxss_key - .open(&key) - .context("failed to open subkey for distro") - .log_err() - }) - .filter_map(|distro| distro.get_string("DistributionName").ok()) - .collect::>(); - - Ok(distros) - } -} - -#[cfg(target_os = "windows")] -impl EventEmitter for Picker {} - -#[cfg(target_os = "windows")] -impl EventEmitter for Picker {} - -#[cfg(target_os = "windows")] -impl picker::PickerDelegate for WslPickerDelegate { - type ListItem = ListItem; - - fn match_count(&self) -> usize { - self.matches.len() - } - - fn selected_index(&self) -> usize { - self.selected_index - } - - fn set_selected_index( - &mut self, - ix: usize, - _window: &mut Window, - cx: &mut Context>, - ) { - self.selected_index = ix; - cx.notify(); - } - - fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { - Arc::from("Enter WSL distro name") - } - - fn update_matches( - &mut self, - query: String, - _window: &mut Window, - cx: &mut Context>, - ) -> Task<()> { - use fuzzy::StringMatchCandidate; - - let needs_fetch = self.distro_list.is_none(); - if needs_fetch { - let distros = Self::fetch_distros().log_err(); - self.distro_list = distros; - } - - if let Some(distro_list) = &self.distro_list { - use ordered_float::OrderedFloat; - - let candidates = distro_list - .iter() - .enumerate() - .map(|(id, distro)| StringMatchCandidate::new(id, distro)) - .collect::>(); - - let query = query.trim_start(); - let smart_case = query.chars().any(|c| c.is_uppercase()); - self.matches = smol::block_on(fuzzy::match_strings( - candidates.as_slice(), - query, - smart_case, - true, - 100, - &Default::default(), - cx.background_executor().clone(), - )); - self.matches.sort_unstable_by_key(|m| m.candidate_id); - - self.selected_index = self - .matches - .iter() - .enumerate() - .rev() - .max_by_key(|(_, m)| OrderedFloat(m.score)) - .map(|(index, _)| index) - .unwrap_or(0); - } - - Task::ready(()) - } - - fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context>) { - if let Some(distro) = self.matches.get(self.selected_index) { - cx.emit(WslDistroSelected(distro.string.clone())); - } - } - - fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { - cx.emit(WslPickerDismissed); - } - - fn render_match( - &self, - ix: usize, - selected: bool, - _: &mut Window, - _: &mut Context>, - ) -> Option { - use ui::HighlightedLabel; - - let matched = self.matches.get(ix)?; - Some( - ListItem::new(ix) - .toggle_state(selected) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .child( - h_flex() - .flex_grow() - .gap_3() - .child(Icon::new(IconName::Linux)) - .child(v_flex().child(HighlightedLabel::new( - matched.string.clone(), - matched.positions.clone(), - ))), - ), - ) - } -} - enum ProjectPickerData { Ssh { connection_string: SharedString, @@ -862,7 +686,7 @@ impl RemoteServerProjects { #[cfg(target_os = "windows")] fn connect_wsl_distro( &mut self, - picker: Entity>, + picker: Entity>, distro: String, window: &mut Window, cx: &mut Context, diff --git a/crates/recent_projects/src/wsl_picker.rs b/crates/recent_projects/src/wsl_picker.rs new file mode 100644 index 0000000000000000000000000000000000000000..e386b723fa43777e496565c11b8308f16031d837 --- /dev/null +++ b/crates/recent_projects/src/wsl_picker.rs @@ -0,0 +1,295 @@ +use std::{path::PathBuf, sync::Arc}; + +use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Subscription, Task}; +use picker::Picker; +use remote::{RemoteConnectionOptions, WslConnectionOptions}; +use ui::{ + App, Context, HighlightedLabel, Icon, IconName, InteractiveElement, ListItem, ParentElement, + Render, Styled, StyledExt, Toggleable, Window, div, h_flex, rems, v_flex, +}; +use util::ResultExt as _; +use workspace::{ModalView, Workspace}; + +use crate::open_remote_project; + +#[derive(Clone, Debug)] +pub struct WslDistroSelected { + pub secondary: bool, + pub distro: String, +} + +#[derive(Clone, Debug)] +pub struct WslPickerDismissed; + +pub(crate) struct WslPickerDelegate { + selected_index: usize, + distro_list: Option>, + matches: Vec, +} + +impl WslPickerDelegate { + pub fn new() -> Self { + WslPickerDelegate { + selected_index: 0, + distro_list: None, + matches: Vec::new(), + } + } + + pub fn selected_distro(&self) -> Option { + self.matches + .get(self.selected_index) + .map(|m| m.string.clone()) + } +} + +impl WslPickerDelegate { + fn fetch_distros() -> anyhow::Result> { + use anyhow::Context; + use windows_registry::CURRENT_USER; + + let lxss_key = CURRENT_USER + .open("Software\\Microsoft\\Windows\\CurrentVersion\\Lxss") + .context("failed to get lxss wsl key")?; + + let distros = lxss_key + .keys() + .context("failed to get wsl distros")? + .filter_map(|key| { + lxss_key + .open(&key) + .context("failed to open subkey for distro") + .log_err() + }) + .filter_map(|distro| distro.get_string("DistributionName").ok()) + .collect::>(); + + Ok(distros) + } +} + +impl EventEmitter for Picker {} + +impl EventEmitter for Picker {} + +impl picker::PickerDelegate for WslPickerDelegate { + type ListItem = ListItem; + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index( + &mut self, + ix: usize, + _window: &mut Window, + cx: &mut Context>, + ) { + self.selected_index = ix; + cx.notify(); + } + + fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { + Arc::from("Enter WSL distro name") + } + + fn update_matches( + &mut self, + query: String, + _window: &mut Window, + cx: &mut Context>, + ) -> Task<()> { + use fuzzy::StringMatchCandidate; + + let needs_fetch = self.distro_list.is_none(); + if needs_fetch { + let distros = Self::fetch_distros().log_err(); + self.distro_list = distros; + } + + if let Some(distro_list) = &self.distro_list { + use ordered_float::OrderedFloat; + + let candidates = distro_list + .iter() + .enumerate() + .map(|(id, distro)| StringMatchCandidate::new(id, distro)) + .collect::>(); + + let query = query.trim_start(); + let smart_case = query.chars().any(|c| c.is_uppercase()); + self.matches = smol::block_on(fuzzy::match_strings( + candidates.as_slice(), + query, + smart_case, + true, + 100, + &Default::default(), + cx.background_executor().clone(), + )); + self.matches.sort_unstable_by_key(|m| m.candidate_id); + + self.selected_index = self + .matches + .iter() + .enumerate() + .rev() + .max_by_key(|(_, m)| OrderedFloat(m.score)) + .map(|(index, _)| index) + .unwrap_or(0); + } + + Task::ready(()) + } + + fn confirm(&mut self, secondary: bool, _window: &mut Window, cx: &mut Context>) { + if let Some(distro) = self.matches.get(self.selected_index) { + cx.emit(WslDistroSelected { + secondary, + distro: distro.string.clone(), + }); + } + } + + fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { + cx.emit(WslPickerDismissed); + } + + fn render_match( + &self, + ix: usize, + selected: bool, + _: &mut Window, + _: &mut Context>, + ) -> Option { + let matched = self.matches.get(ix)?; + Some( + ListItem::new(ix) + .toggle_state(selected) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .child( + h_flex() + .flex_grow() + .gap_3() + .child(Icon::new(IconName::Linux)) + .child(v_flex().child(HighlightedLabel::new( + matched.string.clone(), + matched.positions.clone(), + ))), + ), + ) + } +} + +pub(crate) struct WslOpenModal { + paths: Vec, + create_new_window: bool, + picker: Entity>, + _subscriptions: [Subscription; 2], +} + +impl WslOpenModal { + pub fn new( + paths: Vec, + create_new_window: bool, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let delegate = WslPickerDelegate::new(); + let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)); + + let selected = cx.subscribe_in( + &picker, + window, + |this, _, event: &WslDistroSelected, window, cx| { + this.confirm(&event.distro, event.secondary, window, cx); + }, + ); + + let dismissed = cx.subscribe_in( + &picker, + window, + |this, _, _: &WslPickerDismissed, window, cx| { + this.cancel(&menu::Cancel, window, cx); + }, + ); + + WslOpenModal { + paths, + create_new_window, + picker, + _subscriptions: [selected, dismissed], + } + } + + fn confirm( + &mut self, + distro: &str, + secondary: bool, + window: &mut Window, + cx: &mut Context, + ) { + let app_state = workspace::AppState::global(cx); + let Some(app_state) = app_state.upgrade() else { + return; + }; + + let connection_options = RemoteConnectionOptions::Wsl(WslConnectionOptions { + distro_name: distro.to_string(), + user: None, + }); + + let replace_current_window = match self.create_new_window { + true => secondary, + false => !secondary, + }; + let replace_window = match replace_current_window { + true => window.window_handle().downcast::(), + false => None, + }; + + let paths = self.paths.clone(); + let open_options = workspace::OpenOptions { + replace_window, + ..Default::default() + }; + + cx.emit(DismissEvent); + cx.spawn_in(window, async move |_, cx| { + open_remote_project(connection_options, paths, app_state, open_options, cx).await + }) + .detach(); + } + + fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context) { + cx.emit(DismissEvent); + } +} + +impl ModalView for WslOpenModal {} + +impl Focusable for WslOpenModal { + fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { + self.picker.focus_handle(cx) + } +} + +impl EventEmitter for WslOpenModal {} + +impl Render for WslOpenModal { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl ui::IntoElement { + div() + .on_mouse_down_out(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))) + .on_action(cx.listener(Self::cancel)) + .elevation_3(cx) + .w(rems(34.)) + .flex_1() + .overflow_hidden() + .child(self.picker.clone()) + } +} diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 1658052e6f4894b54c83fecf29e729959c9cfe6e..72753b026e2194e0b083acb1f9d6d69864286c6b 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -65,6 +65,7 @@ pub trait PathExt { .with_context(|| format!("Invalid WTF-8 sequence: {bytes:?}")) } } + fn local_to_wsl(&self) -> Option; } impl> PathExt for T { @@ -118,6 +119,26 @@ impl> PathExt for T { self.as_ref().to_string_lossy().to_string() } } + + /// Converts a local path to one that can be used inside of WSL. + /// Returns `None` if the path cannot be converted into a WSL one (network share). + fn local_to_wsl(&self) -> Option { + let mut new_path = PathBuf::new(); + for component in self.as_ref().components() { + match component { + std::path::Component::Prefix(prefix) => { + let drive_letter = prefix.as_os_str().to_string_lossy().to_lowercase(); + let drive_letter = drive_letter.strip_suffix(':')?; + + new_path.push(format!("/mnt/{}", drive_letter)); + } + std::path::Component::RootDir => {} + _ => new_path.push(component), + } + } + + Some(new_path) + } } /// In memory, this is identical to `Path`. On non-Windows conversions to this type are no-ops. On diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index fd979b3648b9a84aa89039386f8ac300e28d4771..2015f6db3b6c5754fe6b7e433866f05f43de440f 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -497,3 +497,19 @@ actions!( OpenProjectDebugTasks, ] ); + +#[cfg(target_os = "windows")] +pub mod wsl_actions { + use gpui::Action; + use schemars::JsonSchema; + use serde::Deserialize; + + /// Opens a folder inside Wsl. + #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] + #[action(namespace = projects)] + #[serde(deny_unknown_fields)] + pub struct OpenFolderInWsl { + #[serde(default)] + pub create_new_window: bool, + } +} From 52521efc7b898ad0431810ecb89660129bf11dfe Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 17 Sep 2025 17:41:46 +0200 Subject: [PATCH 089/721] acp: update to v0.4 of Rust library (#38336) Release Notes: - N/A --- Cargo.lock | 14 ++++++++------ Cargo.toml | 2 +- crates/agent_servers/Cargo.toml | 1 + crates/agent_servers/src/acp.rs | 15 ++++----------- tooling/workspace-hack/Cargo.toml | 8 ++------ 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 298ef0a4b260d57166f46674817f7717a25c185c..6de762f43b31405d47c4ddcfc26a2be03eaadb80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,12 +195,13 @@ dependencies = [ [[package]] name = "agent-client-protocol" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003fb91bf1b8d6e15f72c45fb9171839af8241e81e3839fbb73536af113b7a79" +checksum = "cc2526e80463b9742afed4829aedd6ae5632d6db778c6cc1fecb80c960c3521b" dependencies = [ "anyhow", "async-broadcast", + "async-trait", "futures 0.3.31", "log", "parking_lot", @@ -293,6 +294,7 @@ dependencies = [ "agent-client-protocol", "agent_settings", "anyhow", + "async-trait", "client", "collections", "env_logger 0.11.8", @@ -2190,7 +2192,7 @@ dependencies = [ "bitflags 2.9.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -13225,7 +13227,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes 1.10.1", "heck 0.5.0", - "itertools 0.11.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -13258,7 +13260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.101", @@ -20622,7 +20624,7 @@ dependencies = [ "idna", "indexmap", "inout", - "itertools 0.11.0", + "itertools 0.12.1", "itertools 0.13.0", "jiff", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index e69885a835f5f579ac5ac5fa0063f5291a1d01f5..96a1fc588f03162ba10ed60c52353a1a710b3783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -437,7 +437,7 @@ zlog_settings = { path = "crates/zlog_settings" } # External crates # -agent-client-protocol = { version = "0.2.1", features = ["unstable"] } +agent-client-protocol = { version = "0.4.0", features = ["unstable"] } aho-corasick = "1.1" alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" } any_vec = "0.14" diff --git a/crates/agent_servers/Cargo.toml b/crates/agent_servers/Cargo.toml index a168da05c83482f9d5b34118a74ee5e1f15e2e37..ca6db6c663ddb2132c05d716e5b935c5855bccdb 100644 --- a/crates/agent_servers/Cargo.toml +++ b/crates/agent_servers/Cargo.toml @@ -23,6 +23,7 @@ action_log.workspace = true agent-client-protocol.workspace = true agent_settings.workspace = true anyhow.workspace = true +async-trait.workspace = true client.workspace = true collections.workspace = true env_logger = { workspace = true, optional = true } diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index cc897d85e7b4de149a0dca84df84d2b8c2c5bc98..b8c75a01a2e2965c255e32bd3c0746b26d78ecab 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -13,7 +13,7 @@ use util::ResultExt as _; use std::path::PathBuf; use std::{any::Any, cell::RefCell}; -use std::{path::Path, rc::Rc, sync::Arc}; +use std::{path::Path, rc::Rc}; use thiserror::Error; use anyhow::{Context as _, Result}; @@ -505,6 +505,7 @@ struct ClientDelegate { cx: AsyncApp, } +#[async_trait::async_trait(?Send)] impl acp::Client for ClientDelegate { async fn request_permission( &self, @@ -638,19 +639,11 @@ impl acp::Client for ClientDelegate { Ok(Default::default()) } - async fn ext_method( - &self, - _name: Arc, - _params: Arc, - ) -> Result, acp::Error> { + async fn ext_method(&self, _args: acp::ExtRequest) -> Result { Err(acp::Error::method_not_found()) } - async fn ext_notification( - &self, - _name: Arc, - _params: Arc, - ) -> Result<(), acp::Error> { + async fn ext_notification(&self, _args: acp::ExtNotification) -> Result<(), acp::Error> { Err(acp::Error::method_not_found()) } diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index a01989a047c6072db1719ccef6278f80b323123b..ec9629685d8366864b92a6160ece623450f72b0c 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -75,6 +75,7 @@ hmac = { version = "0.12", default-features = false, features = ["reset"] } hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] } idna = { version = "1" } indexmap = { version = "2", features = ["serde"] } +itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } jiff = { version = "0.2" } lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } libc = { version = "0.2", features = ["extra_traits"] } @@ -213,6 +214,7 @@ hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", " idna = { version = "1" } indexmap = { version = "2", features = ["serde"] } itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" } +itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } jiff = { version = "0.2" } lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } libc = { version = "0.2", features = ["extra_traits"] } @@ -333,7 +335,6 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } @@ -395,7 +396,6 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } @@ -474,7 +474,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -555,7 +554,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } @@ -614,7 +612,6 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } num = { version = "0.4" } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } @@ -690,7 +687,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } inout = { version = "0.1", default-features = false, features = ["block-padding"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } From 50326ddc359e612e9f91cffa64565f87bc814d0a Mon Sep 17 00:00:00 2001 From: Nils Koch Date: Wed, 17 Sep 2025 17:58:46 +0200 Subject: [PATCH 090/721] project_panel: Collapse top-level entries in `Collapse all entries` command (#38310) Closes #11760 The command `project panel: collapse all entries` currently does not collapse top-level entries (the workspaces themselves). I think this should be expected behaviour if you only have a single workspace in your project. However, if you have multiple workspaces, we should collapse their top-level folders as well. This is the expected behaviour in the screenshots in #11760. For more context: Atm the `.retain` function empties the `self.expanded_dir_ids` Hash Map, because the `expanded_entries` Vec is (almost) never empty - it contains the id of the `root_entry` of the workspace. https://github.com/zed-industries/zed/blob/d48d6a745409a8998998ed59c28493a1aa733ebb/crates/project_panel/src/project_panel.rs#L1148-L1152 We then update the `self.expanded_dir_ids` in the `update_visible_entries` function, and since the Hash Map is empty, we execute the `hash_map::Entry::Vacant` arm of the following match statement. https://github.com/zed-industries/zed/blob/d48d6a745409a8998998ed59c28493a1aa733ebb/crates/project_panel/src/project_panel.rs#L3062-L3073 This change makes sure that we do not clear the `expanded_dir_ids` HashMap and always keep the keys for all visible workspaces and therefore we run the `hash_map::Entry::Occupied` arm, which does not override the `expanded_dir_ids` anymore. https://github.com/user-attachments/assets/b607523b-2ea2-4159-8edf-aed7bca05e3a cc @MrSubidubi Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Finn Evers --- crates/project_panel/src/project_panel.rs | 26 ++++- .../project_panel/src/project_panel_tests.rs | 105 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index aed3b44515554299b3d50fbcf5e2b58123495908..ec63c5ca60ee2bdca9ba699c2af300116e5cdb3a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1147,8 +1147,32 @@ impl ProjectPanel { ) { // By keeping entries for fully collapsed worktrees, we avoid expanding them within update_visible_entries // (which is it's default behavior when there's no entry for a worktree in expanded_dir_ids). + let multiple_worktrees = self.project.read(cx).worktrees(cx).count() > 1; + let project = self.project.read(cx); + self.expanded_dir_ids - .retain(|_, expanded_entries| expanded_entries.is_empty()); + .iter_mut() + .for_each(|(worktree_id, expanded_entries)| { + if multiple_worktrees { + *expanded_entries = Default::default(); + return; + } + + let root_entry_id = project + .worktree_for_id(*worktree_id, cx) + .map(|worktree| worktree.read(cx).snapshot()) + .and_then(|worktree_snapshot| { + worktree_snapshot.root_entry().map(|entry| entry.id) + }); + + match root_entry_id { + Some(id) => { + expanded_entries.retain(|entry_id| entry_id == &id); + } + None => *expanded_entries = Default::default(), + }; + }); + self.update_visible_entries(None, cx); cx.notify(); } diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index ad2a7d12ecce31cf1aa4458b3fd59e23f63ab08b..73c33b057807d66687c2dec13962fed5ed0412d3 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -2747,6 +2747,111 @@ async fn test_collapse_all_entries(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_collapse_all_entries_multiple_worktrees(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor()); + let worktree_content = json!({ + "dir_1": { + "file_1.py": "# File contents", + }, + "dir_2": { + "file_1.py": "# File contents", + } + }); + + fs.insert_tree("/project_root_1", worktree_content.clone()) + .await; + fs.insert_tree("/project_root_2", worktree_content).await; + + let project = Project::test( + fs.clone(), + ["/project_root_1".as_ref(), "/project_root_2".as_ref()], + cx, + ) + .await; + let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + panel.update_in(cx, |panel, window, cx| { + panel.collapse_all_entries(&CollapseAllEntries, window, cx) + }); + cx.executor().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &["> project_root_1", "> project_root_2",] + ); +} + +#[gpui::test] +async fn test_collapse_all_entries_with_collapsed_root(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + "/project_root", + json!({ + "dir_1": { + "nested_dir": { + "file_a.py": "# File contents", + "file_b.py": "# File contents", + "file_c.py": "# File contents", + }, + "file_1.py": "# File contents", + "file_2.py": "# File contents", + "file_3.py": "# File contents", + }, + "dir_2": { + "file_1.py": "# File contents", + "file_2.py": "# File contents", + "file_3.py": "# File contents", + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await; + let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + // Open project_root/dir_1 to ensure that a nested directory is expanded + toggle_expand_dir(&panel, "project_root/dir_1", cx); + cx.executor().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v project_root", + " v dir_1 <== selected", + " > nested_dir", + " file_1.py", + " file_2.py", + " file_3.py", + " > dir_2", + ] + ); + + // Close root directory + toggle_expand_dir(&panel, "project_root", cx); + cx.executor().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &["> project_root <== selected"] + ); + + // Run collapse_all_entries and make sure root is not expanded + panel.update_in(cx, |panel, window, cx| { + panel.collapse_all_entries(&CollapseAllEntries, window, cx) + }); + cx.executor().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &["> project_root <== selected"] + ); +} + #[gpui::test] async fn test_new_file_move(cx: &mut gpui::TestAppContext) { init_test(cx); From 824f6953835784f1408efaa2542bb9f77e9b7145 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 17 Sep 2025 12:25:50 -0400 Subject: [PATCH 091/721] Rename Windows GitHub Issue template (#38339) Release Notes: - N/A --- .../{07_bug_windows_alpha.yml => 07_bug_windows_beta.yml} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename .github/ISSUE_TEMPLATE/{07_bug_windows_alpha.yml => 07_bug_windows_beta.yml} (86%) diff --git a/.github/ISSUE_TEMPLATE/07_bug_windows_alpha.yml b/.github/ISSUE_TEMPLATE/07_bug_windows_beta.yml similarity index 86% rename from .github/ISSUE_TEMPLATE/07_bug_windows_alpha.yml rename to .github/ISSUE_TEMPLATE/07_bug_windows_beta.yml index 826c2b8027144d4b658108e09c79e40490c3005d..b2b2a0f9dfcd5ddaa0dda41650864b053c5bb933 100644 --- a/.github/ISSUE_TEMPLATE/07_bug_windows_alpha.yml +++ b/.github/ISSUE_TEMPLATE/07_bug_windows_beta.yml @@ -1,8 +1,8 @@ -name: Bug Report (Windows Alpha) -description: Zed Windows Alpha Related Bugs +name: Bug Report (Windows Beta) +description: Zed Windows Beta Related Bugs type: "Bug" labels: ["windows"] -title: "Windows Alpha: " +title: "Windows Beta: " body: - type: textarea attributes: From 43f40c60fda1a4d81b503aa0898cde4455931f90 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Sep 2025 13:02:13 -0400 Subject: [PATCH 092/721] rope: Fix spelling of `peek_with_bitmaps` (#38341) This PR fixes the spelling of the `peek_with_bitmaps` method. Release Notes: - N/A --- crates/rope/src/rope.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 8bcaef20ca3bd5c79413791764a313fd1e6b75ac..3f6addb7c2394503098a213f4139fedc9757ba86 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -767,7 +767,7 @@ impl<'a> Chunks<'a> { } /// Returns bitmaps that represent character positions and tab positions - pub fn peak_with_bitmaps(&self) -> Option> { + pub fn peek_with_bitmaps(&self) -> Option> { if !self.offset_is_valid() { return None; } @@ -898,7 +898,7 @@ impl<'a> Iterator for ChunkWithBitmaps<'a> { type Item = ChunkBitmaps<'a>; fn next(&mut self) -> Option { - let chunk_bitmaps = self.0.peak_with_bitmaps()?; + let chunk_bitmaps = self.0.peek_with_bitmaps()?; if self.0.reversed { self.0.offset -= chunk_bitmaps.text.len(); if self.0.offset <= *self.0.chunks.start() { From 3968b9cd09f1c9341c6f220e2e779d68842be253 Mon Sep 17 00:00:00 2001 From: localcc Date: Wed, 17 Sep 2025 19:55:30 +0200 Subject: [PATCH 093/721] Add open WSL shortcut (#38342) Adds a shortcut to add a WSL distro for better wsl feature discoverability. - [x] Open wsl from open remote - [x] Open local folder in wsl action - [x] Open wsl shortcut (shortcuts to open remote) Release Notes: - N/A --- crates/recent_projects/src/recent_projects.rs | 12 ++++++ crates/recent_projects/src/remote_servers.rs | 38 ++++++++++++++++++- crates/zed_actions/src/lib.rs | 9 +++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 35ef024743475fc2036600724224f9f3c06bca4a..71a27737bdd1bfc82923a45459b7e100adbbf22e 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -90,6 +90,18 @@ pub fn init(cx: &mut App) { }); }); + #[cfg(target_os = "windows")] + cx.on_action(|open_wsl: &zed_actions::wsl_actions::OpenWsl, cx| { + let create_new_window = open_wsl.create_new_window; + with_active_or_new_workspace(cx, move |workspace, window, cx| { + let handle = cx.entity().downgrade(); + let fs = workspace.project().read(cx).fs().clone(); + workspace.toggle_modal(window, cx, |window, cx| { + RemoteServerProjects::wsl(create_new_window, fs, window, handle, cx) + }); + }); + }); + cx.on_action(|open_recent: &OpenRecent, cx| { let create_new_window = open_recent.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index f7b9001444dea0371009a2ca878d12ab808a8823..d8dd37485e140d603e219ff6bdf8c9780da51528 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -522,12 +522,48 @@ impl Mode { } impl RemoteServerProjects { + #[cfg(target_os = "windows")] + pub fn wsl( + create_new_window: bool, + fs: Arc, + window: &mut Window, + workspace: WeakEntity, + cx: &mut Context, + ) -> Self { + Self::new_inner( + Mode::AddWslDistro(AddWslDistro::new(window, cx)), + create_new_window, + fs, + window, + workspace, + cx, + ) + } + pub fn new( create_new_window: bool, fs: Arc, window: &mut Window, workspace: WeakEntity, cx: &mut Context, + ) -> Self { + Self::new_inner( + Mode::default_mode(&BTreeSet::new(), cx), + create_new_window, + fs, + window, + workspace, + cx, + ) + } + + fn new_inner( + mode: Mode, + create_new_window: bool, + fs: Arc, + window: &mut Window, + workspace: WeakEntity, + cx: &mut Context, ) -> Self { let focus_handle = cx.focus_handle(); let mut read_ssh_config = SshSettings::get_global(cx).read_ssh_config; @@ -558,7 +594,7 @@ impl RemoteServerProjects { }); Self { - mode: Mode::default_mode(&BTreeSet::new(), cx), + mode, focus_handle, workspace, retained_connections: Vec::new(), diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 2015f6db3b6c5754fe6b7e433866f05f43de440f..81cca94f067de206a52241d202acf517dc80c614 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -512,4 +512,13 @@ pub mod wsl_actions { #[serde(default)] pub create_new_window: bool, } + + /// Open a wsl distro. + #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] + #[action(namespace = projects)] + #[serde(deny_unknown_fields)] + pub struct OpenWsl { + #[serde(default)] + pub create_new_window: bool, + } } From f0b21508ec64b8293074d28d590321a868155c1a Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Wed, 17 Sep 2025 20:37:36 +0200 Subject: [PATCH 094/721] editor: Properly layout expand toggles with git blame enabled (#38349) Release Notes: - Fixed an issue where expand toggles were too large with the git blame deployed. --- crates/editor/src/element.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2212507e38b2e577eed1cf140eea362425152623..37ae90d70b91017016e46b409f67c7000c2b0f91 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3019,6 +3019,12 @@ impl EditorElement { .ilog10() + 1; + let git_gutter_width = Self::gutter_strip_width(line_height) + + gutter_dimensions + .git_blame_entries_width + .unwrap_or_default(); + let available_width = gutter_dimensions.left_padding - git_gutter_width; + buffer_rows .iter() .enumerate() @@ -3034,9 +3040,6 @@ impl EditorElement { ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical, }; - let git_gutter_width = Self::gutter_strip_width(line_height); - let available_width = gutter_dimensions.left_padding - git_gutter_width; - let editor = self.editor.clone(); let is_wide = max_line_number_length >= EditorSettings::get_global(cx).gutter.min_line_number_digits as u32 From 86a26499448209f465282dc06ca2a6bc91f7b460 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Wed, 17 Sep 2025 15:04:18 -0400 Subject: [PATCH 095/721] docs: Add whitespace_map (#38355) Adds docs for settings introduced in: - https://github.com/zed-industries/zed/pull/37704 Release Notes: - N/A --- docs/src/configuring-zed.md | 15 +++++++++++++++ docs/src/visual-customization.md | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 6c25d62e291f91f3faf4dc77e5a6dab3b8637ca8..58cde307662febbd99826b8b0954dddf4984cd9d 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -2626,6 +2626,7 @@ The following settings can be overridden for each specific language: - [`remove_trailing_whitespace_on_save`](#remove-trailing-whitespace-on-save) - [`show_edit_predictions`](#show-edit-predictions) - [`show_whitespaces`](#show-whitespaces) +- [`whitespace_map`](#whitespace-map) - [`soft_wrap`](#soft-wrap) - [`tab_size`](#tab-size) - [`use_autoclose`](#use-autoclose) @@ -3348,6 +3349,20 @@ Positive integer values 3. `none` 4. `boundary` +## Whitespace Map + +- Description: Specify the characters used to render whitespace when show_whitespaces is enabled. +- Setting: `whitespace_map` +- Default: + +```json +{ + "whitespace_map": { + "space": "•", + "tab": "→" + }, +``` + ## Soft Wrap - Description: Whether or not to automatically wrap lines of text to fit editor / preferred width. diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index 150b701168f49980844ea37c223efe00b6dc06cc..55f2dfe9b4d40d46a640520a99952964712c640e 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -185,6 +185,10 @@ TBD: Centered layout related settings // Visually show tabs and spaces (none, all, selection, boundary, trailing) "show_whitespaces": "selection", + "whitespace_map": { // Which characters to show when `show_whitespaces` enabled + "space": "•", + "tab": "→" + }, "unnecessary_code_fade": 0.3, // How much to fade out unused code. From f6d08fe59ce469a4a757739b34cfe1e3f0318d2c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Sep 2025 15:15:56 -0400 Subject: [PATCH 096/721] Remove `/cargo-workspace` slash command (#38354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR removes the `/cargo-workspace` slash command. We never fully shipped this—with it requiring explicit opt-in via a setting—and it doesn't seem like the feature is needed in an agentic world. Release Notes: - Removed the `/cargo-workspace` slash command. --- Cargo.lock | 2 - assets/settings/default.json | 8 - crates/agent_ui/src/agent_ui.rs | 22 --- crates/agent_ui/src/slash_command_settings.rs | 37 ---- crates/assistant_slash_commands/Cargo.toml | 2 - .../src/assistant_slash_commands.rs | 2 - .../src/cargo_workspace_command.rs | 158 ------------------ 7 files changed, 231 deletions(-) delete mode 100644 crates/agent_ui/src/slash_command_settings.rs delete mode 100644 crates/assistant_slash_commands/src/cargo_workspace_command.rs diff --git a/Cargo.lock b/Cargo.lock index 6de762f43b31405d47c4ddcfc26a2be03eaadb80..13acb75ee146f412f704ed7d2c209ac59928a3cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,7 +901,6 @@ version = "0.1.0" dependencies = [ "anyhow", "assistant_slash_command", - "cargo_toml", "chrono", "collections", "context_server", @@ -924,7 +923,6 @@ dependencies = [ "settings", "smol", "text", - "toml 0.8.20", "ui", "util", "workspace", diff --git a/assets/settings/default.json b/assets/settings/default.json index daab56dc93976728667ed6995cb445f363ee2be8..6b521f9bcf1adc4fc2ebd85170f3aa187ca5b734 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -920,14 +920,6 @@ // Default: 4 "message_editor_min_lines": 4 }, - // The settings for slash commands. - "slash_commands": { - // Settings for the `/project` slash command. - "project": { - // Whether `/project` is enabled. - "enabled": false - } - }, // Whether the screen sharing icon is shown in the os status bar. "show_call_status_icon": true, // Whether to use language servers to provide code intelligence. diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 94efa767c5f1cd126695b8345230fba78583eb2f..a1de392158e80be11d69a9d98cbe292f2cadf540 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -14,7 +14,6 @@ mod message_editor; mod profile_selector; mod slash_command; mod slash_command_picker; -mod slash_command_settings; mod terminal_codegen; mod terminal_inline_assistant; mod text_thread_editor; @@ -46,7 +45,6 @@ use std::any::TypeId; use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal}; pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate}; pub use crate::inline_assistant::InlineAssistant; -use crate::slash_command_settings::SlashCommandSettings; pub use agent_diff::{AgentDiffPane, AgentDiffToolbar}; pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor}; use zed_actions; @@ -257,7 +255,6 @@ pub fn init( cx: &mut App, ) { AgentSettings::register(cx); - SlashCommandSettings::register(cx); assistant_context::init(client.clone(), cx); rules_library::init(cx); @@ -413,8 +410,6 @@ fn register_slash_commands(cx: &mut App) { slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true); slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true); slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true); - slash_command_registry - .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true); slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true); slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true); slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false); @@ -434,21 +429,4 @@ fn register_slash_commands(cx: &mut App) { } }) .detach(); - - update_slash_commands_from_settings(cx); - cx.observe_global::(update_slash_commands_from_settings) - .detach(); -} - -fn update_slash_commands_from_settings(cx: &mut App) { - let slash_command_registry = SlashCommandRegistry::global(cx); - let settings = SlashCommandSettings::get_global(cx); - - if settings.cargo_workspace.enabled { - slash_command_registry - .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true); - } else { - slash_command_registry - .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand); - } } diff --git a/crates/agent_ui/src/slash_command_settings.rs b/crates/agent_ui/src/slash_command_settings.rs deleted file mode 100644 index 9580ffef0f317fbe726c57041fad4f0fa438e143..0000000000000000000000000000000000000000 --- a/crates/agent_ui/src/slash_command_settings.rs +++ /dev/null @@ -1,37 +0,0 @@ -use anyhow::Result; -use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; - -/// Settings for slash commands. -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "slash_commands")] -pub struct SlashCommandSettings { - /// Settings for the `/cargo-workspace` slash command. - #[serde(default)] - pub cargo_workspace: CargoWorkspaceCommandSettings, -} - -/// Settings for the `/cargo-workspace` slash command. -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] -pub struct CargoWorkspaceCommandSettings { - /// Whether `/cargo-workspace` is enabled. - #[serde(default)] - pub enabled: bool, -} - -impl Settings for SlashCommandSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _cx: &mut App) -> Result { - SettingsSources::::json_merge_with( - [sources.default] - .into_iter() - .chain(sources.user) - .chain(sources.server), - ) - } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} -} diff --git a/crates/assistant_slash_commands/Cargo.toml b/crates/assistant_slash_commands/Cargo.toml index c054c3ced84825bcd131bdd76644c00595c4c4a9..f151515d4235b7ecb150539aceb1c5478960517b 100644 --- a/crates/assistant_slash_commands/Cargo.toml +++ b/crates/assistant_slash_commands/Cargo.toml @@ -14,7 +14,6 @@ path = "src/assistant_slash_commands.rs" [dependencies] anyhow.workspace = true assistant_slash_command.workspace = true -cargo_toml.workspace = true chrono.workspace = true collections.workspace = true context_server.workspace = true @@ -35,7 +34,6 @@ serde.workspace = true serde_json.workspace = true smol.workspace = true text.workspace = true -toml.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true diff --git a/crates/assistant_slash_commands/src/assistant_slash_commands.rs b/crates/assistant_slash_commands/src/assistant_slash_commands.rs index fb00a912197e07942a67ad92418b85c4920ad66b..2bf2573e99d7a5a0140c1972967ec68523b0b56a 100644 --- a/crates/assistant_slash_commands/src/assistant_slash_commands.rs +++ b/crates/assistant_slash_commands/src/assistant_slash_commands.rs @@ -1,4 +1,3 @@ -mod cargo_workspace_command; mod context_server_command; mod default_command; mod delta_command; @@ -12,7 +11,6 @@ mod streaming_example_command; mod symbols_command; mod tab_command; -pub use crate::cargo_workspace_command::*; pub use crate::context_server_command::*; pub use crate::default_command::*; pub use crate::delta_command::*; diff --git a/crates/assistant_slash_commands/src/cargo_workspace_command.rs b/crates/assistant_slash_commands/src/cargo_workspace_command.rs deleted file mode 100644 index d58b2edc4c3dffd799dd9eb1c104686dc6488687..0000000000000000000000000000000000000000 --- a/crates/assistant_slash_commands/src/cargo_workspace_command.rs +++ /dev/null @@ -1,158 +0,0 @@ -use anyhow::{Context as _, Result, anyhow}; -use assistant_slash_command::{ - ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, - SlashCommandResult, -}; -use fs::Fs; -use gpui::{App, Entity, Task, WeakEntity}; -use language::{BufferSnapshot, LspAdapterDelegate}; -use project::{Project, ProjectPath}; -use std::{ - fmt::Write, - path::Path, - sync::{Arc, atomic::AtomicBool}, -}; -use ui::prelude::*; -use workspace::Workspace; - -pub struct CargoWorkspaceSlashCommand; - -impl CargoWorkspaceSlashCommand { - async fn build_message(fs: Arc, path_to_cargo_toml: &Path) -> Result { - let buffer = fs.load(path_to_cargo_toml).await?; - let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?; - - let mut message = String::new(); - writeln!(message, "You are in a Rust project.")?; - - if let Some(workspace) = cargo_toml.workspace { - writeln!( - message, - "The project is a Cargo workspace with the following members:" - )?; - for member in workspace.members { - writeln!(message, "- {member}")?; - } - - if !workspace.default_members.is_empty() { - writeln!(message, "The default members are:")?; - for member in workspace.default_members { - writeln!(message, "- {member}")?; - } - } - - if !workspace.dependencies.is_empty() { - writeln!( - message, - "The following workspace dependencies are installed:" - )?; - for dependency in workspace.dependencies.keys() { - writeln!(message, "- {dependency}")?; - } - } - } else if let Some(package) = cargo_toml.package { - writeln!( - message, - "The project name is \"{name}\".", - name = package.name - )?; - - let description = package - .description - .as_ref() - .and_then(|description| description.get().ok().cloned()); - if let Some(description) = description.as_ref() { - writeln!(message, "It describes itself as \"{description}\".")?; - } - - if !cargo_toml.dependencies.is_empty() { - writeln!(message, "The following dependencies are installed:")?; - for dependency in cargo_toml.dependencies.keys() { - writeln!(message, "- {dependency}")?; - } - } - } - - Ok(message) - } - - fn path_to_cargo_toml(project: Entity, cx: &mut App) -> Option> { - let worktree = project.read(cx).worktrees(cx).next()?; - let worktree = worktree.read(cx); - let entry = worktree.entry_for_path("Cargo.toml")?; - let path = ProjectPath { - worktree_id: worktree.id(), - path: entry.path.clone(), - }; - Some(Arc::from( - project.read(cx).absolute_path(&path, cx)?.as_path(), - )) - } -} - -impl SlashCommand for CargoWorkspaceSlashCommand { - fn name(&self) -> String { - "cargo-workspace".into() - } - - fn description(&self) -> String { - "insert project workspace metadata".into() - } - - fn menu_text(&self) -> String { - "Insert Project Workspace Metadata".into() - } - - fn complete_argument( - self: Arc, - _arguments: &[String], - _cancel: Arc, - _workspace: Option>, - _window: &mut Window, - _cx: &mut App, - ) -> Task>> { - Task::ready(Err(anyhow!("this command does not require argument"))) - } - - fn requires_argument(&self) -> bool { - false - } - - fn run( - self: Arc, - _arguments: &[String], - _context_slash_command_output_sections: &[SlashCommandOutputSection], - _context_buffer: BufferSnapshot, - workspace: WeakEntity, - _delegate: Option>, - _window: &mut Window, - cx: &mut App, - ) -> Task { - let output = workspace.update(cx, |workspace, cx| { - let project = workspace.project().clone(); - let fs = workspace.project().read(cx).fs().clone(); - let path = Self::path_to_cargo_toml(project, cx); - let output = cx.background_spawn(async move { - let path = path.with_context(|| "Cargo.toml not found")?; - Self::build_message(fs, &path).await - }); - - cx.foreground_executor().spawn(async move { - let text = output.await?; - let range = 0..text.len(); - Ok(SlashCommandOutput { - text, - sections: vec![SlashCommandOutputSection { - range, - icon: IconName::FileTree, - label: "Project".into(), - metadata: None, - }], - run_commands_in_text: false, - } - .into_event_stream()) - }) - }); - output.unwrap_or_else(|error| Task::ready(Err(error))) - } -} From 96111c6ef3c138a2fa186a522da7633ffa998948 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Sep 2025 21:36:06 +0200 Subject: [PATCH 097/721] extension_host: Sanitize cwd path for ResolvedTask (#38357) Ensures build task's CWD paths use POSIX-friendly path separator on Windows host so that `std::path::Path` ops work as expected within the Wasm guest. Release Notes: - N/A --- crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index 84794d5386eda1517808d181eb259a3264f7b82d..e879ed0cb01f70f24a9b2b52438e1ff7d405f2d6 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -309,7 +309,14 @@ impl TryFrom for ResolvedTask { command: value.command.context("missing command")?, args: value.args, env: value.env.into_iter().collect(), - cwd: value.cwd.map(|s| s.to_string_lossy().into_owned()), + cwd: value.cwd.map(|s| { + let s = s.to_string_lossy(); + if cfg!(target_os = "windows") { + s.replace('\\', "/") + } else { + s.into_owned() + } + }), }) } } From 3c69144128241af6fc04640fb699526658c28a69 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 17 Sep 2025 16:22:50 -0400 Subject: [PATCH 098/721] Update release URLs in release actions (#38361) Release Notes: - N/A --- .github/workflows/community_release_actions.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/community_release_actions.yml b/.github/workflows/community_release_actions.yml index 37ade90574e76cd95755aad6b5601a43946a271c..0f7a73649e9e1180c78a66ddf54055bf66f243f9 100644 --- a/.github/workflows/community_release_actions.yml +++ b/.github/workflows/community_release_actions.yml @@ -1,3 +1,6 @@ +# IF YOU UPDATE THE NAME OF ANY GITHUB SECRET, YOU MUST CHERRY PICK THE COMMIT +# TO BOTH STABLE AND PREVIEW CHANNELS + name: Release Actions on: @@ -13,9 +16,9 @@ jobs: id: get-release-url run: | if [ "${{ github.event.release.prerelease }}" == "true" ]; then - URL="https://zed.dev/releases/preview/latest" + URL="https://zed.dev/releases/preview" else - URL="https://zed.dev/releases/stable/latest" + URL="https://zed.dev/releases/stable" fi echo "URL=$URL" >> "$GITHUB_OUTPUT" From 4912096599b42a97eaf10a14693892cd8a15aa3b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Sep 2025 16:31:03 -0400 Subject: [PATCH 099/721] collab: Remove unused feature flag queries (#38360) This PR removes the feature flag queries, as they were no longer used. Release Notes: - N/A --- crates/collab/src/db/queries/users.rs | 73 ------------------- crates/collab/src/db/tables.rs | 2 - crates/collab/src/db/tables/feature_flag.rs | 41 ----------- crates/collab/src/db/tables/user.rs | 23 ------ crates/collab/src/db/tables/user_feature.rs | 42 ----------- crates/collab/src/db/tests.rs | 1 - .../collab/src/db/tests/feature_flag_tests.rs | 66 ----------------- crates/collab/src/seed.rs | 58 +++------------ 8 files changed, 10 insertions(+), 296 deletions(-) delete mode 100644 crates/collab/src/db/tables/feature_flag.rs delete mode 100644 crates/collab/src/db/tables/user_feature.rs delete mode 100644 crates/collab/src/db/tests/feature_flag_tests.rs diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index 4b0f66fcbe09d23af58b0a30ffebf68455651fd8..89211130b88c69d4bf524bba25ae116790321d3e 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -342,79 +342,6 @@ impl Database { result } - /// Returns all feature flags. - pub async fn list_feature_flags(&self) -> Result> { - self.transaction(|tx| async move { Ok(feature_flag::Entity::find().all(&*tx).await?) }) - .await - } - - /// Creates a new feature flag. - pub async fn create_user_flag(&self, flag: &str, enabled_for_all: bool) -> Result { - self.transaction(|tx| async move { - let flag = feature_flag::Entity::insert(feature_flag::ActiveModel { - flag: ActiveValue::set(flag.to_string()), - enabled_for_all: ActiveValue::set(enabled_for_all), - ..Default::default() - }) - .exec(&*tx) - .await? - .last_insert_id; - - Ok(flag) - }) - .await - } - - /// Add the given user to the feature flag - pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> { - self.transaction(|tx| async move { - user_feature::Entity::insert(user_feature::ActiveModel { - user_id: ActiveValue::set(user), - feature_id: ActiveValue::set(flag), - }) - .exec(&*tx) - .await?; - - Ok(()) - }) - .await - } - - /// Returns the active flags for the user. - pub async fn get_user_flags(&self, user: UserId) -> Result> { - self.transaction(|tx| async move { - #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryAs { - Flag, - } - - let flags_enabled_for_all = feature_flag::Entity::find() - .filter(feature_flag::Column::EnabledForAll.eq(true)) - .select_only() - .column(feature_flag::Column::Flag) - .into_values::<_, QueryAs>() - .all(&*tx) - .await?; - - let flags_enabled_for_user = user::Model { - id: user, - ..Default::default() - } - .find_linked(user::UserFlags) - .select_only() - .column(feature_flag::Column::Flag) - .into_values::<_, QueryAs>() - .all(&*tx) - .await?; - - let mut all_flags = HashSet::from_iter(flags_enabled_for_all); - all_flags.extend(flags_enabled_for_user); - - Ok(all_flags.into_iter().collect()) - }) - .await - } - pub async fn get_users_missing_github_user_created_at(&self) -> Result> { self.transaction(|tx| async move { Ok(user::Entity::find() diff --git a/crates/collab/src/db/tables.rs b/crates/collab/src/db/tables.rs index 0082a9fb030a27e4be13af725f08ea9c82217377..32c4570af5893b503f0fcfdaa1759616cf9be387 100644 --- a/crates/collab/src/db/tables.rs +++ b/crates/collab/src/db/tables.rs @@ -13,7 +13,6 @@ pub mod contributor; pub mod embedding; pub mod extension; pub mod extension_version; -pub mod feature_flag; pub mod follower; pub mod language_server; pub mod notification; @@ -29,7 +28,6 @@ pub mod room_participant; pub mod server; pub mod signup; pub mod user; -pub mod user_feature; pub mod worktree; pub mod worktree_diagnostic_summary; pub mod worktree_entry; diff --git a/crates/collab/src/db/tables/feature_flag.rs b/crates/collab/src/db/tables/feature_flag.rs deleted file mode 100644 index 5bbfedd71e70b7f1cc58219475c49c28bc62ff3d..0000000000000000000000000000000000000000 --- a/crates/collab/src/db/tables/feature_flag.rs +++ /dev/null @@ -1,41 +0,0 @@ -use sea_orm::entity::prelude::*; - -use crate::db::FlagId; - -#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] -#[sea_orm(table_name = "feature_flags")] -pub struct Model { - #[sea_orm(primary_key)] - pub id: FlagId, - pub flag: String, - pub enabled_for_all: bool, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::user_feature::Entity")] - UserFeature, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::UserFeature.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} - -pub struct FlaggedUsers; - -impl Linked for FlaggedUsers { - type FromEntity = Entity; - - type ToEntity = super::user::Entity; - - fn link(&self) -> Vec { - vec![ - super::user_feature::Relation::Flag.def().rev(), - super::user_feature::Relation::User.def(), - ] - } -} diff --git a/crates/collab/src/db/tables/user.rs b/crates/collab/src/db/tables/user.rs index af43fe300a6cc1224487541ca72af9d887a6fae3..8e8c03fafc92127f8754f473e04dfab39592ea14 100644 --- a/crates/collab/src/db/tables/user.rs +++ b/crates/collab/src/db/tables/user.rs @@ -35,8 +35,6 @@ pub enum Relation { HostedProjects, #[sea_orm(has_many = "super::channel_member::Entity")] ChannelMemberships, - #[sea_orm(has_many = "super::user_feature::Entity")] - UserFeatures, #[sea_orm(has_one = "super::contributor::Entity")] Contributor, } @@ -84,25 +82,4 @@ impl Related for Entity { } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::UserFeatures.def() - } -} - impl ActiveModelBehavior for ActiveModel {} - -pub struct UserFlags; - -impl Linked for UserFlags { - type FromEntity = Entity; - - type ToEntity = super::feature_flag::Entity; - - fn link(&self) -> Vec { - vec![ - super::user_feature::Relation::User.def().rev(), - super::user_feature::Relation::Flag.def(), - ] - } -} diff --git a/crates/collab/src/db/tables/user_feature.rs b/crates/collab/src/db/tables/user_feature.rs deleted file mode 100644 index cc24b5e796342f7733f59933362d46a0df2be112..0000000000000000000000000000000000000000 --- a/crates/collab/src/db/tables/user_feature.rs +++ /dev/null @@ -1,42 +0,0 @@ -use sea_orm::entity::prelude::*; - -use crate::db::{FlagId, UserId}; - -#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] -#[sea_orm(table_name = "user_features")] -pub struct Model { - #[sea_orm(primary_key)] - pub user_id: UserId, - #[sea_orm(primary_key)] - pub feature_id: FlagId, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::feature_flag::Entity", - from = "Column::FeatureId", - to = "super::feature_flag::Column::Id" - )] - Flag, - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::UserId", - to = "super::user::Column::Id" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Flag.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 25e03f1320a25455ede347b43477761d591fbd57..141262d5e94a4bf1d4d897e78f6281ab9ee3ccfc 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -6,7 +6,6 @@ mod db_tests; #[cfg(target_os = "macos")] mod embedding_tests; mod extension_tests; -mod feature_flag_tests; mod user_tests; use crate::migrations::run_database_migrations; diff --git a/crates/collab/src/db/tests/feature_flag_tests.rs b/crates/collab/src/db/tests/feature_flag_tests.rs deleted file mode 100644 index 0e68dcc941cdb2488c3822548dada56746667bc2..0000000000000000000000000000000000000000 --- a/crates/collab/src/db/tests/feature_flag_tests.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::{ - db::{Database, NewUserParams}, - test_both_dbs, -}; -use pretty_assertions::assert_eq; -use std::sync::Arc; - -test_both_dbs!( - test_get_user_flags, - test_get_user_flags_postgres, - test_get_user_flags_sqlite -); - -async fn test_get_user_flags(db: &Arc) { - let user_1 = db - .create_user( - "user1@example.com", - None, - false, - NewUserParams { - github_login: "user1".to_string(), - github_user_id: 1, - }, - ) - .await - .unwrap() - .user_id; - - let user_2 = db - .create_user( - "user2@example.com", - None, - false, - NewUserParams { - github_login: "user2".to_string(), - github_user_id: 2, - }, - ) - .await - .unwrap() - .user_id; - - const FEATURE_FLAG_ONE: &str = "brand-new-ux"; - const FEATURE_FLAG_TWO: &str = "cool-feature"; - const FEATURE_FLAG_THREE: &str = "feature-enabled-for-everyone"; - - let feature_flag_one = db.create_user_flag(FEATURE_FLAG_ONE, false).await.unwrap(); - let feature_flag_two = db.create_user_flag(FEATURE_FLAG_TWO, false).await.unwrap(); - db.create_user_flag(FEATURE_FLAG_THREE, true).await.unwrap(); - - db.add_user_flag(user_1, feature_flag_one).await.unwrap(); - db.add_user_flag(user_1, feature_flag_two).await.unwrap(); - - db.add_user_flag(user_2, feature_flag_one).await.unwrap(); - - let mut user_1_flags = db.get_user_flags(user_1).await.unwrap(); - user_1_flags.sort(); - assert_eq!( - user_1_flags, - &[FEATURE_FLAG_ONE, FEATURE_FLAG_TWO, FEATURE_FLAG_THREE] - ); - - let mut user_2_flags = db.get_user_flags(user_2).await.unwrap(); - user_2_flags.sort(); - assert_eq!(user_2_flags, &[FEATURE_FLAG_ONE, FEATURE_FLAG_THREE]); -} diff --git a/crates/collab/src/seed.rs b/crates/collab/src/seed.rs index 2d070b30abada79dc177b2b600d9ecc40aa472e1..5f5779e1e4990d1a03461bb3ec2222e82d9f544e 100644 --- a/crates/collab/src/seed.rs +++ b/crates/collab/src/seed.rs @@ -46,27 +46,6 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result let mut first_user = None; let mut others = vec![]; - let flag_names = ["language-models"]; - let mut flags = Vec::new(); - - let existing_feature_flags = db.list_feature_flags().await?; - - for flag_name in flag_names { - if existing_feature_flags - .iter() - .any(|flag| flag.flag == flag_name) - { - log::info!("Flag {flag_name:?} already exists"); - continue; - } - - let flag = db - .create_user_flag(flag_name, false) - .await - .unwrap_or_else(|err| panic!("failed to create flag: '{flag_name}': {err}")); - flags.push(flag); - } - for admin_login in seed_config.admins { let user = fetch_github::( &client, @@ -90,15 +69,6 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result } else { others.push(user.user_id) } - - for flag in &flags { - db.add_user_flag(user.user_id, *flag) - .await - .context(format!( - "Unable to enable flag '{}' for user '{}'", - flag, user.user_id - ))?; - } } for channel in seed_config.channels { @@ -126,24 +96,16 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result for github_user in github_users { log::info!("Seeding {:?} from GitHub", github_user.login); - let user = db - .update_or_create_user_by_github_account( - &github_user.login, - github_user.id, - github_user.email.as_deref(), - github_user.name.as_deref(), - github_user.created_at, - None, - ) - .await - .expect("failed to insert user"); - - for flag in &flags { - db.add_user_flag(user.id, *flag).await.context(format!( - "Unable to enable flag '{}' for user '{}'", - flag, user.id - ))?; - } + db.update_or_create_user_by_github_account( + &github_user.login, + github_user.id, + github_user.email.as_deref(), + github_user.name.as_deref(), + github_user.created_at, + None, + ) + .await + .expect("failed to insert user"); } Ok(()) From ea473eea875dcdbca05a3e98d10117724cdad935 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 17 Sep 2025 16:45:47 -0400 Subject: [PATCH 100/721] acp: Fix agent servers sometimes not being registered when Zed starts (#38330) In local projects, initialize the list of agents in the agent server store immediately. Previously we were initializing the list only after a delay, in an attempt to avoid sending the `ExternalAgentsUpdated` message to the downstream client (if any) before its handlers were initialized. But we already have a separate codepath for that situation, in the `AgentServerStore::shared`, and we can insert the delay in that place instead. Release Notes: - acp: Fixed a bug where starting an external agent thread soon after Zed starts up would show a "not registered" error. --------- Co-authored-by: Michael Co-authored-by: Agus --- .../src/tree_sitter_index.rs | 14 ++---- crates/project/src/agent_server_store.rs | 43 ++++++++----------- crates/project/src/git_store/conflict_set.rs | 6 +-- crates/remote_server/src/headless_project.rs | 2 +- 4 files changed, 26 insertions(+), 39 deletions(-) diff --git a/crates/edit_prediction_context/src/tree_sitter_index.rs b/crates/edit_prediction_context/src/tree_sitter_index.rs index 4dc00941fe1b8a7a095fffd5605b040001c02eb7..f905aa7a01f29d26083d219bc8d2bd600847036a 100644 --- a/crates/edit_prediction_context/src/tree_sitter_index.rs +++ b/crates/edit_prediction_context/src/tree_sitter_index.rs @@ -506,7 +506,6 @@ mod tests { use super::*; use std::{path::Path, sync::Arc}; - use futures::channel::oneshot; use gpui::TestAppContext; use indoc::indoc; use language::{Language, LanguageConfig, LanguageId, LanguageMatcher, tree_sitter_rust}; @@ -655,17 +654,10 @@ mod tests { expect_file_decl("a.rs", &decls[1], &project, cx); }); - // Drop the buffer and wait for release - let (release_tx, release_rx) = oneshot::channel(); - cx.update(|cx| { - cx.observe_release(&buffer, |_, _| { - release_tx.send(()).ok(); - }) - .detach(); + // Need to trigger flush_effects so that the observe_release handler will run. + cx.update(|_cx| { + drop(buffer); }); - drop(buffer); - cx.run_until_parked(); - release_rx.await.ok(); cx.run_until_parked(); index.read_with(cx, |index, cx| { diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index bdb2297624e4a404cb3c918f07eab15004944f97..abe27710d8969d6e365d9a2539855fd625645134 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -234,7 +234,7 @@ impl AgentServerStore { let subscription = cx.observe_global::(|this, cx| { this.agent_servers_settings_changed(cx); }); - let this = Self { + let mut this = Self { state: AgentServerStoreState::Local { node_runtime, fs, @@ -245,14 +245,7 @@ impl AgentServerStore { }, external_agents: Default::default(), }; - cx.spawn(async move |this, cx| { - cx.background_executor().timer(Duration::from_secs(1)).await; - this.update(cx, |this, cx| { - this.agent_servers_settings_changed(cx); - }) - .ok(); - }) - .detach(); + this.agent_servers_settings_changed(cx); this } @@ -305,22 +298,29 @@ impl AgentServerStore { } } - pub fn shared(&mut self, project_id: u64, client: AnyProtoClient) { + pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context) { match &mut self.state { AgentServerStoreState::Local { downstream_client, .. } => { - client - .send(proto::ExternalAgentsUpdated { - project_id, - names: self - .external_agents + *downstream_client = Some((project_id, client.clone())); + // Send the current list of external agents downstream, but only after a delay, + // to avoid having the message arrive before the downstream project's agent server store + // sets up its handlers. + cx.spawn(async move |this, cx| { + cx.background_executor().timer(Duration::from_secs(1)).await; + let names = this.update(cx, |this, _| { + this.external_agents .keys() .map(|name| name.to_string()) - .collect(), - }) - .log_err(); - *downstream_client = Some((project_id, client)); + .collect() + })?; + client + .send(proto::ExternalAgentsUpdated { project_id, names }) + .log_err(); + anyhow::Ok(()) + }) + .detach(); } AgentServerStoreState::Remote { .. } => { debug_panic!( @@ -721,11 +721,6 @@ struct RemoteExternalAgentServer { new_version_available_tx: Option>>, } -// new method: status_updated -// does nothing in the all-local case -// for RemoteExternalAgentServer, sends on the stored tx -// etc. - impl ExternalAgentServer for RemoteExternalAgentServer { fn get_command( &mut self, diff --git a/crates/project/src/git_store/conflict_set.rs b/crates/project/src/git_store/conflict_set.rs index 313a1e90adc2fde8a62dbe6aa60b4d3a366af22c..2bcfc75b32da3c5a4860cc72f3266bff38f022e3 100644 --- a/crates/project/src/git_store/conflict_set.rs +++ b/crates/project/src/git_store/conflict_set.rs @@ -257,7 +257,7 @@ impl EventEmitter for ConflictSet {} mod tests { use std::{path::Path, sync::mpsc}; - use crate::{Project, project_settings::ProjectSettings}; + use crate::Project; use super::*; use fs::FakeFs; @@ -484,7 +484,7 @@ mod tests { cx.update(|cx| { settings::init(cx); WorktreeSettings::register(cx); - ProjectSettings::register(cx); + Project::init_settings(cx); AllLanguageSettings::register(cx); }); let initial_text = " @@ -585,7 +585,7 @@ mod tests { cx.update(|cx| { settings::init(cx); WorktreeSettings::register(cx); - ProjectSettings::register(cx); + Project::init_settings(cx); AllLanguageSettings::register(cx); }); diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index f107ff2c8f8860a621ad5c637e6fa34b54734a6d..504e6a4bfe852bad07c72a01a323e5de22d1a4c2 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -197,7 +197,7 @@ impl HeadlessProject { let agent_server_store = cx.new(|cx| { let mut agent_server_store = AgentServerStore::local(node_runtime.clone(), fs.clone(), environment, cx); - agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone()); + agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx); agent_server_store }); From eaa1cb0ca32d978f2424c417fb10f4e622fb40cb Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 18 Sep 2025 00:02:44 -0400 Subject: [PATCH 101/721] acp: Add a basic test for ACP remoting (#38381) Tests that the downstream project can see custom agents configured in the remote server's settings, and that it constructs an appropriate `AgentServerCommand`. Release Notes: - N/A --- Cargo.lock | 2 + crates/remote_server/Cargo.toml | 2 + .../remote_server/src/remote_editing_tests.rs | 88 ++++++++++++++++++- crates/settings/src/settings_store.rs | 11 +++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 13acb75ee146f412f704ed7d2c209ac59928a3cc..294127b598926ddae6924f0af1dd273416cf1e1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13981,6 +13981,7 @@ dependencies = [ "clap", "client", "clock", + "collections", "crash-handler", "crashes", "dap", @@ -14009,6 +14010,7 @@ dependencies = [ "minidumper", "node_runtime", "paths", + "pretty_assertions", "project", "proto", "release_channel", diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index 59b2af0f410c98385cf13a271833980aacd6c8bc..b1f12fb0a8133b95259b38bbec22dbd031937cd7 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -77,6 +77,7 @@ assistant_tool.workspace = true assistant_tools.workspace = true client = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] } +collections.workspace = true dap = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] } workspace = { workspace = true, features = ["test-support"] } @@ -85,6 +86,7 @@ gpui = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] } node_runtime = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true project = { workspace = true, features = ["test-support"] } remote = { workspace = true, features = ["test-support"] } language_model = { workspace = true, features = ["test-support"] } diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index c0ccaf900d18ee176bab7193c2bfb65b8555318d..cb486732c0a0a63e7f6d5d5aed7fe0499ef98b80 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -6,6 +6,7 @@ use assistant_tool::{Tool as _, ToolResultContent}; use assistant_tools::{ReadFileTool, ReadFileToolInput}; use client::{Client, UserStore}; use clock::FakeSystemClock; +use collections::{HashMap, HashSet}; use language_model::{LanguageModelRequest, fake_provider::FakeLanguageModel}; use extension::ExtensionHostProxy; @@ -20,6 +21,7 @@ use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, Language use node_runtime::NodeRuntime; use project::{ Project, ProjectPath, + agent_server_store::AgentServerCommand, search::{SearchQuery, SearchResult}, }; use remote::RemoteClient; @@ -27,7 +29,6 @@ use serde_json::json; use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content}; use smol::stream::StreamExt; use std::{ - collections::HashSet, path::{Path, PathBuf}, sync::Arc, }; @@ -1770,6 +1771,91 @@ async fn test_remote_agent_fs_tool_calls(cx: &mut TestAppContext, server_cx: &mu does_not_exist_result.output.await.unwrap_err(); } +#[gpui::test] +async fn test_remote_external_agent_server( + cx: &mut TestAppContext, + server_cx: &mut TestAppContext, +) { + let fs = FakeFs::new(server_cx.executor()); + fs.insert_tree(path!("/project"), json!({})).await; + + let (project, _headless_project) = init_test(&fs, cx, server_cx).await; + project + .update(cx, |project, cx| { + project.find_or_create_worktree(path!("/project"), true, cx) + }) + .await + .unwrap(); + let names = project.update(cx, |project, cx| { + project + .agent_server_store() + .read(cx) + .external_agents() + .map(|name| name.to_string()) + .collect::>() + }); + pretty_assertions::assert_eq!(names, ["gemini", "claude"]); + server_cx.update_global::(|settings_store, cx| { + settings_store + .set_raw_server_settings( + Some(json!({ + "agent_servers": { + "foo": { + "command": "foo-cli", + "args": ["--flag"], + "env": { + "VAR": "val" + } + } + } + })), + cx, + ) + .unwrap(); + }); + server_cx.run_until_parked(); + cx.run_until_parked(); + let names = project.update(cx, |project, cx| { + project + .agent_server_store() + .read(cx) + .external_agents() + .map(|name| name.to_string()) + .collect::>() + }); + pretty_assertions::assert_eq!(names, ["gemini", "foo", "claude"]); + let (command, root, login) = project + .update(cx, |project, cx| { + project.agent_server_store().update(cx, |store, cx| { + store + .get_external_agent(&"foo".into()) + .unwrap() + .get_command( + None, + HashMap::from_iter([("OTHER_VAR".into(), "other-val".into())]), + None, + None, + &mut cx.to_async(), + ) + }) + }) + .await + .unwrap(); + assert_eq!( + command, + AgentServerCommand { + path: "ssh".into(), + args: vec!["foo-cli".into(), "--flag".into()], + env: Some(HashMap::from_iter([ + ("VAR".into(), "val".into()), + ("OTHER_VAR".into(), "other-val".into()) + ])) + } + ); + assert_eq!(&PathBuf::from(root), paths::home_dir()); + assert!(login.is_none()); +} + pub async fn init_test( server_fs: &Arc, cx: &mut TestAppContext, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index abce84daf7cf2b0adc304894476d9c763e5e1a3d..e06e423e92aa3fe093c69d8436545d4a13d17a82 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -487,6 +487,17 @@ impl SettingsStore { Ok(()) } + /// Replaces current settings with the values from the given JSON. + pub fn set_raw_server_settings( + &mut self, + new_settings: Option, + cx: &mut App, + ) -> Result<()> { + self.raw_server_settings = new_settings; + self.recompute_values(None, cx)?; + Ok(()) + } + /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator { self.raw_user_settings From 4b1e78cd5c4ed69a168409620a272fc63fa2f433 Mon Sep 17 00:00:00 2001 From: Miao Date: Thu, 18 Sep 2025 16:15:52 +0800 Subject: [PATCH 102/721] terminal: Fix COLORTERM regression for true color support (#38379) Closes #38304 Release Notes: - Fixed true color detection regression by setting `COLORTERM=truecolor` --- Reason: The regression is possibly introduced in [pr#36576: Inject venv environment via the toolchain](https://github.com/zed-industries/zed/pull/36576/files#diff-6f30387876b79f1de44f8193401d6c8fb49a2156479c4f2e32bc922ec5d54d76), where `alacritty_terminal::tty::setup_env();` is removed. The `alacritty_terminal::tty::setup_env();` does 2 things, which sets `TERM` & `COLORTERM` envvar. ```rs /// Setup environment variables. pub fn setup_env() { // Default to 'alacritty' terminfo if it is available, otherwise // default to 'xterm-256color'. May be overridden by user's config // below. let terminfo = if terminfo_exists("alacritty") { "alacritty" } else { "xterm-256color" }; unsafe { env::set_var("TERM", terminfo) }; // Advertise 24-bit color support. unsafe { env::set_var("COLORTERM", "truecolor") }; } ``` --- crates/terminal/src/terminal.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a07aef5f7b4da90373bcbf7c406dd8277cb09387..0fce02a97b04484b5a91d6d43b456ecfb1f75f15 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -364,6 +364,7 @@ impl TerminalBuilder { env.insert("ZED_TERM".to_string(), "true".to_string()); env.insert("TERM_PROGRAM".to_string(), "zed".to_string()); env.insert("TERM".to_string(), "xterm-256color".to_string()); + env.insert("COLORTERM".to_string(), "truecolor".to_string()); env.insert( "TERM_PROGRAM_VERSION".to_string(), release_channel::AppVersion::global(cx).to_string(), From d85a6db6a3f1115d60cea103d33944894a2b2d4c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 18 Sep 2025 10:22:26 +0200 Subject: [PATCH 103/721] git_ui: Use margin instead of padding for blame entries (#38397) This makes the hover background change keep a visible border element between the gutter and blame entries Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/git_ui/src/blame_ui.rs | 139 ++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/crates/git_ui/src/blame_ui.rs b/crates/git_ui/src/blame_ui.rs index ad5823c1674353f2e0531e5f71e1420fe464bfe6..6229c80c739ee73b20fe2640f30f9f751a1b4411 100644 --- a/crates/git_ui/src/blame_ui.rs +++ b/crates/git_ui/src/blame_ui.rs @@ -47,75 +47,84 @@ impl BlameRenderer for GitBlameRenderer { let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED); Some( - h_flex() - .w_full() - .justify_between() - .font_family(style.font().family) - .line_height(style.line_height) - .id(("blame", ix)) - .text_color(cx.theme().status().hint) - .pr_2() - .gap_2() + div() + .mr_2() .child( h_flex() - .items_center() + .w_full() + .justify_between() + .font_family(style.font().family) + .line_height(style.line_height) + .id(("blame", ix)) + .text_color(cx.theme().status().hint) .gap_2() - .child(div().text_color(sha_color).child(short_commit_id)) - .child(name), - ) - .child(relative_timestamp) - .hover(|style| style.bg(cx.theme().colors().element_hover)) - .cursor_pointer() - .on_mouse_down(MouseButton::Right, { - let blame_entry = blame_entry.clone(); - let details = details.clone(); - move |event, window, cx| { - deploy_blame_entry_context_menu( - &blame_entry, - details.as_ref(), - editor.clone(), - event.position, - window, - cx, - ); - } - }) - .on_click({ - let blame_entry = blame_entry.clone(); - let repository = repository.clone(); - let workspace = workspace.clone(); - move |_, window, cx| { - CommitView::open( - CommitSummary { - sha: blame_entry.sha.to_string().into(), - subject: blame_entry.summary.clone().unwrap_or_default().into(), - commit_timestamp: blame_entry.committer_time.unwrap_or_default(), - author_name: blame_entry - .committer_name - .clone() - .unwrap_or_default() - .into(), - has_parent: true, - }, - repository.downgrade(), - workspace.clone(), - window, - cx, - ) - } - }) - .hoverable_tooltip(move |_window, cx| { - cx.new(|cx| { - CommitTooltip::blame_entry( - &blame_entry, - details.clone(), - repository.clone(), - workspace.clone(), - cx, + .child( + h_flex() + .items_center() + .gap_2() + .child(div().text_color(sha_color).child(short_commit_id)) + .child(name), ) - }) - .into() - }) + .child(relative_timestamp) + .hover(|style| style.bg(cx.theme().colors().element_hover)) + .cursor_pointer() + .on_mouse_down(MouseButton::Right, { + let blame_entry = blame_entry.clone(); + let details = details.clone(); + move |event, window, cx| { + deploy_blame_entry_context_menu( + &blame_entry, + details.as_ref(), + editor.clone(), + event.position, + window, + cx, + ); + } + }) + .on_click({ + let blame_entry = blame_entry.clone(); + let repository = repository.clone(); + let workspace = workspace.clone(); + move |_, window, cx| { + CommitView::open( + CommitSummary { + sha: blame_entry.sha.to_string().into(), + subject: blame_entry + .summary + .clone() + .unwrap_or_default() + .into(), + commit_timestamp: blame_entry + .committer_time + .unwrap_or_default(), + author_name: blame_entry + .committer_name + .clone() + .unwrap_or_default() + .into(), + has_parent: true, + }, + repository.downgrade(), + workspace.clone(), + window, + cx, + ) + } + }) + .hoverable_tooltip(move |_window, cx| { + cx.new(|cx| { + CommitTooltip::blame_entry( + &blame_entry, + details.clone(), + repository.clone(), + workspace.clone(), + cx, + ) + }) + .into() + }), + ) .into_any(), ) } From ed46e2ca775b69126f6916534d4f027e51cae452 Mon Sep 17 00:00:00 2001 From: Romans Malinovskis Date: Thu, 18 Sep 2025 09:47:15 +0100 Subject: [PATCH 104/721] helix: Apply modification (e.g. switch case) on a single character only in helix mode (#38119) Closes #34192 Without selection, only current character would be affected. Also if #38117 is merged too, then transformations in SelectMode behave correctly too and selection is not collapsed. Release Notes: - helix: Implemented `~`, `` ` ``, `` Alt-` `` correctly in normal and select modes --------- Co-authored-by: Jakub Konka --- assets/keymaps/vim.json | 5 ++--- crates/vim/src/normal/convert.rs | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 8464e03d251afc166ac45a349894ecf2f7247944..817198659657814dcc597926d689063ae2182c78 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -442,9 +442,8 @@ ">": "vim::Indent", "<": "vim::Outdent", "=": "vim::AutoIndent", - "g u": "vim::PushLowercase", - "g shift-u": "vim::PushUppercase", - "g ~": "vim::PushOppositeCase", + "`": "vim::ConvertToLowerCase", + "alt-`": "vim::ConvertToUpperCase", "g q": "vim::PushRewrap", "g w": "vim::PushRewrap", "insert": "vim::InsertBefore", diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index d5875fe963778756daf5cd78f452b3436b53642f..11d040850d341155bf428ebc337cc9e3f4cc42c3 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -214,11 +214,10 @@ impl Vim { Mode::HelixNormal | Mode::HelixSelect => { if selection.is_empty() { - // Handle empty selection by operating on the whole word - let (word_range, _) = snapshot.surrounding_word(selection.start, false); - let word_start = snapshot.offset_to_point(word_range.start); - let word_end = snapshot.offset_to_point(word_range.end); - ranges.push(word_start..word_end); + // Handle empty selection by operating on single character + let start = selection.start; + let end = snapshot.clip_point(start + Point::new(0, 1), Bias::Right); + ranges.push(start..end); cursor_positions.push(selection.start..selection.start); } else { ranges.push(selection.start..selection.end); @@ -445,15 +444,26 @@ mod test { cx.simulate_keystrokes("~"); cx.assert_state("«HELLO WORLDˇ»", Mode::HelixNormal); - // Cursor-only (empty) selection + // Cursor-only (empty) selection - switch case cx.set_state("The ˇquick brown", Mode::HelixNormal); cx.simulate_keystrokes("~"); - cx.assert_state("The ˇQUICK brown", Mode::HelixNormal); + cx.assert_state("The ˇQuick brown", Mode::HelixNormal); + cx.simulate_keystrokes("~"); + cx.assert_state("The ˇquick brown", Mode::HelixNormal); + + // Cursor-only (empty) selection - switch to uppercase and lowercase explicitly + cx.set_state("The ˇquick brown", Mode::HelixNormal); + cx.simulate_keystrokes("alt-`"); + cx.assert_state("The ˇQuick brown", Mode::HelixNormal); + cx.simulate_keystrokes("`"); + cx.assert_state("The ˇquick brown", Mode::HelixNormal); // With `e` motion (which extends selection to end of word in Helix) cx.set_state("The ˇquick brown fox", Mode::HelixNormal); cx.simulate_keystrokes("e"); cx.simulate_keystrokes("~"); cx.assert_state("The «QUICKˇ» brown fox", Mode::HelixNormal); + + // Cursor-only } } From 32c868ff7d50f4163072c72bc2a09cf710e7b521 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 18 Sep 2025 11:38:59 +0200 Subject: [PATCH 105/721] acp: Fix behavior of read_text_file for ACP agents (#38401) We were incorrectly handling the line number as well as stripping out line breaks when returning portions of files. It also makes sure following is updated even when we load a snapshot from cache, which wasn't the case before. We also are able to load the text via a range in the snapshot, rather than allocating a string for the entire file and then another after iterating over lines in the file. Release Notes: - acp: Fix incorrect behavior when ACP agents requested to read portions of files. --- crates/acp_thread/src/acp_thread.rs | 140 +++++++++++++++++++++------- 1 file changed, 106 insertions(+), 34 deletions(-) diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 68e5266f06aa8bddfaa252bdc1cf5b21891c7f10..f2327ca70b104de12f44d74aacd1a5a2bb1eca3b 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -1781,6 +1781,9 @@ impl AcpThread { reuse_shared_snapshot: bool, cx: &mut Context, ) -> Task> { + // Args are 1-based, move to 0-based + let line = line.unwrap_or_default().saturating_sub(1); + let limit = limit.unwrap_or(u32::MAX); let project = self.project.clone(); let action_log = self.action_log.clone(); cx.spawn(async move |this, cx| { @@ -1808,44 +1811,37 @@ impl AcpThread { action_log.update(cx, |action_log, cx| { action_log.buffer_read(buffer.clone(), cx); })?; - project.update(cx, |project, cx| { - let position = buffer - .read(cx) - .snapshot() - .anchor_before(Point::new(line.unwrap_or_default(), 0)); - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position, - }), - cx, - ); - })?; - buffer.update(cx, |buffer, _| buffer.snapshot())? + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?; + this.update(cx, |this, _| { + this.shared_buffers.insert(buffer.clone(), snapshot.clone()); + })?; + snapshot }; - this.update(cx, |this, _| { - let text = snapshot.text(); - this.shared_buffers.insert(buffer.clone(), snapshot); - if line.is_none() && limit.is_none() { - return Ok(text); - } - let limit = limit.unwrap_or(u32::MAX) as usize; - let Some(line) = line else { - return Ok(text.lines().take(limit).collect::()); - }; + let max_point = snapshot.max_point(); + if line >= max_point.row { + anyhow::bail!( + "Attempting to read beyond the end of the file, line {}:{}", + max_point.row + 1, + max_point.column + ); + } - let count = text.lines().count(); - if count < line as usize { - anyhow::bail!("There are only {} lines", count); - } - Ok(text - .lines() - .skip(line as usize + 1) - .take(limit) - .collect::()) - })? + let start = snapshot.anchor_before(Point::new(line, 0)); + let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0)); + + project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: start, + }), + cx, + ); + })?; + + Ok(snapshot.text_for_range(start..end).collect::()) }) } @@ -2391,6 +2387,82 @@ mod tests { request.await.unwrap(); } + #[gpui::test] + async fn test_reading_from_line(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree(path!("/tmp"), json!({"foo": "one\ntwo\nthree\nfour\n"})) + .await; + let project = Project::test(fs.clone(), [], cx).await; + project + .update(cx, |project, cx| { + project.find_or_create_worktree(path!("/tmp/foo"), true, cx) + }) + .await + .unwrap(); + + let connection = Rc::new(FakeAgentConnection::new()); + + let thread = cx + .update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx)) + .await + .unwrap(); + + // Whole file + let content = thread + .update(cx, |thread, cx| { + thread.read_text_file(path!("/tmp/foo").into(), None, None, false, cx) + }) + .await + .unwrap(); + + assert_eq!(content, "one\ntwo\nthree\nfour\n"); + + // Only start line + let content = thread + .update(cx, |thread, cx| { + thread.read_text_file(path!("/tmp/foo").into(), Some(3), None, false, cx) + }) + .await + .unwrap(); + + assert_eq!(content, "three\nfour\n"); + + // Only limit + let content = thread + .update(cx, |thread, cx| { + thread.read_text_file(path!("/tmp/foo").into(), None, Some(2), false, cx) + }) + .await + .unwrap(); + + assert_eq!(content, "one\ntwo\n"); + + // Range + let content = thread + .update(cx, |thread, cx| { + thread.read_text_file(path!("/tmp/foo").into(), Some(2), Some(2), false, cx) + }) + .await + .unwrap(); + + assert_eq!(content, "two\nthree\n"); + + // Invalid + let err = thread + .update(cx, |thread, cx| { + thread.read_text_file(path!("/tmp/foo").into(), Some(5), Some(2), false, cx) + }) + .await + .unwrap_err(); + + assert_eq!( + err.to_string(), + "Attempting to read beyond the end of the file, line 5:0" + ); + } + #[gpui::test] async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) { init_test(cx); From 9f9e8063fccfaebd5a3c8939c87ef206e0bdca58 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 18 Sep 2025 12:03:35 +0200 Subject: [PATCH 106/721] workspace: Pop a toast if manually spawning a task fails (#38405) Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/workspace/src/tasks.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/workspace/src/tasks.rs b/crates/workspace/src/tasks.rs index 71394c874ae988d7b8fef3e3a224d25e1c290640..5f52cb49e74d67619b9ba7c033a33fe8a7ad51c8 100644 --- a/crates/workspace/src/tasks.rs +++ b/crates/workspace/src/tasks.rs @@ -8,7 +8,7 @@ use remote::ConnectionState; use task::{DebugScenario, ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate}; use ui::Window; -use crate::Workspace; +use crate::{Toast, Workspace, notifications::NotificationId}; impl Workspace { pub fn schedule_task( @@ -73,8 +73,10 @@ impl Workspace { if let Some(terminal_provider) = self.terminal_provider.as_ref() { let task_status = terminal_provider.spawn(spawn_in_terminal, window, cx); - let task = cx.background_spawn(async move { - match task_status.await { + + let task = cx.spawn(async |w, cx| { + let res = cx.background_spawn(task_status).await; + match res { Some(Ok(status)) => { if status.success() { log::debug!("Task spawn succeeded"); @@ -82,9 +84,15 @@ impl Workspace { log::debug!("Task spawn failed, code: {:?}", status.code()); } } - Some(Err(e)) => log::error!("Task spawn failed: {e:#}"), + Some(Err(e)) => { + log::error!("Task spawn failed: {e:#}"); + _ = w.update(cx, |w, cx| { + let id = NotificationId::unique::(); + w.show_toast(Toast::new(id, format!("Task spawn failed: {e}")), cx); + }) + } None => log::debug!("Task spawn got cancelled"), - } + }; }); self.scheduled_tasks.push(task); } From ca05ff89f434319f00c7d99a030bfd767d6edb39 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 18 Sep 2025 12:05:05 +0200 Subject: [PATCH 107/721] agent2: More efficent read file tool (#38407) Before we were always reading the entire file into memory as a string. Now we only read the range that is actually requested. Release Notes: - N/A --- crates/agent2/src/tools/read_file_tool.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index 87163e769c26b0cee053fcf149d047fc451c470f..7be83d8bef174bf9b2799d67eb6240fcae4e5bb6 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -201,7 +201,6 @@ impl AgentTool for ReadFileTool { // Check if specific line ranges are provided let result = if input.start_line.is_some() || input.end_line.is_some() { let result = buffer.read_with(cx, |buffer, _cx| { - let text = buffer.text(); // .max(1) because despite instructions to be 1-indexed, sometimes the model passes 0. let start = input.start_line.unwrap_or(1).max(1); let start_row = start - 1; @@ -210,13 +209,13 @@ impl AgentTool for ReadFileTool { anchor = Some(buffer.anchor_before(Point::new(start_row, column))); } - let lines = text.split('\n').skip(start_row as usize); - if let Some(end) = input.end_line { - let count = end.saturating_sub(start).saturating_add(1); // Ensure at least 1 line - itertools::intersperse(lines.take(count as usize), "\n").collect::() - } else { - itertools::intersperse(lines, "\n").collect::() + let mut end_row = input.end_line.unwrap_or(u32::MAX); + if end_row <= start_row { + end_row = start_row + 1; // read at least one lines } + let start = buffer.anchor_before(Point::new(start_row, 0)); + let end = buffer.anchor_before(Point::new(end_row, 0)); + buffer.text_for_range(start..end).collect::() })?; action_log.update(cx, |log, cx| { @@ -445,7 +444,7 @@ mod test { tool.run(input, ToolCallEventStream::test().0, cx) }) .await; - assert_eq!(result.unwrap(), "Line 2\nLine 3\nLine 4".into()); + assert_eq!(result.unwrap(), "Line 2\nLine 3\nLine 4\n".into()); } #[gpui::test] @@ -475,7 +474,7 @@ mod test { tool.clone().run(input, ToolCallEventStream::test().0, cx) }) .await; - assert_eq!(result.unwrap(), "Line 1\nLine 2".into()); + assert_eq!(result.unwrap(), "Line 1\nLine 2\n".into()); // end_line of 0 should result in at least 1 line let result = cx @@ -488,7 +487,7 @@ mod test { tool.clone().run(input, ToolCallEventStream::test().0, cx) }) .await; - assert_eq!(result.unwrap(), "Line 1".into()); + assert_eq!(result.unwrap(), "Line 1\n".into()); // when start_line > end_line, should still return at least 1 line let result = cx @@ -501,7 +500,7 @@ mod test { tool.clone().run(input, ToolCallEventStream::test().0, cx) }) .await; - assert_eq!(result.unwrap(), "Line 3".into()); + assert_eq!(result.unwrap(), "Line 3\n".into()); } fn init_test(cx: &mut TestAppContext) { From 59a609c9fcebd3f641e64a498e15e3cf2042a648 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 18 Sep 2025 12:06:43 +0200 Subject: [PATCH 108/721] Partially revert "project: Fix terminal activation scripts failing on Windows for new shells (#37986) (#38406) This partially reverts commit 4002602a8926b7fe799acf50fcee6bcffb36d376. Specifically the parts that closes https://github.com/zed-industries/zed/issues/38343 Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/agent_ui/src/acp/thread_view.rs | 2 +- crates/terminal/src/terminal.rs | 8 ++--- crates/terminal_view/src/terminal_panel.rs | 38 ++++++++++++++++++++-- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 8e8f908bc2eea651babb73749e26cb2d6474f74f..cf5284f643cfe3d58ff62a4fa549a84f0a62db69 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1591,7 +1591,7 @@ impl AcpThreadView { task.shell = shell; let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| { - terminal_panel.spawn_task(login.clone(), window, cx) + terminal_panel.spawn_task(&login, window, cx) })?; let terminal = terminal.await?; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 0fce02a97b04484b5a91d6d43b456ecfb1f75f15..6bdeb9638a329c2384e538e27e13c21f02df7284 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -533,14 +533,10 @@ impl TerminalBuilder { child_exited: None, }; - if !activation_script.is_empty() && no_task { + if cfg!(not(target_os = "windows")) && !activation_script.is_empty() && no_task { for activation_script in activation_script { terminal.input(activation_script.into_bytes()); - terminal.write_to_pty(if cfg!(windows) { - &b"\r\n"[..] - } else { - &b"\n"[..] - }); + terminal.write_to_pty(b"\n"); } terminal.clear(); } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 6c93644f992dcd5d3c0126a28c9aa8b8bab020d3..ef574728acdc06bb0db686a1cc9b4c8f8bc0bcce 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,7 @@ use itertools::Itertools; use project::{Fs, Project, ProjectEntryId}; use search::{BufferSearchBar, buffer_search::DivRegistrar}; use settings::Settings; -use task::{RevealStrategy, RevealTarget, SpawnInTerminal, TaskId}; +use task::{RevealStrategy, RevealTarget, ShellBuilder, SpawnInTerminal, TaskId}; use terminal::{ Terminal, terminal_settings::{TerminalDockPosition, TerminalSettings}, @@ -521,10 +521,42 @@ impl TerminalPanel { pub fn spawn_task( &mut self, - task: SpawnInTerminal, + task: &SpawnInTerminal, window: &mut Window, cx: &mut Context, ) -> Task>> { + let remote_client = self + .workspace + .update(cx, |workspace, cx| { + let project = workspace.project().read(cx); + if project.is_via_collab() { + Err(anyhow!("cannot spawn tasks as a guest")) + } else { + Ok(project.remote_client()) + } + }) + .flatten(); + + let remote_client = match remote_client { + Ok(remote_client) => remote_client, + Err(e) => return Task::ready(Err(e)), + }; + + let remote_shell = remote_client + .as_ref() + .and_then(|remote_client| remote_client.read(cx).shell()); + + let builder = ShellBuilder::new(remote_shell.as_deref(), &task.shell); + let command_label = builder.command_label(&task.command_label); + let (command, args) = builder.build(task.command.clone(), &task.args); + + let task = SpawnInTerminal { + command_label, + command: Some(command), + args, + ..task.clone() + }; + if task.allow_concurrent_runs && task.use_new_terminal { return self.spawn_in_new_terminal(task, window, cx); } @@ -1558,7 +1590,7 @@ impl workspace::TerminalProvider for TerminalProvider { window.spawn(cx, async move |cx| { let terminal = terminal_panel .update_in(cx, |terminal_panel, window, cx| { - terminal_panel.spawn_task(task, window, cx) + terminal_panel.spawn_task(&task, window, cx) }) .ok()? .await; From b1aa2723e9b10350ec4270ab6685ffe57ffe4309 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 18 Sep 2025 12:58:10 +0200 Subject: [PATCH 109/721] editor: Reverse range of pending selection if required (#38410) cc https://github.com/zed-industries/zed/issues/38129 Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/selections_collection.rs | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index e562be10e92344c1c892878ab674cba39beb74c2..4343443ff8c4cb4e388984c9014b13ddc8726523 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -469,13 +469,24 @@ impl<'a> MutableSelectionsCollection<'a> { } pub(crate) fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); self.collection.pending = Some(PendingSelection { - selection: Selection { - id: post_inc(&mut self.collection.next_selection_id), - start: range.start, - end: range.end, - reversed: false, - goal: SelectionGoal::None, + selection: { + let mut start = range.start; + let mut end = range.end; + let reversed = if start.cmp(&end, &buffer).is_gt() { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + } }, mode, }); From 202dcb122f719036590f05430b2bac73cdfa5b07 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 18 Sep 2025 08:10:33 -0400 Subject: [PATCH 110/721] remote: Remove excess quoting in WSL `build_command` (#38380) The built-up command for the WSL remote connection looks like ``` wsl.exe --distribution Ubuntu --user cole --cd /home/cole -- bash -c SCRIPT ``` Where `SCRIPT` is a command itself. We don't need extra quotes around `SCRIPT` because we already pass it whole as a separate argument to `wsl.exe`. This isn't yet enough to get ACP servers working in WSL projects (#38332), but it removes one roadblock. Release Notes: - windows: Fixed an issue that could prevent running binaries in WSL remote projects. --- crates/remote/src/transport/wsl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index 6b386ee361c763e30c9e31c15b47c836ef922dae..160a953129ebd82c357ecbfbb48b3d875f196c3d 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -400,7 +400,7 @@ impl RemoteConnection for WslRemoteConnection { "--".to_string(), self.shell.clone(), "-c".to_string(), - shlex::try_quote(&script)?.to_string(), + script, ] } else { vec![ @@ -411,7 +411,7 @@ impl RemoteConnection for WslRemoteConnection { "--".to_string(), self.shell.clone(), "-c".to_string(), - shlex::try_quote(&script)?.to_string(), + script, ] }; From f562e7e157b7f53b1dcd64d06778a68bb9abddaa Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Thu, 18 Sep 2025 06:44:40 -0600 Subject: [PATCH 111/721] edit predictions: Initial Tree-sitter context gathering (#38372) Release Notes: - N/A Co-authored-by: Agus Co-authored-by: Oleksiy Co-authored-by: Finn --- Cargo.lock | 6 + crates/edit_prediction_context/Cargo.toml | 7 + .../src/declaration.rs | 193 ++++++ .../src/declaration_scoring.rs | 326 +++++++++ .../src/edit_prediction_context.rs | 216 +++++- crates/edit_prediction_context/src/excerpt.rs | 2 +- crates/edit_prediction_context/src/outline.rs | 12 +- .../edit_prediction_context/src/reference.rs | 2 +- .../{tree_sitter_index.rs => syntax_index.rs} | 638 +++++++++--------- .../src/text_similarity.rs | 241 +++++++ .../src/wip_requests.rs | 35 + 11 files changed, 1361 insertions(+), 317 deletions(-) create mode 100644 crates/edit_prediction_context/src/declaration.rs create mode 100644 crates/edit_prediction_context/src/declaration_scoring.rs rename crates/edit_prediction_context/src/{tree_sitter_index.rs => syntax_index.rs} (58%) create mode 100644 crates/edit_prediction_context/src/text_similarity.rs create mode 100644 crates/edit_prediction_context/src/wip_requests.rs diff --git a/Cargo.lock b/Cargo.lock index 294127b598926ddae6924f0af1dd273416cf1e1c..4966106b79599db9ece65fd9d9e6d794196a46c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5140,17 +5140,23 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", + "clap", "collections", "futures 0.3.31", "gpui", "indoc", + "itertools 0.14.0", "language", "log", + "ordered-float 2.10.1", "pretty_assertions", "project", + "regex", + "serde", "serde_json", "settings", "slotmap", + "strum 0.27.1", "text", "tree-sitter", "util", diff --git a/crates/edit_prediction_context/Cargo.toml b/crates/edit_prediction_context/Cargo.toml index ad455b0a4ecb1746debafd23f0503b4365f9a0cf..48f51da3912ea5bca589e7b559d5b665b9b762d6 100644 --- a/crates/edit_prediction_context/Cargo.toml +++ b/crates/edit_prediction_context/Cargo.toml @@ -15,17 +15,24 @@ path = "src/edit_prediction_context.rs" anyhow.workspace = true arrayvec.workspace = true collections.workspace = true +futures.workspace = true gpui.workspace = true +itertools.workspace = true language.workspace = true log.workspace = true +ordered-float.workspace = true project.workspace = true +regex.workspace = true +serde.workspace = true slotmap.workspace = true +strum.workspace = true text.workspace = true tree-sitter.workspace = true util.workspace = true workspace-hack.workspace = true [dev-dependencies] +clap.workspace = true futures.workspace = true gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true diff --git a/crates/edit_prediction_context/src/declaration.rs b/crates/edit_prediction_context/src/declaration.rs new file mode 100644 index 0000000000000000000000000000000000000000..fcf54fead80194fe97a2719971f86318a57ad75c --- /dev/null +++ b/crates/edit_prediction_context/src/declaration.rs @@ -0,0 +1,193 @@ +use language::LanguageId; +use project::ProjectEntryId; +use std::borrow::Cow; +use std::ops::Range; +use std::sync::Arc; +use text::{Bias, BufferId, Rope}; + +use crate::outline::OutlineDeclaration; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Identifier { + pub name: Arc, + pub language_id: LanguageId, +} + +slotmap::new_key_type! { + pub struct DeclarationId; +} + +#[derive(Debug, Clone)] +pub enum Declaration { + File { + project_entry_id: ProjectEntryId, + declaration: FileDeclaration, + }, + Buffer { + project_entry_id: ProjectEntryId, + buffer_id: BufferId, + rope: Rope, + declaration: BufferDeclaration, + }, +} + +const ITEM_TEXT_TRUNCATION_LENGTH: usize = 1024; + +impl Declaration { + pub fn identifier(&self) -> &Identifier { + match self { + Declaration::File { declaration, .. } => &declaration.identifier, + Declaration::Buffer { declaration, .. } => &declaration.identifier, + } + } + + pub fn project_entry_id(&self) -> Option { + match self { + Declaration::File { + project_entry_id, .. + } => Some(*project_entry_id), + Declaration::Buffer { + project_entry_id, .. + } => Some(*project_entry_id), + } + } + + pub fn item_text(&self) -> (Cow<'_, str>, bool) { + match self { + Declaration::File { declaration, .. } => ( + declaration.text.as_ref().into(), + declaration.text_is_truncated, + ), + Declaration::Buffer { + rope, declaration, .. + } => ( + rope.chunks_in_range(declaration.item_range.clone()) + .collect::>(), + declaration.item_range_is_truncated, + ), + } + } + + pub fn signature_text(&self) -> (Cow<'_, str>, bool) { + match self { + Declaration::File { declaration, .. } => ( + declaration.text[declaration.signature_range_in_text.clone()].into(), + declaration.signature_is_truncated, + ), + Declaration::Buffer { + rope, declaration, .. + } => ( + rope.chunks_in_range(declaration.signature_range.clone()) + .collect::>(), + declaration.signature_range_is_truncated, + ), + } + } +} + +fn expand_range_to_line_boundaries_and_truncate( + range: &Range, + limit: usize, + rope: &Rope, +) -> (Range, bool) { + let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end); + point_range.start.column = 0; + point_range.end.row += 1; + point_range.end.column = 0; + + let mut item_range = + rope.point_to_offset(point_range.start)..rope.point_to_offset(point_range.end); + let is_truncated = item_range.len() > limit; + if is_truncated { + item_range.end = item_range.start + limit; + } + item_range.end = rope.clip_offset(item_range.end, Bias::Left); + (item_range, is_truncated) +} + +#[derive(Debug, Clone)] +pub struct FileDeclaration { + pub parent: Option, + pub identifier: Identifier, + /// offset range of the declaration in the file, expanded to line boundaries and truncated + pub item_range_in_file: Range, + /// text of `item_range_in_file` + pub text: Arc, + /// whether `text` was truncated + pub text_is_truncated: bool, + /// offset range of the signature within `text` + pub signature_range_in_text: Range, + /// whether `signature` was truncated + pub signature_is_truncated: bool, +} + +impl FileDeclaration { + pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration { + let (item_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate( + &declaration.item_range, + ITEM_TEXT_TRUNCATION_LENGTH, + rope, + ); + + // TODO: consider logging if unexpected + let signature_start = declaration + .signature_range + .start + .saturating_sub(item_range_in_file.start); + let mut signature_end = declaration + .signature_range + .end + .saturating_sub(item_range_in_file.start); + let signature_is_truncated = signature_end > item_range_in_file.len(); + if signature_is_truncated { + signature_end = item_range_in_file.len(); + } + + FileDeclaration { + parent: None, + identifier: declaration.identifier, + signature_range_in_text: signature_start..signature_end, + signature_is_truncated, + text: rope + .chunks_in_range(item_range_in_file.clone()) + .collect::() + .into(), + text_is_truncated, + item_range_in_file, + } + } +} + +#[derive(Debug, Clone)] +pub struct BufferDeclaration { + pub parent: Option, + pub identifier: Identifier, + pub item_range: Range, + pub item_range_is_truncated: bool, + pub signature_range: Range, + pub signature_range_is_truncated: bool, +} + +impl BufferDeclaration { + pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> Self { + let (item_range, item_range_is_truncated) = expand_range_to_line_boundaries_and_truncate( + &declaration.item_range, + ITEM_TEXT_TRUNCATION_LENGTH, + rope, + ); + let (signature_range, signature_range_is_truncated) = + expand_range_to_line_boundaries_and_truncate( + &declaration.signature_range, + ITEM_TEXT_TRUNCATION_LENGTH, + rope, + ); + Self { + parent: None, + identifier: declaration.identifier, + item_range, + item_range_is_truncated, + signature_range, + signature_range_is_truncated, + } + } +} diff --git a/crates/edit_prediction_context/src/declaration_scoring.rs b/crates/edit_prediction_context/src/declaration_scoring.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc442710516a935a65e755393fdfc15026ff1f0e --- /dev/null +++ b/crates/edit_prediction_context/src/declaration_scoring.rs @@ -0,0 +1,326 @@ +use itertools::Itertools as _; +use language::BufferSnapshot; +use ordered_float::OrderedFloat; +use serde::Serialize; +use std::{collections::HashMap, ops::Range}; +use strum::EnumIter; +use text::{OffsetRangeExt, Point, ToPoint}; + +use crate::{ + Declaration, EditPredictionExcerpt, EditPredictionExcerptText, Identifier, + reference::{Reference, ReferenceRegion}, + syntax_index::SyntaxIndexState, + text_similarity::{IdentifierOccurrences, jaccard_similarity, weighted_overlap_coefficient}, +}; + +const MAX_IDENTIFIER_DECLARATION_COUNT: usize = 16; + +// TODO: +// +// * Consider adding declaration_file_count + +#[derive(Clone, Debug)] +pub struct ScoredSnippet { + pub identifier: Identifier, + pub declaration: Declaration, + pub score_components: ScoreInputs, + pub scores: Scores, +} + +// TODO: Consider having "Concise" style corresponding to `concise_text` +#[derive(EnumIter, Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum SnippetStyle { + Signature, + Declaration, +} + +impl ScoredSnippet { + /// Returns the score for this snippet with the specified style. + pub fn score(&self, style: SnippetStyle) -> f32 { + match style { + SnippetStyle::Signature => self.scores.signature, + SnippetStyle::Declaration => self.scores.declaration, + } + } + + pub fn size(&self, style: SnippetStyle) -> usize { + // TODO: how to handle truncation? + match &self.declaration { + Declaration::File { declaration, .. } => match style { + SnippetStyle::Signature => declaration.signature_range_in_text.len(), + SnippetStyle::Declaration => declaration.text.len(), + }, + Declaration::Buffer { declaration, .. } => match style { + SnippetStyle::Signature => declaration.signature_range.len(), + SnippetStyle::Declaration => declaration.item_range.len(), + }, + } + } + + pub fn score_density(&self, style: SnippetStyle) -> f32 { + self.score(style) / (self.size(style)) as f32 + } +} + +pub fn scored_snippets( + index: &SyntaxIndexState, + excerpt: &EditPredictionExcerpt, + excerpt_text: &EditPredictionExcerptText, + identifier_to_references: HashMap>, + cursor_offset: usize, + current_buffer: &BufferSnapshot, +) -> Vec { + let containing_range_identifier_occurrences = + IdentifierOccurrences::within_string(&excerpt_text.body); + let cursor_point = cursor_offset.to_point(¤t_buffer); + + let start_point = Point::new(cursor_point.row.saturating_sub(2), 0); + let end_point = Point::new(cursor_point.row + 1, 0); + let adjacent_identifier_occurrences = IdentifierOccurrences::within_string( + ¤t_buffer + .text_for_range(start_point..end_point) + .collect::(), + ); + + let mut snippets = identifier_to_references + .into_iter() + .flat_map(|(identifier, references)| { + let declarations = + index.declarations_for_identifier::(&identifier); + let declaration_count = declarations.len(); + + declarations + .iter() + .filter_map(|declaration| match declaration { + Declaration::Buffer { + buffer_id, + declaration: buffer_declaration, + .. + } => { + let is_same_file = buffer_id == ¤t_buffer.remote_id(); + + if is_same_file { + range_intersection( + &buffer_declaration.item_range.to_offset(¤t_buffer), + &excerpt.range, + ) + .is_none() + .then(|| { + let declaration_line = buffer_declaration + .item_range + .start + .to_point(current_buffer) + .row; + ( + true, + (cursor_point.row as i32 - declaration_line as i32) + .unsigned_abs(), + declaration, + ) + }) + } else { + // TODO should we prefer the current file instead? + Some((false, 0, declaration)) + } + } + Declaration::File { .. } => { + // TODO should we prefer the current file instead? + // We can assume that a file declaration is in a different file, + // because the current one must be open + Some((false, 0, declaration)) + } + }) + .sorted_by_key(|&(_, distance, _)| distance) + .enumerate() + .map( + |( + declaration_line_distance_rank, + (is_same_file, declaration_line_distance, declaration), + )| { + let same_file_declaration_count = index.file_declaration_count(declaration); + + score_snippet( + &identifier, + &references, + declaration.clone(), + is_same_file, + declaration_line_distance, + declaration_line_distance_rank, + same_file_declaration_count, + declaration_count, + &containing_range_identifier_occurrences, + &adjacent_identifier_occurrences, + cursor_point, + current_buffer, + ) + }, + ) + .collect::>() + }) + .flatten() + .collect::>(); + + snippets.sort_unstable_by_key(|snippet| { + OrderedFloat( + snippet + .score_density(SnippetStyle::Declaration) + .max(snippet.score_density(SnippetStyle::Signature)), + ) + }); + + snippets +} + +fn range_intersection(a: &Range, b: &Range) -> Option> { + let start = a.start.clone().max(b.start.clone()); + let end = a.end.clone().min(b.end.clone()); + if start < end { + Some(Range { start, end }) + } else { + None + } +} + +fn score_snippet( + identifier: &Identifier, + references: &[Reference], + declaration: Declaration, + is_same_file: bool, + declaration_line_distance: u32, + declaration_line_distance_rank: usize, + same_file_declaration_count: usize, + declaration_count: usize, + containing_range_identifier_occurrences: &IdentifierOccurrences, + adjacent_identifier_occurrences: &IdentifierOccurrences, + cursor: Point, + current_buffer: &BufferSnapshot, +) -> Option { + let is_referenced_nearby = references + .iter() + .any(|r| r.region == ReferenceRegion::Nearby); + let is_referenced_in_breadcrumb = references + .iter() + .any(|r| r.region == ReferenceRegion::Breadcrumb); + let reference_count = references.len(); + let reference_line_distance = references + .iter() + .map(|r| { + let reference_line = r.range.start.to_point(current_buffer).row as i32; + (cursor.row as i32 - reference_line).unsigned_abs() + }) + .min() + .unwrap(); + + let item_source_occurrences = IdentifierOccurrences::within_string(&declaration.item_text().0); + let item_signature_occurrences = + IdentifierOccurrences::within_string(&declaration.signature_text().0); + let containing_range_vs_item_jaccard = jaccard_similarity( + containing_range_identifier_occurrences, + &item_source_occurrences, + ); + let containing_range_vs_signature_jaccard = jaccard_similarity( + containing_range_identifier_occurrences, + &item_signature_occurrences, + ); + let adjacent_vs_item_jaccard = + jaccard_similarity(adjacent_identifier_occurrences, &item_source_occurrences); + let adjacent_vs_signature_jaccard = + jaccard_similarity(adjacent_identifier_occurrences, &item_signature_occurrences); + + let containing_range_vs_item_weighted_overlap = weighted_overlap_coefficient( + containing_range_identifier_occurrences, + &item_source_occurrences, + ); + let containing_range_vs_signature_weighted_overlap = weighted_overlap_coefficient( + containing_range_identifier_occurrences, + &item_signature_occurrences, + ); + let adjacent_vs_item_weighted_overlap = + weighted_overlap_coefficient(adjacent_identifier_occurrences, &item_source_occurrences); + let adjacent_vs_signature_weighted_overlap = + weighted_overlap_coefficient(adjacent_identifier_occurrences, &item_signature_occurrences); + + let score_components = ScoreInputs { + is_same_file, + is_referenced_nearby, + is_referenced_in_breadcrumb, + reference_line_distance, + declaration_line_distance, + declaration_line_distance_rank, + reference_count, + same_file_declaration_count, + declaration_count, + containing_range_vs_item_jaccard, + containing_range_vs_signature_jaccard, + adjacent_vs_item_jaccard, + adjacent_vs_signature_jaccard, + containing_range_vs_item_weighted_overlap, + containing_range_vs_signature_weighted_overlap, + adjacent_vs_item_weighted_overlap, + adjacent_vs_signature_weighted_overlap, + }; + + Some(ScoredSnippet { + identifier: identifier.clone(), + declaration: declaration, + scores: score_components.score(), + score_components, + }) +} + +#[derive(Clone, Debug, Serialize)] +pub struct ScoreInputs { + pub is_same_file: bool, + pub is_referenced_nearby: bool, + pub is_referenced_in_breadcrumb: bool, + pub reference_count: usize, + pub same_file_declaration_count: usize, + pub declaration_count: usize, + pub reference_line_distance: u32, + pub declaration_line_distance: u32, + pub declaration_line_distance_rank: usize, + pub containing_range_vs_item_jaccard: f32, + pub containing_range_vs_signature_jaccard: f32, + pub adjacent_vs_item_jaccard: f32, + pub adjacent_vs_signature_jaccard: f32, + pub containing_range_vs_item_weighted_overlap: f32, + pub containing_range_vs_signature_weighted_overlap: f32, + pub adjacent_vs_item_weighted_overlap: f32, + pub adjacent_vs_signature_weighted_overlap: f32, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Scores { + pub signature: f32, + pub declaration: f32, +} + +impl ScoreInputs { + fn score(&self) -> Scores { + // Score related to how likely this is the correct declaration, range 0 to 1 + let accuracy_score = if self.is_same_file { + // TODO: use declaration_line_distance_rank + 1.0 / self.same_file_declaration_count as f32 + } else { + 1.0 / self.declaration_count as f32 + }; + + // Score related to the distance between the reference and cursor, range 0 to 1 + let distance_score = if self.is_referenced_nearby { + 1.0 / (1.0 + self.reference_line_distance as f32 / 10.0).powf(2.0) + } else { + // same score as ~14 lines away, rationale is to not overly penalize references from parent signatures + 0.5 + }; + + // For now instead of linear combination, the scores are just multiplied together. + let combined_score = 10.0 * accuracy_score * distance_score; + + Scores { + signature: combined_score * self.containing_range_vs_signature_weighted_overlap, + // declaration score gets boosted both by being multiplied by 2 and by there being more + // weighted overlap. + declaration: 2.0 * combined_score * self.containing_range_vs_item_weighted_overlap, + } + } +} diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs index acfb89880c3ed9e7b1ebcacd4b5fa313830165ba..5d73dc7f7dcf2223ae1f23b22c2f104842206e12 100644 --- a/crates/edit_prediction_context/src/edit_prediction_context.rs +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -1,8 +1,220 @@ +mod declaration; +mod declaration_scoring; mod excerpt; mod outline; mod reference; -mod tree_sitter_index; +mod syntax_index; +mod text_similarity; +pub use declaration::{BufferDeclaration, Declaration, FileDeclaration, Identifier}; pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions, EditPredictionExcerptText}; +use gpui::{App, AppContext as _, Entity, Task}; +use language::BufferSnapshot; pub use reference::references_in_excerpt; -pub use tree_sitter_index::{BufferDeclaration, Declaration, FileDeclaration, TreeSitterIndex}; +pub use syntax_index::SyntaxIndex; +use text::{Point, ToOffset as _}; + +use crate::declaration_scoring::{ScoredSnippet, scored_snippets}; + +pub struct EditPredictionContext { + pub excerpt: EditPredictionExcerpt, + pub excerpt_text: EditPredictionExcerptText, + pub snippets: Vec, +} + +impl EditPredictionContext { + pub fn gather( + cursor_point: Point, + buffer: BufferSnapshot, + excerpt_options: EditPredictionExcerptOptions, + syntax_index: Entity, + cx: &mut App, + ) -> Task { + let index_state = syntax_index.read_with(cx, |index, _cx| index.state().clone()); + cx.background_spawn(async move { + let index_state = index_state.lock().await; + + let excerpt = + EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &excerpt_options) + .unwrap(); + let excerpt_text = excerpt.text(&buffer); + let references = references_in_excerpt(&excerpt, &excerpt_text, &buffer); + let cursor_offset = cursor_point.to_offset(&buffer); + + let snippets = scored_snippets( + &index_state, + &excerpt, + &excerpt_text, + references, + cursor_offset, + &buffer, + ); + + Self { + excerpt, + excerpt_text, + snippets, + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + + use gpui::{Entity, TestAppContext}; + use indoc::indoc; + use language::{Language, LanguageConfig, LanguageId, LanguageMatcher, tree_sitter_rust}; + use project::{FakeFs, Project}; + use serde_json::json; + use settings::SettingsStore; + use util::path; + + use crate::{EditPredictionExcerptOptions, SyntaxIndex}; + + #[gpui::test] + async fn test_call_site(cx: &mut TestAppContext) { + let (project, index, _rust_lang_id) = init_test(cx).await; + + let buffer = project + .update(cx, |project, cx| { + let project_path = project.find_project_path("c.rs", cx).unwrap(); + project.open_buffer(project_path, cx) + }) + .await + .unwrap(); + + cx.run_until_parked(); + + // first process_data call site + let cursor_point = language::Point::new(8, 21); + let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); + + let context = cx + .update(|cx| { + EditPredictionContext::gather( + cursor_point, + buffer_snapshot, + EditPredictionExcerptOptions { + max_bytes: 40, + min_bytes: 10, + target_before_cursor_over_total_bytes: 0.5, + include_parent_signatures: false, + }, + index, + cx, + ) + }) + .await; + + assert_eq!(context.snippets.len(), 1); + assert_eq!(context.snippets[0].identifier.name.as_ref(), "process_data"); + drop(buffer); + } + + async fn init_test( + cx: &mut TestAppContext, + ) -> (Entity, Entity, LanguageId) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + language::init(cx); + Project::init_settings(cx); + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + json!({ + "a.rs": indoc! {r#" + fn main() { + let x = 1; + let y = 2; + let z = add(x, y); + println!("Result: {}", z); + } + + fn add(a: i32, b: i32) -> i32 { + a + b + } + "#}, + "b.rs": indoc! {" + pub struct Config { + pub name: String, + pub value: i32, + } + + impl Config { + pub fn new(name: String, value: i32) -> Self { + Config { name, value } + } + } + "}, + "c.rs": indoc! {r#" + use std::collections::HashMap; + + fn main() { + let args: Vec = std::env::args().collect(); + let data: Vec = args[1..] + .iter() + .filter_map(|s| s.parse().ok()) + .collect(); + let result = process_data(data); + println!("{:?}", result); + } + + fn process_data(data: Vec) -> HashMap { + let mut counts = HashMap::new(); + for value in data { + *counts.entry(value).or_insert(0) += 1; + } + counts + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_process_data() { + let data = vec![1, 2, 2, 3]; + let result = process_data(data); + assert_eq!(result.get(&2), Some(&2)); + } + } + "#} + }), + ) + .await; + let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + let lang = rust_lang(); + let lang_id = lang.id(); + language_registry.add(Arc::new(lang)); + + let index = cx.new(|cx| SyntaxIndex::new(&project, cx)); + cx.run_until_parked(); + + (project, index, lang_id) + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) + .with_highlights_query(include_str!("../../languages/src/rust/highlights.scm")) + .unwrap() + .with_outline_query(include_str!("../../languages/src/rust/outline.scm")) + .unwrap() + } +} diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index c6caa6a1b7b4076cf739c1ac198656b9fba431a6..da1de042623167d17f078c1e85b461fb0ecc8c24 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -31,7 +31,7 @@ pub struct EditPredictionExcerptOptions { pub include_parent_signatures: bool, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EditPredictionExcerpt { pub range: Range, pub parent_signature_ranges: Vec>, diff --git a/crates/edit_prediction_context/src/outline.rs b/crates/edit_prediction_context/src/outline.rs index 492352add1fd4c666eab3b12989f9b801d03570f..ec02c869dfae4cb861206cb801c285462e734f36 100644 --- a/crates/edit_prediction_context/src/outline.rs +++ b/crates/edit_prediction_context/src/outline.rs @@ -1,5 +1,7 @@ -use language::{BufferSnapshot, LanguageId, SyntaxMapMatches}; -use std::{cmp::Reverse, ops::Range, sync::Arc}; +use language::{BufferSnapshot, SyntaxMapMatches}; +use std::{cmp::Reverse, ops::Range}; + +use crate::declaration::Identifier; // TODO: // @@ -18,12 +20,6 @@ pub struct OutlineDeclaration { pub signature_range: Range, } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Identifier { - pub name: Arc, - pub language_id: LanguageId, -} - pub fn declarations_in_buffer(buffer: &BufferSnapshot) -> Vec { declarations_overlapping_range(0..buffer.len(), buffer) } diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs index 65d34e73bf20f62b24ac2a654af43fc3b83041a9..ee2fc7ba573c3909b5a650e3ca0ff20155272b9f 100644 --- a/crates/edit_prediction_context/src/reference.rs +++ b/crates/edit_prediction_context/src/reference.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use std::ops::Range; use crate::{ + declaration::Identifier, excerpt::{EditPredictionExcerpt, EditPredictionExcerptText}, - outline::Identifier, }; #[derive(Debug)] diff --git a/crates/edit_prediction_context/src/tree_sitter_index.rs b/crates/edit_prediction_context/src/syntax_index.rs similarity index 58% rename from crates/edit_prediction_context/src/tree_sitter_index.rs rename to crates/edit_prediction_context/src/syntax_index.rs index f905aa7a01f29d26083d219bc8d2bd600847036a..852973dd7296647b0f868c3f9242ed59b81b6743 100644 --- a/crates/edit_prediction_context/src/tree_sitter_index.rs +++ b/crates/edit_prediction_context/src/syntax_index.rs @@ -1,20 +1,26 @@ +use std::sync::Arc; + use collections::{HashMap, HashSet}; +use futures::lock::Mutex; use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity}; -use language::{Buffer, BufferEvent, BufferSnapshot}; +use language::{Buffer, BufferEvent}; use project::buffer_store::{BufferStore, BufferStoreEvent}; use project::worktree_store::{WorktreeStore, WorktreeStoreEvent}; use project::{PathChange, Project, ProjectEntryId, ProjectPath}; use slotmap::SlotMap; -use std::ops::Range; -use std::sync::Arc; -use text::Anchor; +use text::BufferId; use util::{ResultExt as _, debug_panic, some_or_debug_panic}; -use crate::outline::{Identifier, OutlineDeclaration, declarations_in_buffer}; +use crate::declaration::{ + BufferDeclaration, Declaration, DeclarationId, FileDeclaration, Identifier, +}; +use crate::outline::declarations_in_buffer; // TODO: // // * Skip for remote projects +// +// * Consider making SyntaxIndex not an Entity. // Potential future improvements: // @@ -34,17 +40,19 @@ use crate::outline::{Identifier, OutlineDeclaration, declarations_in_buffer}; // * Concurrent slotmap // // * Use queue for parsing +// -slotmap::new_key_type! { - pub struct DeclarationId; +pub struct SyntaxIndex { + state: Arc>, + project: WeakEntity, } -pub struct TreeSitterIndex { +#[derive(Default)] +pub struct SyntaxIndexState { declarations: SlotMap, identifiers: HashMap>, files: HashMap, - buffers: HashMap, BufferState>, - project: WeakEntity, + buffers: HashMap, } #[derive(Debug, Default)] @@ -59,52 +67,11 @@ struct BufferState { task: Option>, } -#[derive(Debug, Clone)] -pub enum Declaration { - File { - project_entry_id: ProjectEntryId, - declaration: FileDeclaration, - }, - Buffer { - buffer: WeakEntity, - declaration: BufferDeclaration, - }, -} - -impl Declaration { - fn identifier(&self) -> &Identifier { - match self { - Declaration::File { declaration, .. } => &declaration.identifier, - Declaration::Buffer { declaration, .. } => &declaration.identifier, - } - } -} - -#[derive(Debug, Clone)] -pub struct FileDeclaration { - pub parent: Option, - pub identifier: Identifier, - pub item_range: Range, - pub signature_range: Range, - pub signature_text: Arc, -} - -#[derive(Debug, Clone)] -pub struct BufferDeclaration { - pub parent: Option, - pub identifier: Identifier, - pub item_range: Range, - pub signature_range: Range, -} - -impl TreeSitterIndex { +impl SyntaxIndex { pub fn new(project: &Entity, cx: &mut Context) -> Self { let mut this = Self { - declarations: SlotMap::with_key(), - identifiers: HashMap::default(), project: project.downgrade(), - files: HashMap::default(), - buffers: HashMap::default(), + state: Arc::new(Mutex::new(SyntaxIndexState::default())), }; let worktree_store = project.read(cx).worktree_store(); @@ -139,73 +106,6 @@ impl TreeSitterIndex { this } - pub fn declaration(&self, id: DeclarationId) -> Option<&Declaration> { - self.declarations.get(id) - } - - pub fn declarations_for_identifier( - &self, - identifier: Identifier, - cx: &App, - ) -> Vec { - // make sure to not have a large stack allocation - assert!(N < 32); - - let Some(declaration_ids) = self.identifiers.get(&identifier) else { - return vec![]; - }; - - let mut result = Vec::with_capacity(N); - let mut included_buffer_entry_ids = arrayvec::ArrayVec::<_, N>::new(); - let mut file_declarations = Vec::new(); - - for declaration_id in declaration_ids { - let declaration = self.declarations.get(*declaration_id); - let Some(declaration) = some_or_debug_panic(declaration) else { - continue; - }; - match declaration { - Declaration::Buffer { buffer, .. } => { - if let Ok(Some(entry_id)) = buffer.read_with(cx, |buffer, cx| { - project::File::from_dyn(buffer.file()).and_then(|f| f.project_entry_id(cx)) - }) { - included_buffer_entry_ids.push(entry_id); - result.push(declaration.clone()); - if result.len() == N { - return result; - } - } - } - Declaration::File { - project_entry_id, .. - } => { - if !included_buffer_entry_ids.contains(project_entry_id) { - file_declarations.push(declaration.clone()); - } - } - } - } - - for declaration in file_declarations { - match declaration { - Declaration::File { - project_entry_id, .. - } => { - if !included_buffer_entry_ids.contains(&project_entry_id) { - result.push(declaration); - - if result.len() == N { - return result; - } - } - } - Declaration::Buffer { .. } => {} - } - } - - result - } - fn handle_worktree_store_event( &mut self, _worktree_store: Entity, @@ -215,21 +115,33 @@ impl TreeSitterIndex { use WorktreeStoreEvent::*; match event { WorktreeUpdatedEntries(worktree_id, updated_entries_set) => { - for (path, entry_id, path_change) in updated_entries_set.iter() { - if let PathChange::Removed = path_change { - self.files.remove(entry_id); - } else { - let project_path = ProjectPath { - worktree_id: *worktree_id, - path: path.clone(), - }; - self.update_file(*entry_id, project_path, cx); + let state = Arc::downgrade(&self.state); + let worktree_id = *worktree_id; + let updated_entries_set = updated_entries_set.clone(); + cx.spawn(async move |this, cx| { + let Some(state) = state.upgrade() else { return }; + for (path, entry_id, path_change) in updated_entries_set.iter() { + if let PathChange::Removed = path_change { + state.lock().await.files.remove(entry_id); + } else { + let project_path = ProjectPath { + worktree_id, + path: path.clone(), + }; + this.update(cx, |this, cx| { + this.update_file(*entry_id, project_path, cx); + }) + .ok(); + } } - } + }) + .detach(); } WorktreeDeletedEntry(_worktree_id, project_entry_id) => { - // TODO: Is this needed? - self.files.remove(project_entry_id); + let project_entry_id = *project_entry_id; + self.with_state(cx, move |state| { + state.files.remove(&project_entry_id); + }) } _ => {} } @@ -251,15 +163,42 @@ impl TreeSitterIndex { } } + pub fn state(&self) -> &Arc> { + &self.state + } + + fn with_state(&self, cx: &mut App, f: impl FnOnce(&mut SyntaxIndexState) + Send + 'static) { + if let Some(mut state) = self.state.try_lock() { + f(&mut state); + return; + } + let state = Arc::downgrade(&self.state); + cx.background_spawn(async move { + let Some(state) = state.upgrade() else { + return; + }; + let mut state = state.lock().await; + f(&mut state) + }) + .detach(); + } + fn register_buffer(&mut self, buffer: &Entity, cx: &mut Context) { - self.buffers - .insert(buffer.downgrade(), BufferState::default()); - let weak_buf = buffer.downgrade(); - cx.observe_release(buffer, move |this, _buffer, _cx| { - this.buffers.remove(&weak_buf); + let buffer_id = buffer.read(cx).remote_id(); + cx.observe_release(buffer, move |this, _buffer, cx| { + this.with_state(cx, move |state| { + if let Some(buffer_state) = state.buffers.remove(&buffer_id) { + SyntaxIndexState::remove_buffer_declarations( + &buffer_state.declarations, + &mut state.declarations, + &mut state.identifiers, + ); + } + }) }) .detach(); cx.subscribe(buffer, Self::handle_buffer_event).detach(); + self.update_buffer(buffer.clone(), cx); } @@ -275,10 +214,19 @@ impl TreeSitterIndex { } } - fn update_buffer(&mut self, buffer: Entity, cx: &Context) { - let mut parse_status = buffer.read(cx).parse_status(); + fn update_buffer(&mut self, buffer_entity: Entity, cx: &mut Context) { + let buffer = buffer_entity.read(cx); + + let Some(project_entry_id) = + project::File::from_dyn(buffer.file()).and_then(|f| f.project_entry_id(cx)) + else { + return; + }; + let buffer_id = buffer.remote_id(); + + let mut parse_status = buffer.parse_status(); let snapshot_task = cx.spawn({ - let weak_buffer = buffer.downgrade(); + let weak_buffer = buffer_entity.downgrade(); async move |_, cx| { while *parse_status.borrow() != language::ParseStatus::Idle { parse_status.changed().await?; @@ -289,75 +237,77 @@ impl TreeSitterIndex { let parse_task = cx.background_spawn(async move { let snapshot = snapshot_task.await?; + let rope = snapshot.text.as_rope().clone(); - anyhow::Ok( + anyhow::Ok(( declarations_in_buffer(&snapshot) .into_iter() .map(|item| { ( item.parent_index, - BufferDeclaration::from_outline(item, &snapshot), + BufferDeclaration::from_outline(item, &rope), ) }) .collect::>(), - ) + rope, + )) }); let task = cx.spawn({ - let weak_buffer = buffer.downgrade(); async move |this, cx| { - let Ok(declarations) = parse_task.await else { + let Ok((declarations, rope)) = parse_task.await else { return; }; - this.update(cx, |this, _cx| { - let buffer_state = this - .buffers - .entry(weak_buffer.clone()) - .or_insert_with(Default::default); - - for old_declaration_id in &buffer_state.declarations { - let Some(declaration) = this.declarations.remove(*old_declaration_id) - else { - debug_panic!("declaration not found"); - continue; - }; - if let Some(identifier_declarations) = - this.identifiers.get_mut(declaration.identifier()) - { - identifier_declarations.remove(old_declaration_id); + this.update(cx, move |this, cx| { + this.with_state(cx, move |state| { + let buffer_state = state + .buffers + .entry(buffer_id) + .or_insert_with(Default::default); + + SyntaxIndexState::remove_buffer_declarations( + &buffer_state.declarations, + &mut state.declarations, + &mut state.identifiers, + ); + + let mut new_ids = Vec::with_capacity(declarations.len()); + state.declarations.reserve(declarations.len()); + for (parent_index, mut declaration) in declarations { + declaration.parent = parent_index + .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied())); + + let identifier = declaration.identifier.clone(); + let declaration_id = state.declarations.insert(Declaration::Buffer { + rope: rope.clone(), + buffer_id, + declaration, + project_entry_id, + }); + new_ids.push(declaration_id); + + state + .identifiers + .entry(identifier) + .or_default() + .insert(declaration_id); } - } - - let mut new_ids = Vec::with_capacity(declarations.len()); - this.declarations.reserve(declarations.len()); - for (parent_index, mut declaration) in declarations { - declaration.parent = parent_index - .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied())); - - let identifier = declaration.identifier.clone(); - let declaration_id = this.declarations.insert(Declaration::Buffer { - buffer: weak_buffer.clone(), - declaration, - }); - new_ids.push(declaration_id); - - this.identifiers - .entry(identifier) - .or_default() - .insert(declaration_id); - } - buffer_state.declarations = new_ids; + buffer_state.declarations = new_ids; + }); }) .ok(); } }); - self.buffers - .entry(buffer.downgrade()) - .or_insert_with(Default::default) - .task = Some(task); + self.with_state(cx, move |state| { + state + .buffers + .entry(buffer_id) + .or_insert_with(Default::default) + .task = Some(task) + }); } fn update_file( @@ -401,14 +351,10 @@ impl TreeSitterIndex { let parse_task = cx.background_spawn(async move { let snapshot = snapshot_task.await?; + let rope = snapshot.as_rope(); let declarations = declarations_in_buffer(&snapshot) .into_iter() - .map(|item| { - ( - item.parent_index, - FileDeclaration::from_outline(item, &snapshot), - ) - }) + .map(|item| (item.parent_index, FileDeclaration::from_outline(item, rope))) .collect::>(); anyhow::Ok(declarations) }); @@ -419,84 +365,160 @@ impl TreeSitterIndex { let Ok(declarations) = parse_task.await else { return; }; - this.update(cx, |this, _cx| { - let file_state = this.files.entry(entry_id).or_insert_with(Default::default); - - for old_declaration_id in &file_state.declarations { - let Some(declaration) = this.declarations.remove(*old_declaration_id) - else { - debug_panic!("declaration not found"); - continue; - }; - if let Some(identifier_declarations) = - this.identifiers.get_mut(declaration.identifier()) - { - identifier_declarations.remove(old_declaration_id); + this.update(cx, |this, cx| { + this.with_state(cx, move |state| { + let file_state = + state.files.entry(entry_id).or_insert_with(Default::default); + + for old_declaration_id in &file_state.declarations { + let Some(declaration) = state.declarations.remove(*old_declaration_id) + else { + debug_panic!("declaration not found"); + continue; + }; + if let Some(identifier_declarations) = + state.identifiers.get_mut(declaration.identifier()) + { + identifier_declarations.remove(old_declaration_id); + } } - } - - let mut new_ids = Vec::with_capacity(declarations.len()); - this.declarations.reserve(declarations.len()); - for (parent_index, mut declaration) in declarations { - declaration.parent = parent_index - .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied())); - - let identifier = declaration.identifier.clone(); - let declaration_id = this.declarations.insert(Declaration::File { - project_entry_id: entry_id, - declaration, - }); - new_ids.push(declaration_id); - - this.identifiers - .entry(identifier) - .or_default() - .insert(declaration_id); - } + let mut new_ids = Vec::with_capacity(declarations.len()); + state.declarations.reserve(declarations.len()); + + for (parent_index, mut declaration) in declarations { + declaration.parent = parent_index + .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied())); + + let identifier = declaration.identifier.clone(); + let declaration_id = state.declarations.insert(Declaration::File { + project_entry_id: entry_id, + declaration, + }); + new_ids.push(declaration_id); + + state + .identifiers + .entry(identifier) + .or_default() + .insert(declaration_id); + } - file_state.declarations = new_ids; + file_state.declarations = new_ids; + }); }) .ok(); } }); - self.files - .entry(entry_id) - .or_insert_with(Default::default) - .task = Some(task); + self.with_state(cx, move |state| { + state + .files + .entry(entry_id) + .or_insert_with(Default::default) + .task = Some(task); + }); } } -impl BufferDeclaration { - pub fn from_outline(declaration: OutlineDeclaration, snapshot: &BufferSnapshot) -> Self { - // use of anchor_before is a guess that the proper behavior is to expand to include - // insertions immediately before the declaration, but not for insertions immediately after - Self { - parent: None, - identifier: declaration.identifier, - item_range: snapshot.anchor_before(declaration.item_range.start) - ..snapshot.anchor_before(declaration.item_range.end), - signature_range: snapshot.anchor_before(declaration.signature_range.start) - ..snapshot.anchor_before(declaration.signature_range.end), +impl SyntaxIndexState { + pub fn declaration(&self, id: DeclarationId) -> Option<&Declaration> { + self.declarations.get(id) + } + + /// Returns declarations for the identifier. If the limit is exceeded, returns an empty vector. + /// + /// TODO: Consider doing some pre-ranking and instead truncating when N is exceeded. + pub fn declarations_for_identifier( + &self, + identifier: &Identifier, + ) -> Vec { + // make sure to not have a large stack allocation + assert!(N < 32); + + let Some(declaration_ids) = self.identifiers.get(&identifier) else { + return vec![]; + }; + + let mut result = Vec::with_capacity(N); + let mut included_buffer_entry_ids = arrayvec::ArrayVec::<_, N>::new(); + let mut file_declarations = Vec::new(); + + for declaration_id in declaration_ids { + let declaration = self.declarations.get(*declaration_id); + let Some(declaration) = some_or_debug_panic(declaration) else { + continue; + }; + match declaration { + Declaration::Buffer { + project_entry_id, .. + } => { + included_buffer_entry_ids.push(*project_entry_id); + result.push(declaration.clone()); + if result.len() == N { + return Vec::new(); + } + } + Declaration::File { + project_entry_id, .. + } => { + if !included_buffer_entry_ids.contains(&project_entry_id) { + file_declarations.push(declaration.clone()); + } + } + } + } + + for declaration in file_declarations { + match declaration { + Declaration::File { + project_entry_id, .. + } => { + if !included_buffer_entry_ids.contains(&project_entry_id) { + result.push(declaration); + + if result.len() == N { + return Vec::new(); + } + } + } + Declaration::Buffer { .. } => {} + } + } + + result + } + + pub fn file_declaration_count(&self, declaration: &Declaration) -> usize { + match declaration { + Declaration::File { + project_entry_id, .. + } => self + .files + .get(project_entry_id) + .map(|file_state| file_state.declarations.len()) + .unwrap_or_default(), + Declaration::Buffer { buffer_id, .. } => self + .buffers + .get(buffer_id) + .map(|buffer_state| buffer_state.declarations.len()) + .unwrap_or_default(), } } -} -impl FileDeclaration { - pub fn from_outline( - declaration: OutlineDeclaration, - snapshot: &BufferSnapshot, - ) -> FileDeclaration { - FileDeclaration { - parent: None, - identifier: declaration.identifier, - item_range: declaration.item_range, - signature_text: snapshot - .text_for_range(declaration.signature_range.clone()) - .collect::() - .into(), - signature_range: declaration.signature_range, + fn remove_buffer_declarations( + old_declaration_ids: &[DeclarationId], + declarations: &mut SlotMap, + identifiers: &mut HashMap>, + ) { + for old_declaration_id in old_declaration_ids { + let Some(declaration) = declarations.remove(*old_declaration_id) else { + debug_panic!("declaration not found"); + continue; + }; + if let Some(identifier_declarations) = identifiers.get_mut(declaration.identifier()) { + identifier_declarations.remove(old_declaration_id); + } } } } @@ -509,13 +531,13 @@ mod tests { use gpui::TestAppContext; use indoc::indoc; use language::{Language, LanguageConfig, LanguageId, LanguageMatcher, tree_sitter_rust}; - use project::{FakeFs, Project, ProjectItem}; + use project::{FakeFs, Project}; use serde_json::json; use settings::SettingsStore; use text::OffsetRangeExt as _; use util::path; - use crate::tree_sitter_index::TreeSitterIndex; + use crate::syntax_index::SyntaxIndex; #[gpui::test] async fn test_unopen_indexed_files(cx: &mut TestAppContext) { @@ -525,17 +547,19 @@ mod tests { language_id: rust_lang_id, }; - index.read_with(cx, |index, cx| { - let decls = index.declarations_for_identifier::<8>(main.clone(), cx); + let index_state = index.read_with(cx, |index, _cx| index.state().clone()); + let index_state = index_state.lock().await; + cx.update(|cx| { + let decls = index_state.declarations_for_identifier::<8>(&main); assert_eq!(decls.len(), 2); let decl = expect_file_decl("c.rs", &decls[0], &project, cx); assert_eq!(decl.identifier, main.clone()); - assert_eq!(decl.item_range, 32..279); + assert_eq!(decl.item_range_in_file, 32..280); let decl = expect_file_decl("a.rs", &decls[1], &project, cx); assert_eq!(decl.identifier, main); - assert_eq!(decl.item_range, 0..97); + assert_eq!(decl.item_range_in_file, 0..98); }); } @@ -547,15 +571,17 @@ mod tests { language_id: rust_lang_id, }; - index.read_with(cx, |index, cx| { - let decls = index.declarations_for_identifier::<8>(test_process_data.clone(), cx); + let index_state = index.read_with(cx, |index, _cx| index.state().clone()); + let index_state = index_state.lock().await; + cx.update(|cx| { + let decls = index_state.declarations_for_identifier::<8>(&test_process_data); assert_eq!(decls.len(), 1); let decl = expect_file_decl("c.rs", &decls[0], &project, cx); assert_eq!(decl.identifier, test_process_data); let parent_id = decl.parent.unwrap(); - let parent = index.declaration(parent_id).unwrap(); + let parent = index_state.declaration(parent_id).unwrap(); let parent_decl = expect_file_decl("c.rs", &parent, &project, cx); assert_eq!( parent_decl.identifier, @@ -586,16 +612,18 @@ mod tests { cx.run_until_parked(); - index.read_with(cx, |index, cx| { - let decls = index.declarations_for_identifier::<8>(test_process_data.clone(), cx); + let index_state = index.read_with(cx, |index, _cx| index.state().clone()); + let index_state = index_state.lock().await; + cx.update(|cx| { + let decls = index_state.declarations_for_identifier::<8>(&test_process_data); assert_eq!(decls.len(), 1); - let decl = expect_buffer_decl("c.rs", &decls[0], cx); + let decl = expect_buffer_decl("c.rs", &decls[0], &project, cx); assert_eq!(decl.identifier, test_process_data); let parent_id = decl.parent.unwrap(); - let parent = index.declaration(parent_id).unwrap(); - let parent_decl = expect_buffer_decl("c.rs", &parent, cx); + let parent = index_state.declaration(parent_id).unwrap(); + let parent_decl = expect_buffer_decl("c.rs", &parent, &project, cx); assert_eq!( parent_decl.identifier, Identifier { @@ -613,16 +641,13 @@ mod tests { async fn test_declarations_limt(cx: &mut TestAppContext) { let (_, index, rust_lang_id) = init_test(cx).await; - index.read_with(cx, |index, cx| { - let decls = index.declarations_for_identifier::<1>( - Identifier { - name: "main".into(), - language_id: rust_lang_id, - }, - cx, - ); - assert_eq!(decls.len(), 1); + let index_state = index.read_with(cx, |index, _cx| index.state().clone()); + let index_state = index_state.lock().await; + let decls = index_state.declarations_for_identifier::<1>(&Identifier { + name: "main".into(), + language_id: rust_lang_id, }); + assert_eq!(decls.len(), 0); } #[gpui::test] @@ -644,24 +669,31 @@ mod tests { cx.run_until_parked(); - index.read_with(cx, |index, cx| { - let decls = index.declarations_for_identifier::<8>(main.clone(), cx); - assert_eq!(decls.len(), 2); - let decl = expect_buffer_decl("c.rs", &decls[0], cx); - assert_eq!(decl.identifier, main); - assert_eq!(decl.item_range.to_offset(&buffer.read(cx)), 32..279); + let index_state_arc = index.read_with(cx, |index, _cx| index.state().clone()); + { + let index_state = index_state_arc.lock().await; - expect_file_decl("a.rs", &decls[1], &project, cx); - }); + cx.update(|cx| { + let decls = index_state.declarations_for_identifier::<8>(&main); + assert_eq!(decls.len(), 2); + let decl = expect_buffer_decl("c.rs", &decls[0], &project, cx); + assert_eq!(decl.identifier, main); + assert_eq!(decl.item_range.to_offset(&buffer.read(cx)), 32..280); + + expect_file_decl("a.rs", &decls[1], &project, cx); + }); + } - // Need to trigger flush_effects so that the observe_release handler will run. - cx.update(|_cx| { + // Drop the buffer and wait for release + cx.update(|_| { drop(buffer); }); cx.run_until_parked(); - index.read_with(cx, |index, cx| { - let decls = index.declarations_for_identifier::<8>(main, cx); + let index_state = index_state_arc.lock().await; + + cx.update(|cx| { + let decls = index_state.declarations_for_identifier::<8>(&main); assert_eq!(decls.len(), 2); expect_file_decl("c.rs", &decls[0], &project, cx); expect_file_decl("a.rs", &decls[1], &project, cx); @@ -671,24 +703,20 @@ mod tests { fn expect_buffer_decl<'a>( path: &str, declaration: &'a Declaration, + project: &Entity, cx: &App, ) -> &'a BufferDeclaration { if let Declaration::Buffer { declaration, - buffer, + project_entry_id, + .. } = declaration { - assert_eq!( - buffer - .upgrade() - .unwrap() - .read(cx) - .project_path(cx) - .unwrap() - .path - .as_ref(), - Path::new(path), - ); + let project_path = project + .read(cx) + .path_for_entry(*project_entry_id, cx) + .unwrap(); + assert_eq!(project_path.path.as_ref(), Path::new(path),); declaration } else { panic!("Expected a buffer declaration, found {:?}", declaration); @@ -723,7 +751,7 @@ mod tests { async fn init_test( cx: &mut TestAppContext, - ) -> (Entity, Entity, LanguageId) { + ) -> (Entity, Entity, LanguageId) { cx.update(|cx| { let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); @@ -801,7 +829,7 @@ mod tests { let lang_id = lang.id(); language_registry.add(Arc::new(lang)); - let index = cx.new(|cx| TreeSitterIndex::new(&project, cx)); + let index = cx.new(|cx| SyntaxIndex::new(&project, cx)); cx.run_until_parked(); (project, index, lang_id) diff --git a/crates/edit_prediction_context/src/text_similarity.rs b/crates/edit_prediction_context/src/text_similarity.rs new file mode 100644 index 0000000000000000000000000000000000000000..f7a9822ecca01f1b6ff1dc04bdc12fbcddc5159b --- /dev/null +++ b/crates/edit_prediction_context/src/text_similarity.rs @@ -0,0 +1,241 @@ +use regex::Regex; +use std::{collections::HashMap, sync::LazyLock}; + +use crate::reference::Reference; + +// TODO: Consider implementing sliding window similarity matching like +// https://github.com/sourcegraph/cody-public-snapshot/blob/8e20ac6c1460c08b0db581c0204658112a246eda/vscode/src/completions/context/retrievers/jaccard-similarity/bestJaccardMatch.ts +// +// That implementation could actually be more efficient - no need to track words in the window that +// are not in the query. + +static IDENTIFIER_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"\b\w+\b").unwrap()); + +#[derive(Debug)] +pub struct IdentifierOccurrences { + identifier_to_count: HashMap, + total_count: usize, +} + +impl IdentifierOccurrences { + pub fn within_string(code: &str) -> Self { + Self::from_iterator(IDENTIFIER_REGEX.find_iter(code).map(|mat| mat.as_str())) + } + + #[allow(dead_code)] + pub fn within_references(references: &[Reference]) -> Self { + Self::from_iterator( + references + .iter() + .map(|reference| reference.identifier.name.as_ref()), + ) + } + + pub fn from_iterator<'a>(identifier_iterator: impl Iterator) -> Self { + let mut identifier_to_count = HashMap::new(); + let mut total_count = 0; + for identifier in identifier_iterator { + // TODO: Score matches that match case higher? + // + // TODO: Also include unsplit identifier? + for identifier_part in split_identifier(identifier) { + identifier_to_count + .entry(identifier_part.to_lowercase()) + .and_modify(|count| *count += 1) + .or_insert(1); + total_count += 1; + } + } + IdentifierOccurrences { + identifier_to_count, + total_count, + } + } +} + +// Splits camelcase / snakecase / kebabcase / pascalcase +// +// TODO: Make this more efficient / elegant. +fn split_identifier(identifier: &str) -> Vec<&str> { + let mut parts = Vec::new(); + let mut start = 0; + let chars: Vec = identifier.chars().collect(); + + if chars.is_empty() { + return parts; + } + + let mut i = 0; + while i < chars.len() { + let ch = chars[i]; + + // Handle explicit delimiters (underscore and hyphen) + if ch == '_' || ch == '-' { + if i > start { + parts.push(&identifier[start..i]); + } + start = i + 1; + i += 1; + continue; + } + + // Handle camelCase and PascalCase transitions + if i > 0 && i < chars.len() { + let prev_char = chars[i - 1]; + + // Transition from lowercase/digit to uppercase + if (prev_char.is_lowercase() || prev_char.is_ascii_digit()) && ch.is_uppercase() { + parts.push(&identifier[start..i]); + start = i; + } + // Handle sequences like "XMLParser" -> ["XML", "Parser"] + else if i + 1 < chars.len() + && ch.is_uppercase() + && chars[i + 1].is_lowercase() + && prev_char.is_uppercase() + { + parts.push(&identifier[start..i]); + start = i; + } + } + + i += 1; + } + + // Add the last part if there's any remaining + if start < identifier.len() { + parts.push(&identifier[start..]); + } + + // Filter out empty strings + parts.into_iter().filter(|s| !s.is_empty()).collect() +} + +pub fn jaccard_similarity<'a>( + mut set_a: &'a IdentifierOccurrences, + mut set_b: &'a IdentifierOccurrences, +) -> f32 { + if set_a.identifier_to_count.len() > set_b.identifier_to_count.len() { + std::mem::swap(&mut set_a, &mut set_b); + } + let intersection = set_a + .identifier_to_count + .keys() + .filter(|key| set_b.identifier_to_count.contains_key(*key)) + .count(); + let union = set_a.identifier_to_count.len() + set_b.identifier_to_count.len() - intersection; + intersection as f32 / union as f32 +} + +// TODO +#[allow(dead_code)] +pub fn overlap_coefficient<'a>( + mut set_a: &'a IdentifierOccurrences, + mut set_b: &'a IdentifierOccurrences, +) -> f32 { + if set_a.identifier_to_count.len() > set_b.identifier_to_count.len() { + std::mem::swap(&mut set_a, &mut set_b); + } + let intersection = set_a + .identifier_to_count + .keys() + .filter(|key| set_b.identifier_to_count.contains_key(*key)) + .count(); + intersection as f32 / set_a.identifier_to_count.len() as f32 +} + +// TODO +#[allow(dead_code)] +pub fn weighted_jaccard_similarity<'a>( + mut set_a: &'a IdentifierOccurrences, + mut set_b: &'a IdentifierOccurrences, +) -> f32 { + if set_a.identifier_to_count.len() > set_b.identifier_to_count.len() { + std::mem::swap(&mut set_a, &mut set_b); + } + + let mut numerator = 0; + let mut denominator_a = 0; + let mut used_count_b = 0; + for (symbol, count_a) in set_a.identifier_to_count.iter() { + let count_b = set_b.identifier_to_count.get(symbol).unwrap_or(&0); + numerator += count_a.min(count_b); + denominator_a += count_a.max(count_b); + used_count_b += count_b; + } + + let denominator = denominator_a + (set_b.total_count - used_count_b); + if denominator == 0 { + 0.0 + } else { + numerator as f32 / denominator as f32 + } +} + +pub fn weighted_overlap_coefficient<'a>( + mut set_a: &'a IdentifierOccurrences, + mut set_b: &'a IdentifierOccurrences, +) -> f32 { + if set_a.identifier_to_count.len() > set_b.identifier_to_count.len() { + std::mem::swap(&mut set_a, &mut set_b); + } + + let mut numerator = 0; + for (symbol, count_a) in set_a.identifier_to_count.iter() { + let count_b = set_b.identifier_to_count.get(symbol).unwrap_or(&0); + numerator += count_a.min(count_b); + } + + let denominator = set_a.total_count.min(set_b.total_count); + if denominator == 0 { + 0.0 + } else { + numerator as f32 / denominator as f32 + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_split_identifier() { + assert_eq!(split_identifier("snake_case"), vec!["snake", "case"]); + assert_eq!(split_identifier("kebab-case"), vec!["kebab", "case"]); + assert_eq!(split_identifier("PascalCase"), vec!["Pascal", "Case"]); + assert_eq!(split_identifier("camelCase"), vec!["camel", "Case"]); + assert_eq!(split_identifier("XMLParser"), vec!["XML", "Parser"]); + } + + #[test] + fn test_similarity_functions() { + // 10 identifier parts, 8 unique + // Repeats: 2 "outline", 2 "items" + let set_a = IdentifierOccurrences::within_string( + "let mut outline_items = query_outline_items(&language, &tree, &source);", + ); + // 14 identifier parts, 11 unique + // Repeats: 2 "outline", 2 "language", 2 "tree" + let set_b = IdentifierOccurrences::within_string( + "pub fn query_outline_items(language: &Language, tree: &Tree, source: &str) -> Vec {", + ); + + // 6 overlaps: "outline", "items", "query", "language", "tree", "source" + // 7 non-overlaps: "let", "mut", "pub", "fn", "vec", "item", "str" + assert_eq!(jaccard_similarity(&set_a, &set_b), 6.0 / (6.0 + 7.0)); + + // Numerator is one more than before due to both having 2 "outline". + // Denominator is the same except for 3 more due to the non-overlapping duplicates + assert_eq!( + weighted_jaccard_similarity(&set_a, &set_b), + 7.0 / (7.0 + 7.0 + 3.0) + ); + + // Numerator is the same as jaccard_similarity. Denominator is the size of the smaller set, 8. + assert_eq!(overlap_coefficient(&set_a, &set_b), 6.0 / 8.0); + + // Numerator is the same as weighted_jaccard_similarity. Denominator is the total weight of + // the smaller set, 10. + assert_eq!(weighted_overlap_coefficient(&set_a, &set_b), 7.0 / 10.0); + } +} diff --git a/crates/edit_prediction_context/src/wip_requests.rs b/crates/edit_prediction_context/src/wip_requests.rs new file mode 100644 index 0000000000000000000000000000000000000000..9189587929725c8e1e4369fe5bd24cc641d6afab --- /dev/null +++ b/crates/edit_prediction_context/src/wip_requests.rs @@ -0,0 +1,35 @@ +// To discuss: What to send to the new endpoint? Thinking it'd make sense to put `prompt.rs` from +// `zeta_context.rs` in cloud. +// +// * Run excerpt selection at several different sizes, send the largest size with offsets within for +// the smaller sizes. +// +// * Longer event history. +// +// * Many more snippets than could fit in model context - allows ranking experimentation. + +pub struct Zeta2Request { + pub event_history: Vec, + pub excerpt: String, + pub excerpt_subsets: Vec, + /// Within `excerpt` + pub cursor_position: usize, + pub signatures: Vec, + pub retrieved_declarations: Vec, +} + +pub struct Zeta2ExcerptSubset { + /// Within `excerpt` text. + pub excerpt_range: Range, + /// Within `signatures`. + pub parent_signatures: Vec, +} + +pub struct ReferencedDeclaration { + pub text: Arc, + /// Range within `text` + pub signature_range: Range, + /// Indices within `signatures`. + pub parent_signatures: Vec, + // A bunch of score metrics +} From 82686bf94c35b2162102342002f8adc7e23bc496 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 18 Sep 2025 08:53:30 -0400 Subject: [PATCH 112/721] Start working on refreshing Python docs (#37880) - Reflect that basedpyright is the new primary language server - Discuss Ruff - Deemphasize manual venv configuration for language servers Release Notes: - N/A --------- Co-authored-by: Katie Geer Co-authored-by: Piotr --- docs/src/SUMMARY.md | 1 + docs/src/configuring-languages.md | 6 + docs/src/languages/python.md | 285 +++++++++++++++++++++++------- docs/src/toolchains.md | 28 +++ 4 files changed, 258 insertions(+), 62 deletions(-) create mode 100644 docs/src/toolchains.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index a470018b2c9e111f11144149855b833876c511d1..abf6cc127254e5f3b7fdc91219b638a8bf50eec6 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -15,6 +15,7 @@ - [Configuring Zed](./configuring-zed.md) - [Configuring Languages](./configuring-languages.md) + - [Toolchains](./toolchains.md) - [Key bindings](./key-bindings.md) - [All Actions](./all-actions.md) - [Snippets](./snippets.md) diff --git a/docs/src/configuring-languages.md b/docs/src/configuring-languages.md index fae19686c70731252f38216cf1c30b692a3985d9..16d1a7ecd41c09b6b207e8a14c14ab8619ad625b 100644 --- a/docs/src/configuring-languages.md +++ b/docs/src/configuring-languages.md @@ -135,6 +135,12 @@ In this example: This configuration allows you to tailor the language server setup to your specific needs, ensuring that you get the most suitable functionality for your development workflow. +### Toolchains + +Some language servers need to be configured with a current "toolchain", which is an installation of a specific version of a programming language compiler or/and interpreter, which can possibly include a full set of dependencies of a project. +An example of what Zed considers a toolchain is a virtual environment in Python. +Not all languages in Zed support toolchain discovery and selection, but for those that do, you can specify the toolchain from a toolchain picker (via {#action toolchain::Select}). To learn more about toolchains in Zed, see [`toolchains`](./toolchains.md). + ### Configuring Language Servers Many language servers accept custom configuration options. You can set these in the `lsp` section of your `settings.json`: diff --git a/docs/src/languages/python.md b/docs/src/languages/python.md index 5d90065de055b3dd5568f0093ac4ba49f4bb0536..f4960bc8e8d3643bab6310020ab7d7834a6a85b3 100644 --- a/docs/src/languages/python.md +++ b/docs/src/languages/python.md @@ -1,59 +1,134 @@ -# Python +# How to Set Up Python in Zed Python support is available natively in Zed. - Tree-sitter: [tree-sitter-python](https://github.com/zed-industries/tree-sitter-python) - Language Servers: + - [DetachHead/basedpyright](https://github.com/DetachHead/basedpyright) + - [astral-sh/ruff](https://github.com/astral-sh/ruff) + - [astral-sh/ty](https://github.com/astral-sh/ty) - [microsoft/pyright](https://github.com/microsoft/pyright) - [python-lsp/python-lsp-server](https://github.com/python-lsp/python-lsp-server) (PyLSP) - Debug Adapter: [debugpy](https://github.com/microsoft/debugpy) -## Language Servers +## Install Python -Zed supports multiple Python language servers some of which may require configuration to work properly. +You'll need both Zed and Python installed before you can begin. -See: [Working with Language Servers](https://zed.dev/docs/configuring-languages#working-with-language-servers) for more information. +### Step 1: Install Python -## Virtual Environments in the Terminal {#terminal-detect_venv} +Zed does not bundle a Python runtime, so you’ll need to install one yourself. +Choose one of the following options: -Zed will detect Python virtual environments and automatically activate them in terminal if available. -See: [detect_venv documentation](../configuring-zed.md#terminal-detect_venv) for more. +- uv (recommended) -## PyLSP +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` -[python-lsp-server](https://github.com/python-lsp/python-lsp-server/), more commonly known as PyLSP, by default integrates with a number of external tools (autopep8, mccabe, pycodestyle, yapf) while others are optional and must be explicitly enabled and configured (flake8, pylint). +To learn more, visit [Astral’s installation guide](https://docs.astral.sh/uv/getting-started/installation/). -See [Python Language Server Configuration](https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md) for more. +- Homebrew: + +```bash +brew install python +``` + +- Python.org installer: Download the latest version from [python.org/downloads](https://python.org/downloads). + +### Step 2: Verify Python Installation + +Confirm Python is installed and available in your shell: + +```bash +python3 --version +``` + +You should see an output like `Python 3.x.x`. + +## Open Your First Python Project in Zed + +Once Zed and Python are installed, open a folder containing Python code to start working. + +### Step 1: Launch Zed with a Python Project + +Open Zed. +From the menu bar, choose File > Open Folder, or launch from the terminal: + +```bash +zed path/to/your/project +``` + +Zed will recognize `.py` files automatically using its native tree-sitter-python parser, with no plugins or manual setup required. + +### Step 2: Use the Integrated Terminal (Optional) + +Zed includes an integrated terminal, accessible from the bottom panel. If Zed detects that your project is using a [virtual environment](#virtual-environments), it will be activated automatically in newly-created terminals. You can configure this behavior with the [`detect_venv`](../configuring-zed.md#terminal-detect_venv) setting. + +## Configure Python Language Servers in Zed + +Zed provides several Python language servers out of the box. By default, [basedpyright](https://github.com/DetachHead/basedpyright) is the primary language server, and [Ruff](https://github.com/astral-sh/ruff) is used for formatting and linting. -## PyRight +Other built-in language servers are: -### PyRight Configuration +- [Ty](https://docs.astral.sh/ty/)—Up-and-coming language server from Astral, built for speed. +- [Pyright](https://github.com/microsoft/pyright)—The basis for basedpyright. +- [PyLSP](https://github.com/python-lsp/python-lsp-server)—A plugin-based language server that integrates with tools like `pycodestyle`, `autopep8`, and `yapf`. -The [pyright](https://github.com/microsoft/pyright) language server offers flexible configuration options specified in a JSON-formatted text configuration. By default, the file is called `pyrightconfig.json` and is located within the root directory of your project. Pyright settings can also be specified in a `[tool.pyright]` section of a `pyproject.toml` file. A `pyrightconfig.json` file always takes precedence over `pyproject.toml` if both are present. +These are disabled by default, but can be enabled in your settings. For example: -For more information, see the Pyright [configuration documentation](https://microsoft.github.io/pyright/#/configuration). +```json +{ + "languages": { + "Python": { + "language_servers": { + // Disable basedpyright and enable Ty, and otherwise + // use the default configuration. + "ty", "!basedpyright", ".." + } + } + } +} +``` + +See: [Working with Language Servers](https://zed.dev/docs/configuring-languages#working-with-language-servers) for more information about how to enable and disable language servers. + +### Basedpyright + +[basedpyright](https://docs.basedpyright.com/latest/) is the primary Python language server in Zed beginning with Zed v0.204.0. It provides core language server functionality like navigation (go to definition/find all references) and type checking. Compared to Pyright, it adds support for additional language server features (like inlay hints) and checking rules. + +Note that while basedpyright in isolation defaults to the `recommended` [type-checking mode](https://docs.basedpyright.com/latest/benefits-over-pyright/better-defaults/#typecheckingmode), Zed configures it to use the less-strict `standard` mode by default, which matches the behavior of Pyright. You can set the type-checking mode for your project using the `typeCheckingMode` setting in `pyrightconfig.json` or `pyproject.toml`, which will override Zed's default. Read on more for more details about how to configure basedpyright. -### PyRight Settings +#### Basedpyright Configuration -The [pyright](https://github.com/microsoft/pyright) language server also accepts specific LSP-related settings, not necessarily connected to a project. These can be changed in the `lsp` section of your `settings.json`. +basedpyright reads configuration options from two different kinds of sources: + +- Language server settings ("workspace configuration"), which must be configured per-editor (using `settings.json` in Zed's case) but apply to all projects opened in that editor +- Configuration files (`pyrightconfig.json`, `pyproject.toml`), which are editor-independent but specific to the project where they are placed + +As a rule of thumb, options that are only relevant when using basedpyright from an editor must be set in language server settings, and options that are relevant even if you're running it [as a command-line tool](https://docs.basedpyright.com/latest/configuration/command-line/) must be set in configuration files. Settings related to inlay hints are examples of the first category, and the [diagnostic category](https://docs.basedpyright.com/latest/configuration/config-files/#diagnostic-categories) settings are examples of the second category. + +Examples of both kinds of configuration are provided below. Refer to the basedpyright documentation on [language server settings](https://docs.basedpyright.com/latest/configuration/language-server-settings/) and [configuration files](https://docs.basedpyright.com/latest/configuration/config-files/) for comprehensive lists of available options. + +##### Language server settings + +Language server settings for basedpyright in Zed can be set in the `lsp` section of your `settings.json`. For example, in order to: -- use strict type-checking level - diagnose all files in the workspace instead of the only open files default -- provide the path to a specific Python interpreter +- disable inlay hints on function arguments + +You can use the following configuration: ```json { "lsp": { - "pyright": { + "basedpyright": { "settings": { - "python.analysis": { + "basedpyright.analysis": { "diagnosticMode": "workspace", - "typeCheckingMode": "strict" - }, - "python": { - "pythonPath": ".venv/bin/python" + "inlayHints.callArgumentNames": false } } } @@ -61,54 +136,103 @@ For example, in order to: } ``` -For more information, see the Pyright [settings documentation](https://microsoft.github.io/pyright/#/settings). +##### Configuration files + +basedpyright reads project-specific configuration from the `pyrightconfig.json` configuration file and from the `[tool.basedpyright]` and `[tool.pyright]` sections of `pyproject.toml` manifests. `pyrightconfig.json` overrides `pyproject.toml` if configuration is present in both places. + +Here's an example `pyrightconfig.json` file that configures basedpyright to use the `strict` type-checking mode and not to issue diagnostics for any files in `__pycache__` directories: + +```json +{ + "typeCheckingMode": "strict", + "ignore": ["**/__pycache__"] +} +``` + +### PyLSP -### Pyright Virtual environments +[python-lsp-server](https://github.com/python-lsp/python-lsp-server/), more commonly known as PyLSP, by default integrates with a number of external tools (autopep8, mccabe, pycodestyle, yapf) while others are optional and must be explicitly enabled and configured (flake8, pylint). + +See [Python Language Server Configuration](https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md) for more. -A Python [virtual environment](https://docs.python.org/3/tutorial/venv.html) allows you to store all of a project's dependencies, including the Python interpreter and package manager, in a single directory that's isolated from any other Python projects on your computer. +## Virtual Environments -By default, the Pyright language server will look for Python packages in the default global locations. But you can also configure Pyright to use the packages installed in a given virtual environment. +[Virtual environments](https://docs.python.org/3/library/venv.html) are a useful tool for fixing a Python version and set of dependencies for a specific project, in a way that's isolated from other projects on the same machine. Zed has built-in support for discovering, configuring, and activating virtual environments, based on the language-agnostic concept of a [toolchain](../toolchains.md). -To do this, create a JSON file called `pyrightconfig.json` at the root of your project. This file must include two keys: +Note that if you have a global Python installation, it is also counted as a toolchain for Zed's purposes. -- `venvPath`: a relative path from your project directory to any directory that _contains_ one or more virtual environment directories -- `venv`: the name of a virtual environment directory +### Create a Virtual Environment -For example, a common approach is to create a virtual environment directory called `.venv` at the root of your project directory with the following commands: +If your project doesn't have a virtual environment set up already, you can create one as follows: -```sh -# create a virtual environment in the .venv directory +```bash python3 -m venv .venv -# set up the current shell to use that virtual environment -source .venv/bin/activate ``` -Having done that, you would create a `pyrightconfig.json` with the following content: +Alternatively, if you're using `uv`, running `uv sync` will create a virtual environment the first time you run it. + +### How Zed Uses Python Toolchains + +Zed uses the selected Python toolchain for your project in the following ways: + +- Built-in language servers will be automatically configured with the path to the toolchain's Python interpreter and, if applicable, virtual environment. This is important so that they can resolve dependencies. (Note that language servers provided by extensions can't be automatically configured like this currently.) +- Python tasks (such as pytest tests) will be run using the toolchain's Python interpreter. +- If the toolchain is a virtual environment, the environment's activation script will be run automatically when you launch a new shell in Zed's integrated terminal, giving you convenient access to the selected Python interpreter and dependency set. +- If a built-in language server is installed in the active virtual environment, that binary will be used instead of Zed's private automatically-installed binary. This also applies to debugpy. + +### Selecting a Toolchain + +For most projects, Zed will automatically select the right Python toolchain. In complex projects with multiple virtual environments, it might be necessary to override this selection. You can use the [toolchain selector](../toolchains.md#selecting-toolchains) to pick a toolchain from the list discovered by Zed, or [specify the path to a toolchain manually](../toolchains.md#adding-toolchains-manually) if it's not on the list. + +## Code Formatting & Linting + +Zed provides the [Ruff](https://docs.astral.sh/ruff/) formatter and linter for Python code. (Specifically, Zed runs Ruff as an LSP server using the `ruff server` subcommand.) Both formatting and linting are enabled by default, including format-on-save. + +### Configuring formatting + +You can disable format-on-save for Python files in your `settings.json`: ```json { - "venvPath": ".", - "venv": ".venv" + "languages": { + "Python": { + "format_on_save": false + } + } } ``` -If you prefer to use a `pyproject.toml` file, you can add the following section: +Alternatively, you can use the `black` command-line tool for Python formatting, while keeping Ruff enabled for linting: -```toml -[tool.pyright] -venvPath = "." -venv = ".venv" +```json +{ + "languages": { + "Python": { + "formatter": { + "external": { + "command": "black", + "arguments": ["--stdin-filename", "{buffer_path}", "-"] + } + } + // Or use `"formatter": null` to disable formatting entirely. + } + } +} ``` -You can also configure this option directly in your `settings.json` file ([pyright settings](#pyright-settings)), as recommended in [Configuring Your Python Environment](https://microsoft.github.io/pyright/#/import-resolution?id=configuring-your-python-environment). +### Configuring Ruff + +Like basedpyright, Ruff reads options from both Zed's language server settings and configuration files (`ruff.toml`) when used in Zed. Unlike basedpyright, _all_ options can be configured in either of these locations, so the choice of where to put your Ruff configuration comes down to whether you want it to be shared between projects but specific to Zed (in which case you should use language server settings), or specific to one project but common to all Ruff invocations (in which case you should use `ruff.toml`). + +Here's an example of using language server settings in Zed's `settings.json` to disable all Ruff lints in Zed (while still using Ruff as a formatter): ```json { "lsp": { - "pyright": { - "settings": { - "python": { - "pythonPath": ".venv/bin/python" + "ruff": { + "initialization_options": { + "settings": { + "exclude": ["*"] } } } @@ -116,24 +240,40 @@ You can also configure this option directly in your `settings.json` file ([pyrig } ``` -### Code formatting & Linting +And here's an example `ruff.toml` with linting and formatting options, adapted from the Ruff documentation: -The Pyright language server does not provide code formatting or linting. If you want to detect lint errors and reformat your Python code upon saving, you'll need to set up. +```toml +[lint] +# Avoid enforcing line-length violations (`E501`) +ignore = ["E501"] -A common tool for formatting Python code is [Ruff](https://docs.astral.sh/ruff/). It is another tool written in Rust, an extremely fast Python linter and code formatter. It is available through the [Ruff extension](https://github.com/zed-industries/zed/tree/main/extensions/ruff/). To configure the Ruff extension to work within Zed, see the setup documentation [here](https://docs.astral.sh/ruff/editors/setup/#zed). +[format] +# Use single quotes when formatting. +quote-style = "single" +``` - +For more details, refer to the Ruff documentation about [configuration files](https://docs.astral.sh/ruff/configuration/) and [language server settings](https://docs.astral.sh/ruff/editors/settings/), and the [list of options](https://docs.astral.sh/ruff/settings/). ## Debugging -Zed supports zero-configuration debugging of Python module entry points and pytest tests. -Run {#action debugger::Start} ({#kb debugger::Start}) to see a contextual list for the current project. -For greater control, you can add debug configurations to `.zed/debug.json`. See the examples below. +Zed supports Python debugging through the `debugpy` adapter. You can start with no configuration or define custom launch profiles in `.zed/debug.json`. + +### Start Debugging with No Setup + +Zed can automatically detect debuggable Python entry points. Press F4 (or run debugger: start from the Command Palette) to see available options for your current project. +This works for: + +- Python scripts +- Modules +- pytest tests -### Debug Active File +Zed uses `debugpy` under the hood, but no manual adapter configuration is required. + +### Define Custom Debug Configurations + +For reusable setups, create a `.zed/debug.json` file in your project root. This gives you more control over how Zed runs and debugs your code. + +#### Debug Active File ```json [ @@ -146,9 +286,11 @@ For greater control, you can add debug configurations to `.zed/debug.json`. See ] ``` -### Flask App +This runs the file currently open in the editor. -For a common Flask Application with a file structure similar to the following: +#### Debug a Flask App + +For projects using Flask, you can define a full launch configuration: ``` .venv/ @@ -190,3 +332,22 @@ requirements.txt } ] ``` + +These can be combined to tailor the experience for web servers, test runners, or custom scripts. + +## Troubleshoot and Maintain a Productive Python Setup + +Zed is designed to minimize configuration overhead, but occasional issues can still arise—especially around environments, language servers, or tooling. Here's how to keep your Python setup working smoothly. + +### Resolve Language Server Startup Issues + +If a language server isn't responding or features like diagnostics or autocomplete aren't available: + +- Check your Zed log (using the {#action zed::OpenLog} action) for errors related to the language server you're trying to use. This is where you're likely to find useful information if the language server failed to start up at all. +- Use the language server logs view to understand the lifecycle of the affected language server. You can access this view using the {#action dev::OpenLanguageServerLogs} action, or by clicking the lightning bolt icon in the status bar and selecting your language server. The most useful pieces of data in this view are: + - "Server Logs", which shows any errors printed by the language server + - "Server Info", which shows details about how the language server was started +- Verify your `settings.json` or `pyrightconfig.json` is syntactically correct. +- Restart Zed to reinitialize language server connections, or try restarting the language server using the {#action editor::RestartLanguageServer} + +If the language server is failing to resolve imports, and you're using a virtual environment, make sure that the right environment is chosen in the selector. You can use "Server Info" view to confirm which virtual environment Zed is sending to the language server—look for the `* Configuration` section at the end. diff --git a/docs/src/toolchains.md b/docs/src/toolchains.md new file mode 100644 index 0000000000000000000000000000000000000000..213042d5e4690bf3313b617839d9290595a4304f --- /dev/null +++ b/docs/src/toolchains.md @@ -0,0 +1,28 @@ +# Toolchains + +Zed projects offer a dedicated UI for toolchain selection, which lets you pick a set of tools for working with a given language in a current project. +Imagine you're working with Python project, which has virtual environments that encapsulate a set of dependencies of your project along with a suitable interpreter to run it with. The language server has to know which virtual environment you're working with, as it uses it to understand your project's code. +With toolchain selector, you don't need to spend time configuring your language server to point it at the right virtual environment directory - you can just select the right virtual environment (toolchain) from a dropdown. +You can even select different toolchains for different subprojects within your Zed project. A definition of a subproject is language-specific. +In collaborative scenarios, only the project owner can see and modify an active toolchain. +In [remote projects](./remote-development.md)., you can use the toolchain selector to control the active toolchain on the SSH host. When [sharing your project](./collaboration.md), the toolchain selector is not available to guests. + +## Why do we need toolchains? + +The active toolchain is relevant for launching language servers, which may need it to function properly - it may not be able to resolve dependencies, which in turn may make functionalities like "Go to definition" or "Code completions" unavailable. + +The active toolchain is also relevant when launching a shell in the terminal panel: some toolchains provide "activation scripts" for shells, which make those toolchains available in the shell environment for your convenience. Zed will run these activation scripts automatically when you create a new terminal. + +This also applies to [tasks](./tasks.md) - Zed tasks behave "as if" you opened a new terminal tab and ran a given task invocation yourself, which in turn means that Zed task execution is affected by the active toolchain and its activation script. + +## Selecting toolchains + +The active toolchain (if there is one) is displayed in the status bar (on the right hand side). Click on it to access the toolchain selector - you can also use an action from a command palette ({#action toolchain::Select}). + +Zed will automatically infer a set of toolchains to choose from based on the project you're working with. A default will also be selected on your behalf on a best-effort basis when you open a project for the first time. + +The toolchain selection applies to a current subproject, which - depending on the structure of your Zed project - might be your whole project or just a subset of it. For example, if you have a monorepo with multiple subprojects, you might want to select a different toolchain for each subproject. + +## Adding toolchains manually + +If automatic detection does not suffice for you, you can add toolchains manually. To do that, click on the "Add toolchain" button in the toolchain selector. From there you can provide a path to a toolchain and set a name of your liking for it. From 21d8b1992622556851090c351d51fb375d1e0832 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 18 Sep 2025 16:35:06 +0200 Subject: [PATCH 113/721] dap: Add more debug logs for child's stderr (#38418) Without this, I would never have converged on @cole-miller's patch https://github.com/zed-industries/zed/pull/38380 when debugging codelldb not spawning in WSL! Release Notes: - N/A --- crates/dap/src/client.rs | 3 +++ crates/dap/src/transport.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 2590bf5c8b0db8e70a7897b8de4bc878187e4daa..15801e989169677f6e42bdd7b9c5642d82ea644a 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -118,6 +118,7 @@ impl DebugAdapterClient { R::COMMAND, sequence_id ); + log::debug!(" request: {request:?}"); self.send_message(Message::Request(request)).await?; @@ -130,6 +131,8 @@ impl DebugAdapterClient { command, sequence_id ); + log::debug!(" response: {response:?}"); + match response.success { true => { if let Some(json) = response.body { diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index f9fbbfc84295bfba946ad96b5eb701d13c6aa52c..7d0af4d0b3048092933c00bfe2f5d755e00d34d3 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -262,6 +262,7 @@ impl TransportDelegate { break; } } + log::debug!("stderr: {line}"); for (kind, handler) in log_handlers.lock().iter_mut() { if matches!(kind, LogKind::Adapter) { From 589e2c0fe4ae4327d415be604b9337132b9c40c2 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:48:36 -0300 Subject: [PATCH 114/721] agent: Make settings view more consistent across different sections (#38419) Closes https://github.com/zed-industries/zed/issues/37660 This PR makes sections in the AI settings UI more consistent with each other and also just overall simpler. One of the main changes here is adding the tools from a given MCP server in a modal (as opposed to in a disclosure within the settings view). That's mostly an artifact of wanting to make all of the items within sections look more of the same. Then, in the process of doing so, also changed the logic that we were using to display MCP servers; previously, in the case of extension-based servers, we were only showing those that were _configured_, which felt wrong because you should be able to see everything you have _installed_, despite of its status (configured or not). However, there's still a bit of a bug (to be solved in a follow-up PR), which already existed but it was just not visible given we'd only display configured servers: an MCP server installed through an extension stays as a "custom server" until it is configured. If you don't configure it, you can't also uninstall it from the settings view (though it is possible to do so via the extensions UI). Release Notes: - agent: Improve settings view UI and solve issue where MCP servers would get unsorted upon turning them on and off (they're all alphabetically sorted now). --- assets/keymaps/default-linux.json | 7 + assets/keymaps/default-macos.json | 7 + assets/keymaps/default-windows.json | 7 + crates/agent_ui/src/agent_configuration.rs | 342 ++++++++---------- .../configure_context_server_tools_modal.rs | 176 +++++++++ crates/project/src/context_server_store.rs | 9 + crates/ui/src/components/divider.rs | 2 + 7 files changed, 367 insertions(+), 183 deletions(-) create mode 100644 crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 8ca0a5d42094db8b4b37c7e6919da0f7a6bd41db..ceb25c1835172068a809c6e5492d000cde7da5fe 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -1140,6 +1140,13 @@ "ctrl-enter": "menu::Confirm" } }, + { + "context": "ContextServerToolsModal", + "use_key_equivalents": true, + "bindings": { + "escape": "menu::Cancel" + } + }, { "context": "OnboardingAiConfigurationModal", "use_key_equivalents": true, diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 0ef4757fc523c9ae145175da07a52ced322efa0c..3d5887de75bf13218985c33ab37b8b54ca9ea0a1 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -1244,6 +1244,13 @@ "cmd-enter": "menu::Confirm" } }, + { + "context": "ContextServerToolsModal", + "use_key_equivalents": true, + "bindings": { + "escape": "menu::Cancel" + } + }, { "context": "OnboardingAiConfigurationModal", "use_key_equivalents": true, diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 78d5e4e698daefee5a57b04d6a8548fb948233b1..9165840d695af73a41aedded9b8037ffbce8ccbf 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1160,6 +1160,13 @@ "ctrl-enter": "menu::Confirm" } }, + { + "context": "ContextServerToolsModal", + "use_key_equivalents": true, + "bindings": { + "escape": "menu::Cancel" + } + }, { "context": "OnboardingAiConfigurationModal", "use_key_equivalents": true, diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index aacfb423539496e6c5cb93ad8c12f1ed8ede346a..69044d9c663e6b42b0a73fb9bc29738a3c3fc737 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -1,5 +1,6 @@ mod add_llm_provider_modal; mod configure_context_server_modal; +mod configure_context_server_tools_modal; mod manage_profiles_modal; mod tool_picker; @@ -42,12 +43,12 @@ use workspace::{Workspace, create_and_open_local_file}; use zed_actions::ExtensionCategoryFilter; pub(crate) use configure_context_server_modal::ConfigureContextServerModal; +pub(crate) use configure_context_server_tools_modal::ConfigureContextServerToolsModal; pub(crate) use manage_profiles_modal::ManageProfilesModal; use crate::{ - AddContextServer, ExternalAgent, NewExternalAgentThread, + AddContextServer, agent_configuration::add_llm_provider_modal::{AddLlmProviderModal, LlmCompatibleProvider}, - placeholder_command, }; pub struct AgentConfiguration { @@ -200,9 +201,8 @@ impl AgentConfiguration { .when(is_expanded, |this| this.mb_2()) .child( div() - .opacity(0.6) .px_2() - .child(Divider::horizontal().color(DividerColor::Border)), + .child(Divider::horizontal().color(DividerColor::BorderFaded)), ) .child( h_flex() @@ -227,7 +227,7 @@ impl AgentConfiguration { .child( h_flex() .w_full() - .gap_2() + .gap_1p5() .child( Icon::new(provider.icon()) .size(IconSize::Small) @@ -345,6 +345,8 @@ impl AgentConfiguration { PopoverMenu::new("add-provider-popover") .trigger( Button::new("add-provider", "Add Provider") + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) .icon_position(IconPosition::Start) .icon(IconName::Plus) .icon_size(IconSize::Small) @@ -533,10 +535,6 @@ impl AgentConfiguration { } } - fn card_item_bg_color(&self, cx: &mut Context) -> Hsla { - cx.theme().colors().background.opacity(0.25) - } - fn card_item_border_color(&self, cx: &mut Context) -> Hsla { cx.theme().colors().border.opacity(0.6) } @@ -546,7 +544,73 @@ impl AgentConfiguration { window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let context_server_ids = self.context_server_store.read(cx).configured_server_ids(); + let mut registry_descriptors = self + .context_server_store + .read(cx) + .all_registry_descriptor_ids(cx); + let server_count = registry_descriptors.len(); + + // Sort context servers: non-mcp-server ones first, then mcp-server ones + registry_descriptors.sort_by(|a, b| { + let has_mcp_prefix_a = a.0.starts_with("mcp-server-"); + let has_mcp_prefix_b = b.0.starts_with("mcp-server-"); + + match (has_mcp_prefix_a, has_mcp_prefix_b) { + // If one has mcp-server- prefix and other doesn't, non-mcp comes first + (true, false) => std::cmp::Ordering::Greater, + (false, true) => std::cmp::Ordering::Less, + // If both have same prefix status, sort by appropriate key + _ => { + let get_sort_key = |server_id: &str| -> String { + if let Some(suffix) = server_id.strip_prefix("mcp-server-") { + suffix.to_string() + } else { + server_id.to_string() + } + }; + + let key_a = get_sort_key(&a.0); + let key_b = get_sort_key(&b.0); + key_a.cmp(&key_b) + } + } + }); + + let add_server_popover = PopoverMenu::new("add-server-popover") + .trigger( + Button::new("add-server", "Add Server") + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) + .icon_position(IconPosition::Start) + .icon(IconName::Plus) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .label_size(LabelSize::Small), + ) + .anchor(gpui::Corner::TopRight) + .menu({ + move |window, cx| { + Some(ContextMenu::build(window, cx, |menu, _window, _cx| { + menu.entry("Add Custom Server", None, { + |window, cx| window.dispatch_action(AddContextServer.boxed_clone(), cx) + }) + .entry("Install from Extensions", None, { + |window, cx| { + window.dispatch_action( + zed_actions::Extensions { + category_filter: Some( + ExtensionCategoryFilter::ContextServers, + ), + id: None, + } + .boxed_clone(), + cx, + ) + } + }) + })) + } + }); v_flex() .p(DynamicSpacing::Base16.rems(cx)) @@ -555,18 +619,26 @@ impl AgentConfiguration { .border_b_1() .border_color(cx.theme().colors().border) .child( - v_flex() - .gap_0p5() - .child(Headline::new("Model Context Protocol (MCP) Servers")) + h_flex() + .w_full() + .items_start() + .justify_between() + .gap_1() .child( - Label::new( - "All context servers connected through the Model Context Protocol.", - ) - .color(Color::Muted), - ), + v_flex() + .gap_0p5() + .child(Headline::new("Model Context Protocol (MCP) Servers")) + .child( + Label::new( + "All MCP servers connected directly or via a Zed extension.", + ) + .color(Color::Muted), + ), + ) + .child(add_server_popover), ) - .map(|parent| { - if context_server_ids.is_empty() { + .child(v_flex().w_full().gap_1().map(|parent| { + if registry_descriptors.is_empty() { parent.child( h_flex() .p_4() @@ -582,56 +654,28 @@ impl AgentConfiguration { ), ) } else { - parent.children(context_server_ids.into_iter().map(|context_server_id| { - self.render_context_server(context_server_id, window, cx) - })) + { + parent.children(registry_descriptors.into_iter().enumerate().flat_map( + |(index, context_server_id)| { + let mut elements: Vec = vec![ + self.render_context_server(context_server_id, window, cx) + .into_any_element(), + ]; + + if index < server_count - 1 { + elements.push( + Divider::horizontal() + .color(DividerColor::BorderFaded) + .into_any_element(), + ); + } + + elements + }, + )) + } } - }) - .child( - h_flex() - .justify_between() - .gap_1p5() - .child( - h_flex().w_full().child( - Button::new("add-context-server", "Add Custom Server") - .style(ButtonStyle::Filled) - .layer(ElevationIndex::ModalSurface) - .full_width() - .icon(IconName::Plus) - .icon_size(IconSize::Small) - .icon_position(IconPosition::Start) - .on_click(|_event, window, cx| { - window.dispatch_action(AddContextServer.boxed_clone(), cx) - }), - ), - ) - .child( - h_flex().w_full().child( - Button::new( - "install-context-server-extensions", - "Install MCP Extensions", - ) - .style(ButtonStyle::Filled) - .layer(ElevationIndex::ModalSurface) - .full_width() - .icon(IconName::ToolHammer) - .icon_size(IconSize::Small) - .icon_position(IconPosition::Start) - .on_click(|_event, window, cx| { - window.dispatch_action( - zed_actions::Extensions { - category_filter: Some( - ExtensionCategoryFilter::ContextServers, - ), - id: None, - } - .boxed_clone(), - cx, - ) - }), - ), - ), - ) + })) } fn render_context_server( @@ -724,7 +768,7 @@ impl AgentConfiguration { IconButton::new("context-server-config-menu", IconName::Settings) .icon_color(Color::Muted) .icon_size(IconSize::Small), - Tooltip::text("Open MCP server options"), + Tooltip::text("Configure MCP Server"), ) .anchor(Corner::TopRight) .menu({ @@ -733,6 +777,8 @@ impl AgentConfiguration { let language_registry = self.language_registry.clone(); let context_server_store = self.context_server_store.clone(); let workspace = self.workspace.clone(); + let tools = self.tools.clone(); + move |window, cx| { Some(ContextMenu::build(window, cx, |menu, _window, _cx| { menu.entry("Configure Server", None, { @@ -749,7 +795,28 @@ impl AgentConfiguration { ) .detach_and_log_err(cx); } - }) + }).when(tool_count >= 1, |this| this.entry("View Tools", None, { + let context_server_id = context_server_id.clone(); + let tools = tools.clone(); + let workspace = workspace.clone(); + + move |window, cx| { + let context_server_id = context_server_id.clone(); + let tools = tools.clone(); + let workspace = workspace.clone(); + + workspace.update(cx, |workspace, cx| { + ConfigureContextServerToolsModal::toggle( + context_server_id, + tools, + workspace, + window, + cx, + ); + }) + .ok(); + } + })) .separator() .entry("Uninstall", None, { let fs = fs.clone(); @@ -820,17 +887,11 @@ impl AgentConfiguration { v_flex() .id(item_id.clone()) - .border_1() - .rounded_md() - .border_color(self.card_item_border_color(cx)) - .bg(self.card_item_bg_color(cx)) - .overflow_hidden() .child( h_flex() - .p_1() .justify_between() .when( - error.is_some() || are_tools_expanded && tool_count >= 1, + error.is_none() && are_tools_expanded && tool_count >= 1, |element| { element .border_b_1() @@ -841,31 +902,12 @@ impl AgentConfiguration { h_flex() .flex_1() .min_w_0() - .child( - Disclosure::new( - "tool-list-disclosure", - are_tools_expanded || error.is_some(), - ) - .disabled(tool_count == 0) - .on_click(cx.listener({ - let context_server_id = context_server_id.clone(); - move |this, _event, _window, _cx| { - let is_open = this - .expanded_context_server_tools - .entry(context_server_id.clone()) - .or_insert(false); - - *is_open = !*is_open; - } - })), - ) .child( h_flex() .id(SharedString::from(format!("tooltip-{}", item_id))) .h_full() .w_3() - .ml_1() - .mr_1p5() + .mr_2() .justify_center() .tooltip(Tooltip::text(tooltip_text)) .child(status_indicator), @@ -969,8 +1011,8 @@ impl AgentConfiguration { if let Some(error) = error { return parent.child( h_flex() - .p_2() .gap_2() + .pr_4() .items_start() .child( h_flex() @@ -998,37 +1040,11 @@ impl AgentConfiguration { return parent; } - parent.child(v_flex().py_1p5().px_1().gap_1().children( - tools.iter().enumerate().map(|(ix, tool)| { - h_flex() - .id(("tool-item", ix)) - .px_1() - .gap_2() - .justify_between() - .hover(|style| style.bg(cx.theme().colors().element_hover)) - .rounded_sm() - .child( - Label::new(tool.name()) - .buffer_font(cx) - .size(LabelSize::Small), - ) - .child( - Icon::new(IconName::Info) - .size(IconSize::Small) - .color(Color::Ignored), - ) - .tooltip(Tooltip::text(tool.description())) - }), - )) + parent }) } fn render_agent_servers_section(&mut self, cx: &mut Context) -> impl IntoElement { - let custom_settings = cx - .global::() - .get::(None) - .custom - .clone(); let user_defined_agents = self .agent_server_store .read(cx) @@ -1036,22 +1052,12 @@ impl AgentConfiguration { .filter(|name| name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME) .cloned() .collect::>(); + let user_defined_agents = user_defined_agents .into_iter() .map(|name| { - self.render_agent_server( - IconName::Ai, - name.clone(), - ExternalAgent::Custom { - name: name.clone().into(), - command: custom_settings - .get(&name.0) - .map(|settings| settings.command.clone()) - .unwrap_or(placeholder_command()), - }, - cx, - ) - .into_any_element() + self.render_agent_server(IconName::Ai, name) + .into_any_element() }) .collect::>(); @@ -1075,6 +1081,8 @@ impl AgentConfiguration { .child(Headline::new("External Agents")) .child( Button::new("add-agent", "Add Agent") + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) .icon_position(IconPosition::Start) .icon(IconName::Plus) .icon_size(IconSize::Small) @@ -1107,14 +1115,11 @@ impl AgentConfiguration { .child(self.render_agent_server( IconName::AiGemini, "Gemini CLI", - ExternalAgent::Gemini, - cx, )) + .child(Divider::horizontal().color(DividerColor::BorderFaded)) .child(self.render_agent_server( IconName::AiClaude, "Claude Code", - ExternalAgent::ClaudeCode, - cx, )) .children(user_defined_agents), ) @@ -1124,47 +1129,18 @@ impl AgentConfiguration { &self, icon: IconName, name: impl Into, - agent: ExternalAgent, - cx: &mut Context, ) -> impl IntoElement { - let name = name.into(); - h_flex() - .p_1() - .pl_2() - .gap_1p5() - .justify_between() - .border_1() - .rounded_md() - .border_color(self.card_item_border_color(cx)) - .bg(self.card_item_bg_color(cx)) - .overflow_hidden() - .child( - h_flex() - .gap_1p5() - .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted)) - .child(Label::new(name.clone())), - ) - .child( - Button::new( - SharedString::from(format!("start_acp_thread-{name}")), - "Start New Thread", - ) - .layer(ElevationIndex::ModalSurface) - .label_size(LabelSize::Small) - .icon(IconName::Thread) - .icon_position(IconPosition::Start) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .on_click(move |_, window, cx| { - window.dispatch_action( - NewExternalAgentThread { - agent: Some(agent.clone()), - } - .boxed_clone(), - cx, - ); - }), - ) + h_flex().gap_1p5().justify_between().child( + h_flex() + .gap_1p5() + .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted)) + .child(Label::new(name.into())) + .child( + Icon::new(IconName::Check) + .size(IconSize::Small) + .color(Color::Success), + ), + ) } } diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a59806972ecf1b6cbc0702809c98acf1a86b387 --- /dev/null +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs @@ -0,0 +1,176 @@ +use assistant_tool::{ToolSource, ToolWorkingSet}; +use context_server::ContextServerId; +use gpui::{ + DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Window, prelude::*, +}; +use ui::{Divider, DividerColor, Modal, ModalHeader, WithScrollbar, prelude::*}; +use workspace::{ModalView, Workspace}; + +pub struct ConfigureContextServerToolsModal { + context_server_id: ContextServerId, + tools: Entity, + focus_handle: FocusHandle, + expanded_tools: std::collections::HashMap, + scroll_handle: ScrollHandle, +} + +impl ConfigureContextServerToolsModal { + fn new( + context_server_id: ContextServerId, + tools: Entity, + _window: &mut Window, + cx: &mut Context, + ) -> Self { + Self { + context_server_id, + tools, + focus_handle: cx.focus_handle(), + expanded_tools: std::collections::HashMap::new(), + scroll_handle: ScrollHandle::new(), + } + } + + pub fn toggle( + context_server_id: ContextServerId, + tools: Entity, + workspace: &mut Workspace, + window: &mut Window, + cx: &mut Context, + ) { + workspace.toggle_modal(window, cx, |window, cx| { + Self::new(context_server_id, tools, window, cx) + }); + } + + fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context) { + cx.emit(DismissEvent) + } + + fn render_modal_content( + &self, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let tools_by_source = self.tools.read(cx).tools_by_source(cx); + let server_tools = tools_by_source + .get(&ToolSource::ContextServer { + id: self.context_server_id.0.clone().into(), + }) + .map(|tools| tools.as_slice()) + .unwrap_or(&[]); + + div() + .size_full() + .pb_2() + .child( + v_flex() + .id("modal_content") + .px_2() + .gap_1() + .max_h_128() + .overflow_y_scroll() + .track_scroll(&self.scroll_handle) + .children(server_tools.iter().enumerate().flat_map(|(index, tool)| { + let tool_name = tool.name(); + let is_expanded = self + .expanded_tools + .get(&tool_name) + .copied() + .unwrap_or(false); + + let icon = if is_expanded { + IconName::ChevronUp + } else { + IconName::ChevronDown + }; + + let mut items = vec![ + v_flex() + .child( + h_flex() + .id(SharedString::from(format!("tool-header-{}", index))) + .py_1() + .pl_1() + .pr_2() + .w_full() + .justify_between() + .rounded_sm() + .hover(|s| s.bg(cx.theme().colors().element_hover)) + .child( + Label::new(tool_name.clone()) + .buffer_font(cx) + .size(LabelSize::Small), + ) + .child( + Icon::new(icon) + .size(IconSize::Small) + .color(Color::Muted), + ) + .on_click(cx.listener({ + move |this, _event, _window, _cx| { + let current = this + .expanded_tools + .get(&tool_name) + .copied() + .unwrap_or(false); + this.expanded_tools + .insert(tool_name.clone(), !current); + _cx.notify(); + } + })), + ) + .when(is_expanded, |this| { + this.child( + Label::new(tool.description()).color(Color::Muted).mx_1(), + ) + }) + .into_any_element(), + ]; + + if index < server_tools.len() - 1 { + items.push( + h_flex() + .w_full() + .child(Divider::horizontal().color(DividerColor::BorderVariant)) + .into_any_element(), + ); + } + + items + })), + ) + .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx) + .into_any_element() + } +} + +impl ModalView for ConfigureContextServerToolsModal {} + +impl Focusable for ConfigureContextServerToolsModal { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl EventEmitter for ConfigureContextServerToolsModal {} + +impl Render for ConfigureContextServerToolsModal { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .key_context("ContextServerToolsModal") + .occlude() + .elevation_3(cx) + .w(rems(34.)) + .on_action(cx.listener(Self::cancel)) + .track_focus(&self.focus_handle) + .child( + Modal::new("configure-context-server-tools", None::) + .header( + ModalHeader::new() + .headline(format!("Tools from {}", self.context_server_id.0)) + .show_dismiss_button(true), + ) + .child(self.render_modal_content(window, cx)), + ) + } +} diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 20188df5c4ae38b2ae305daee5b3eecc25319951..7abd9d85fa6395d104a87e6f585a6c8934a84684 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -286,6 +286,15 @@ impl ContextServerStore { self.servers.keys().cloned().collect() } + pub fn all_registry_descriptor_ids(&self, cx: &App) -> Vec { + self.registry + .read(cx) + .context_server_descriptors() + .into_iter() + .map(|(id, _)| ContextServerId(id)) + .collect() + } + pub fn running_servers(&self) -> Vec> { self.servers .values() diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index e2bb2341192cd44708f851fc0e64055ba8a25523..98eb45fd1dc1845284d63952eac684790d73bec4 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -36,6 +36,7 @@ enum DividerDirection { #[derive(Default)] pub enum DividerColor { Border, + BorderFaded, #[default] BorderVariant, } @@ -44,6 +45,7 @@ impl DividerColor { pub fn hsla(self, cx: &mut App) -> Hsla { match self { DividerColor::Border => cx.theme().colors().border, + DividerColor::BorderFaded => cx.theme().colors().border.opacity(0.6), DividerColor::BorderVariant => cx.theme().colors().border_variant, } } From fb60f710e3410d092d0512b939d38571287ed2a4 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Thu, 18 Sep 2025 17:08:14 +0200 Subject: [PATCH 115/721] Make scrollbars auto-hide by default (#38340) With this, scrollbars across the app will now auto-hide unless it is specified that they should follow a specific setting. Optimally, we would just track the user preference by default. However, this is currently not possible. because the setting we would need to read lives in `editor` and we cannot read that from within the `ui` crate. Release Notes: - N/A --- crates/ui/src/components/scrollbar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 6885a11c51647aafbf1e3136060d52105e3664b7..f1d97fa455dded285b10033d721e5028bedbd35a 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -35,11 +35,11 @@ pub mod scrollbars { pub enum ShowScrollbar { /// Show the scrollbar if there's important information or /// follow the system's configured behavior. + #[default] Auto, /// Match the system's configured behavior. System, /// Always show the scrollbar. - #[default] Always, /// Never show the scrollbar. Never, From 0a9023bce09daf889bc323fc14580a9ffec32b04 Mon Sep 17 00:00:00 2001 From: Devdatta Talele <50290838+devdattatalele@users.noreply.github.com> Date: Thu, 18 Sep 2025 20:45:10 +0530 Subject: [PATCH 116/721] ui: Use hoverable tooltips for Badge component to fix tooltip behavior (#38387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #38362 - Privacy tooltip behavior issues in AI Setup onboarding ## Problem The Privacy tooltip in AI Setup onboarding had incorrect behavior: 1. Tooltip remained visible after mouse left the Privacy button 2. Clicking the button didn't toggle tooltip properly 3. Clicking in intersection area between tooltip and button didn't work ## Root Cause Badge component used `tooltip()` instead of `hoverable_tooltip()`, causing: - Immediate tooltip hiding when mouse left triggering element - No support for tooltip content interaction - Poor intersection area click handling ## Solution **Single line change** in `crates/ui/src/components/badge.rs:61`: ```rust // Before: this.tooltip(move |window, cx| tooltip(window, cx)) // After: this.hoverable_tooltip(move |window, cx| tooltip(window, cx)) ``` ## Technical Details - Leverages existing GPUI `hoverable_tooltip()` infrastructure - Enables 500ms grace period before tooltip hiding - Allows hovering over tooltip content without disappearing - Uses proper tooltip bounds detection for click handling - Affects all Badge tooltips system-wide (positive improvement) - Full backward compatibility - no API changes ## Test Plan - [x] Hover over Privacy badge → tooltip appears - [x] Move mouse away → tooltip stays visible for 500ms - [x] Move mouse to tooltip content → tooltip remains visible - [x] Click on tooltip content → properly handled - [x] Move mouse completely away → tooltip hides after delay - [x] Verify no regression in other Badge tooltip usage Release Notes: - N/A --- crates/ui/src/components/badge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/components/badge.rs b/crates/ui/src/components/badge.rs index f36e03291c5915f70e8370c6cc1e037d097622b0..9db6fd616f56769b03d1856cfda3fdeef66e446f 100644 --- a/crates/ui/src/components/badge.rs +++ b/crates/ui/src/components/badge.rs @@ -58,7 +58,7 @@ impl RenderOnce for Badge { .child(Divider::vertical().color(DividerColor::Border)) .child(Label::new(self.label.clone()).size(LabelSize::Small).ml_1()) .when_some(tooltip, |this, tooltip| { - this.tooltip(move |window, cx| tooltip(window, cx)) + this.hoverable_tooltip(move |window, cx| tooltip(window, cx)) }) } } From fcdab160f964c6ddaa2e59a5547467d82bc2c1ad Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Sep 2025 10:47:23 -0600 Subject: [PATCH 117/721] Settings refactor (#38367) Co-Authored-By: Ben K Co-Authored-By: Anthony Co-Authored-By: Mikayla Release Notes: - settings: Major internal changes to settings. The primary user-facing effect is that some settings which did not make sense in project settings files are no-longer read from there. (For example the inline blame settings) --------- Co-authored-by: Ben Kunkle Co-authored-by: Mikayla Maki Co-authored-by: Anthony --- Cargo.lock | 328 ++- Cargo.toml | 2 +- assets/settings/default.json | 83 +- crates/agent/src/agent_profile.rs | 4 +- crates/agent/src/thread.rs | 4 +- crates/agent2/src/agent.rs | 21 +- crates/agent2/src/thread.rs | 7 +- crates/agent2/src/tools/edit_file_tool.rs | 49 +- crates/agent2/src/tools/grep_tool.rs | 15 +- .../agent2/src/tools/list_directory_tool.rs | 14 +- crates/agent2/src/tools/read_file_tool.rs | 13 +- crates/agent_servers/src/claude.rs | 9 +- crates/agent_servers/src/custom.rs | 10 +- crates/agent_settings/src/agent_profile.rs | 97 +- crates/agent_settings/src/agent_settings.rs | 580 +---- crates/agent_ui/src/acp/thread_view.rs | 4 +- crates/agent_ui/src/agent_configuration.rs | 175 +- .../add_llm_provider_modal.rs | 28 +- .../configure_context_server_modal.rs | 21 +- .../src/agent_configuration/tool_picker.rs | 19 +- crates/agent_ui/src/agent_model_selector.rs | 15 +- crates/agent_ui/src/agent_panel.rs | 62 +- crates/agent_ui/src/agent_ui.rs | 4 +- .../src/context_server_configuration.rs | 4 +- crates/agent_ui/src/profile_selector.rs | 20 +- crates/agent_ui/src/text_thread_editor.rs | 22 +- crates/anthropic/Cargo.toml | 1 + crates/anthropic/src/anthropic.rs | 19 + crates/assistant_tools/src/edit_file_tool.rs | 51 +- crates/assistant_tools/src/grep_tool.rs | 15 +- .../src/list_directory_tool.rs | 14 +- crates/assistant_tools/src/read_file_tool.rs | 15 +- crates/audio/Cargo.toml | 1 - crates/audio/src/audio_settings.rs | 61 +- crates/auto_update/Cargo.toml | 1 - crates/auto_update/src/auto_update.rs | 51 +- crates/call/Cargo.toml | 1 - crates/call/src/call_settings.rs | 44 +- crates/client/src/client.rs | 104 +- crates/collab/src/tests/editor_tests.rs | 140 +- crates/collab/src/tests/following_tests.rs | 6 +- crates/collab/src/tests/integration_tests.rs | 67 +- .../remote_editing_collaboration_tests.rs | 41 +- crates/collab_ui/Cargo.toml | 1 - crates/collab_ui/src/collab_panel.rs | 8 +- crates/collab_ui/src/collab_ui.rs | 2 - crates/collab_ui/src/notification_panel.rs | 8 +- crates/collab_ui/src/panel_settings.rs | 120 +- crates/context_server/Cargo.toml | 3 +- crates/context_server/src/context_server.rs | 31 +- .../src/copilot_completion_provider.rs | 31 +- crates/dap/src/debugger_settings.rs | 80 +- crates/debugger_ui/src/debugger_panel.rs | 22 +- .../src/edit_prediction_button.rs | 44 +- crates/editor/src/code_completion_tests.rs | 3 +- crates/editor/src/code_context_menus.rs | 2 +- crates/editor/src/display_map.rs | 22 +- crates/editor/src/editor.rs | 26 +- crates/editor/src/editor_settings.rs | 948 +++---- crates/editor/src/editor_settings_controls.rs | 89 +- crates/editor/src/editor_tests.rs | 175 +- crates/editor/src/element.rs | 54 +- crates/editor/src/hover_links.rs | 20 +- crates/editor/src/hover_popover.rs | 20 +- crates/editor/src/inlay_hint_cache.rs | 317 +-- crates/editor/src/jsx_tag_auto_close.rs | 17 +- crates/eval/src/eval.rs | 5 +- crates/extension_host/Cargo.toml | 1 - .../extension_host/src/extension_settings.rs | 34 +- .../src/extension_version_selector.rs | 9 +- crates/extensions_ui/src/extensions_ui.rs | 16 +- crates/file_finder/src/file_finder.rs | 12 +- .../file_finder/src/file_finder_settings.rs | 74 +- crates/git_hosting_providers/Cargo.toml | 1 - crates/git_hosting_providers/src/settings.rs | 47 +- crates/git_ui/src/blame_ui.rs | 3 +- crates/git_ui/src/branch_picker.rs | 1 - crates/git_ui/src/git_panel.rs | 16 +- crates/git_ui/src/git_panel_settings.rs | 121 +- crates/go_to_line/Cargo.toml | 2 - crates/go_to_line/src/cursor_position.rs | 48 +- crates/google_ai/Cargo.toml | 1 + crates/google_ai/src/google_ai.rs | 11 +- crates/image_viewer/Cargo.toml | 1 - .../image_viewer/src/image_viewer_settings.rs | 42 +- crates/journal/Cargo.toml | 2 +- crates/journal/src/journal.rs | 60 +- crates/language/src/buffer.rs | 19 +- crates/language/src/buffer_tests.rs | 6 +- crates/language/src/language.rs | 2 +- crates/language/src/language_registry.rs | 9 +- crates/language/src/language_settings.rs | 1811 +++++--------- crates/language_model/Cargo.toml | 1 + crates/language_model/src/language_model.rs | 9 +- .../language_models/src/provider/anthropic.rs | 55 +- .../language_models/src/provider/bedrock.rs | 19 +- crates/language_models/src/provider/cloud.rs | 38 +- .../language_models/src/provider/deepseek.rs | 12 +- crates/language_models/src/provider/google.rs | 29 +- .../language_models/src/provider/lmstudio.rs | 12 +- .../language_models/src/provider/mistral.rs | 15 +- crates/language_models/src/provider/ollama.rs | 50 +- .../language_models/src/provider/open_ai.rs | 14 +- .../src/provider/open_ai_compatible.rs | 34 +- .../src/provider/open_router.rs | 54 +- crates/language_models/src/provider/vercel.rs | 14 +- crates/language_models/src/provider/x_ai.rs | 12 +- crates/language_models/src/settings.rs | 460 ++-- crates/languages/src/bash.rs | 6 +- crates/languages/src/c.rs | 6 +- crates/languages/src/lib.rs | 7 +- crates/languages/src/python.rs | 6 +- crates/languages/src/rust.rs | 5 +- crates/markdown/examples/markdown.rs | 5 +- crates/markdown/examples/markdown_as_child.rs | 5 +- crates/multi_buffer/src/multi_buffer.rs | 12 +- crates/ollama/Cargo.toml | 1 + crates/ollama/src/ollama.rs | 24 +- crates/onboarding/Cargo.toml | 3 + crates/onboarding/src/ai_setup_page.rs | 4 +- crates/onboarding/src/base_keymap_picker.rs | 4 +- crates/onboarding/src/basics_page.rs | 30 +- crates/onboarding/src/editing_page.rs | 67 +- crates/open_ai/Cargo.toml | 1 + crates/open_ai/src/open_ai.rs | 11 +- crates/open_router/Cargo.toml | 4 +- crates/open_router/src/open_router.rs | 50 +- crates/outline_panel/Cargo.toml | 1 - crates/outline_panel/src/outline_panel.rs | 24 +- .../src/outline_panel_settings.rs | 166 +- crates/project/src/agent_server_store.rs | 114 +- crates/project/src/context_server_store.rs | 35 +- crates/project/src/debugger/dap_store.rs | 15 +- crates/project/src/lsp_store.rs | 3 +- crates/project/src/project.rs | 214 +- crates/project/src/project_settings.rs | 725 +++--- crates/project/src/project_tests.rs | 20 +- crates/project_panel/src/project_panel.rs | 45 +- .../src/project_panel_settings.rs | 252 +- .../project_panel/src/project_panel_tests.rs | 81 +- crates/recent_projects/Cargo.toml | 1 - crates/recent_projects/src/recent_projects.rs | 9 +- .../recent_projects/src/remote_connections.rs | 116 +- crates/recent_projects/src/remote_servers.rs | 78 +- crates/remote/Cargo.toml | 2 +- crates/remote/src/transport/ssh.rs | 24 +- crates/remote/src/transport/wsl.rs | 9 + crates/repl/Cargo.toml | 1 - crates/repl/src/jupyter_settings.rs | 53 +- crates/repl/src/repl_settings.rs | 47 +- crates/search/src/buffer_search.rs | 12 +- crates/settings/Cargo.toml | 2 + crates/settings/src/base_keymap_setting.rs | 65 +- .../settings/src/editable_setting_control.rs | 13 +- crates/settings/src/settings.rs | 11 +- crates/settings/src/settings_content.rs | 818 ++++++ crates/settings/src/settings_content/agent.rs | 336 +++ .../settings/src/settings_content/editor.rs | 603 +++++ .../settings/src/settings_content/language.rs | 894 +++++++ .../src/settings_content/language_model.rs | 419 ++++ .../settings/src/settings_content/project.rs | 435 ++++ .../settings/src/settings_content/terminal.rs | 318 +++ crates/settings/src/settings_content/theme.rs | 1076 ++++++++ .../src/settings_content/workspace.rs | 440 ++++ crates/settings/src/settings_file.rs | 8 +- crates/settings/src/settings_json.rs | 9 +- crates/settings/src/settings_store.rs | 1673 ++++--------- crates/settings/src/vscode_import.rs | 11 +- crates/settings_ui/Cargo.toml | 42 - crates/settings_ui/LICENSE-GPL | 1 - .../src/appearance_settings_controls.rs | 387 --- crates/settings_ui/src/settings_ui.rs | 1017 -------- crates/terminal/src/terminal_settings.rs | 437 ++-- crates/terminal_view/src/terminal_panel.rs | 27 +- crates/terminal_view/src/terminal_view.rs | 11 +- crates/theme/Cargo.toml | 1 - crates/theme/src/schema.rs | 2225 ++++++----------- crates/theme/src/settings.rs | 794 +++--- crates/theme/src/styles/accents.rs | 6 +- crates/theme/src/styles/players.rs | 6 +- crates/theme/src/theme.rs | 9 +- crates/theme_importer/Cargo.toml | 1 + crates/theme_importer/src/main.rs | 4 +- crates/theme_importer/src/vscode/converter.rs | 8 +- .../theme_selector/src/icon_theme_selector.rs | 4 +- crates/theme_selector/src/theme_selector.rs | 4 +- crates/title_bar/src/title_bar_settings.rs | 95 +- crates/ui/src/components/scrollbar.rs | 11 + crates/ui_macros/src/dynamic_spacing.rs | 12 +- crates/util/src/util.rs | 18 + crates/vim/src/digraph.rs | 5 +- crates/vim/src/normal.rs | 15 +- crates/vim/src/normal/paste.rs | 39 +- crates/vim/src/normal/scroll.rs | 15 +- crates/vim/src/normal/search.rs | 5 +- crates/vim/src/test.rs | 5 +- .../src/test/neovim_backed_test_context.rs | 13 +- crates/vim/src/test/vim_test_context.rs | 10 +- crates/vim/src/vim.rs | 139 +- crates/vim_mode_setting/Cargo.toml | 3 - .../vim_mode_setting/src/vim_mode_setting.rs | 99 +- crates/workspace/src/dock.rs | 25 +- crates/workspace/src/item.rs | 212 +- crates/workspace/src/pane.rs | 13 +- crates/workspace/src/workspace.rs | 47 +- crates/workspace/src/workspace_settings.rs | 458 ++-- crates/worktree/Cargo.toml | 1 - crates/worktree/src/worktree_settings.rs | 136 +- crates/worktree/src/worktree_tests.rs | 39 +- crates/zed/Cargo.toml | 2 - crates/zed/src/main.rs | 1 - crates/zed/src/zed.rs | 68 +- crates/zeta/src/init.rs | 19 +- crates/zeta/src/onboarding_modal.rs | 6 +- crates/zeta_cli/src/headless.rs | 5 +- crates/zlog/Cargo.toml | 1 + crates/zlog/src/filter.rs | 11 +- crates/zlog_settings/Cargo.toml | 4 +- crates/zlog_settings/src/zlog_settings.rs | 45 +- 219 files changed, 11630 insertions(+), 11827 deletions(-) create mode 100644 crates/settings/src/settings_content.rs create mode 100644 crates/settings/src/settings_content/agent.rs create mode 100644 crates/settings/src/settings_content/editor.rs create mode 100644 crates/settings/src/settings_content/language.rs create mode 100644 crates/settings/src/settings_content/language_model.rs create mode 100644 crates/settings/src/settings_content/project.rs create mode 100644 crates/settings/src/settings_content/terminal.rs create mode 100644 crates/settings/src/settings_content/theme.rs create mode 100644 crates/settings/src/settings_content/workspace.rs delete mode 100644 crates/settings_ui/Cargo.toml delete mode 120000 crates/settings_ui/LICENSE-GPL delete mode 100644 crates/settings_ui/src/appearance_settings_controls.rs delete mode 100644 crates/settings_ui/src/settings_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 4966106b79599db9ece65fd9d9e6d794196a46c5..b80e101f868dbe8dd7d6d939a6ab0c94951ed555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,7 +174,7 @@ dependencies = [ "rand 0.9.1", "ref-cast", "rope", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -205,7 +205,7 @@ dependencies = [ "futures 0.3.31", "log", "parking_lot", - "schemars", + "schemars 1.0.1", "serde", "serde_json", ] @@ -257,7 +257,7 @@ dependencies = [ "prompt_store", "reqwest_client", "rust-embed", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -337,7 +337,7 @@ dependencies = [ "gpui", "language_model", "paths", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", @@ -412,7 +412,7 @@ dependencies = [ "release_channel", "rope", "rules_library", - "schemars", + "schemars 1.0.1", "search", "serde", "serde_json", @@ -656,9 +656,10 @@ dependencies = [ "chrono", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "strum 0.27.1", "thiserror 2.0.12", "workspace-hack", @@ -1007,7 +1008,7 @@ dependencies = [ "regex", "reqwest_client", "rust-embed", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -1409,7 +1410,6 @@ dependencies = [ "log", "parking_lot", "rodio", - "schemars", "serde", "settings", "smol", @@ -1442,7 +1442,6 @@ dependencies = [ "log", "paths", "release_channel", - "schemars", "serde", "serde_json", "settings", @@ -2144,7 +2143,7 @@ dependencies = [ "aws-sdk-bedrockruntime", "aws-smithy-types", "futures 0.3.31", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "strum 0.27.1", @@ -2645,7 +2644,6 @@ dependencies = [ "log", "postage", "project", - "schemars", "serde", "settings", "telemetry", @@ -2868,7 +2866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ "heck 0.4.1", - "indexmap", + "indexmap 2.9.0", "log", "proc-macro2", "quote", @@ -3146,7 +3144,7 @@ dependencies = [ "release_channel", "rpc", "rustls-pki-types", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_urlencoded", @@ -3457,7 +3455,6 @@ dependencies = [ "project", "release_channel", "rpc", - "schemars", "serde", "serde_json", "settings", @@ -3479,7 +3476,7 @@ dependencies = [ name = "collections" version = "0.1.0" dependencies = [ - "indexmap", + "indexmap 2.9.0", "rustc-hash 2.1.1", "workspace-hack", ] @@ -3641,9 +3638,10 @@ dependencies = [ "net", "parking_lot", "postage", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "smol", "tempfile", "url", @@ -4441,7 +4439,7 @@ dependencies = [ "parking_lot", "paths", "proto", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -4461,7 +4459,7 @@ name = "dap-types" version = "0.0.1" source = "git+https://github.com/zed-industries/dap-types?rev=1b461b310481d01e02b2603c16d7144b926339f8#1b461b310481d01e02b2603c16d7144b926339f8" dependencies = [ - "schemars", + "schemars 1.0.1", "serde", "serde_json", ] @@ -4491,6 +4489,41 @@ dependencies = [ "workspace-hack", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.101", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.101", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -4632,7 +4665,7 @@ dependencies = [ "pretty_assertions", "project", "rpc", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", @@ -4673,7 +4706,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "workspace-hack", @@ -5207,7 +5240,7 @@ dependencies = [ "regex", "release_channel", "rpc", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -5714,7 +5747,6 @@ dependencies = [ "release_channel", "remote", "reqwest_client", - "schemars", "semantic_version", "serde", "serde_json", @@ -5911,7 +5943,7 @@ dependencies = [ "picker", "pretty_assertions", "project", - "schemars", + "schemars 1.0.1", "search", "serde", "serde_json", @@ -6770,7 +6802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 2.9.0", "stable_deref_trait", ] @@ -6793,7 +6825,7 @@ dependencies = [ "rand 0.9.1", "regex", "rope", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "smol", @@ -6835,7 +6867,6 @@ dependencies = [ "indoc", "pretty_assertions", "regex", - "schemars", "serde", "serde_json", "settings", @@ -6880,7 +6911,7 @@ dependencies = [ "postage", "pretty_assertions", "project", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -7707,7 +7738,6 @@ dependencies = [ name = "go_to_line" version = "0.1.0" dependencies = [ - "anyhow", "editor", "gpui", "indoc", @@ -7715,7 +7745,6 @@ dependencies = [ "menu", "project", "rope", - "schemars", "serde", "serde_json", "settings", @@ -7747,9 +7776,10 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "strum 0.27.1", "workspace-hack", ] @@ -7848,7 +7878,7 @@ dependencies = [ "reqwest_client", "resvg", "scap", - "schemars", + "schemars 1.0.1", "seahash", "semantic_version", "serde", @@ -7934,7 +7964,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -7953,7 +7983,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -8652,6 +8682,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -8734,7 +8770,6 @@ dependencies = [ "language", "log", "project", - "schemars", "serde", "settings", "theme", @@ -8765,6 +8800,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -9129,7 +9175,7 @@ dependencies = [ "hashbrown 0.15.3", "hex", "ignore", - "indexmap", + "indexmap 2.9.0", "interim", "itertools 0.14.0", "jj-lib-proc-macros", @@ -9226,10 +9272,10 @@ dependencies = [ "editor", "gpui", "log", - "schemars", "serde", "settings", "shellexpand 2.1.2", + "util", "workspace", "workspace-hack", ] @@ -9460,7 +9506,7 @@ dependencies = [ "rand 0.9.1", "regex", "rpc", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -9534,9 +9580,10 @@ dependencies = [ "open_router", "parking_lot", "proto", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "smol", "telemetry_events", "thiserror 2.0.12", @@ -9583,7 +9630,7 @@ dependencies = [ "partial-json-fixer", "project", "release_channel", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -9699,7 +9746,7 @@ dependencies = [ "regex", "rope", "rust-embed", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", @@ -10113,7 +10160,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "workspace-hack", @@ -10219,7 +10266,7 @@ dependencies = [ "parking_lot", "postage", "release_channel", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "smol", @@ -10757,7 +10804,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "strum 0.27.1", @@ -10844,7 +10891,7 @@ dependencies = [ "half", "hashbrown 0.15.3", "hexf-parse", - "indexmap", + "indexmap 2.9.0", "log", "num-traits", "once_cell", @@ -11503,7 +11550,7 @@ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", "hashbrown 0.15.3", - "indexmap", + "indexmap 2.9.0", "memchr", ] @@ -11514,9 +11561,10 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "workspace-hack", ] @@ -11542,7 +11590,7 @@ dependencies = [ "notifications", "picker", "project", - "schemars", + "schemars 1.0.1", "serde", "settings", "telemetry", @@ -11622,9 +11670,10 @@ dependencies = [ "futures 0.3.31", "http_client", "log", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "strum 0.27.1", "workspace-hack", ] @@ -11636,12 +11685,12 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars", + "schemars 1.0.1", "serde", "serde_json", + "settings", "strum 0.27.1", "thiserror 2.0.12", - "util", "workspace-hack", ] @@ -11815,7 +11864,6 @@ dependencies = [ "outline", "pretty_assertions", "project", - "schemars", "search", "serde", "serde_json", @@ -12516,7 +12564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.9.0", ] [[package]] @@ -12590,7 +12638,7 @@ dependencies = [ "env_logger 0.11.8", "gpui", "menu", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "ui", @@ -12697,7 +12745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" dependencies = [ "base64 0.22.1", - "indexmap", + "indexmap 2.9.0", "quick-xml 0.32.0", "serde", "time", @@ -13027,7 +13075,7 @@ dependencies = [ "gpui", "http_client", "image", - "indexmap", + "indexmap 2.9.0", "itertools 0.14.0", "language", "log", @@ -13045,7 +13093,7 @@ dependencies = [ "release_channel", "remote", "rpc", - "schemars", + "schemars 1.0.1", "semver", "serde", "serde_json", @@ -13087,12 +13135,12 @@ dependencies = [ "git", "git_ui", "gpui", - "indexmap", + "indexmap 2.9.0", "language", "menu", "pretty_assertions", "project", - "schemars", + "schemars 1.0.1", "search", "serde", "serde_json", @@ -13787,7 +13835,6 @@ dependencies = [ "project", "release_channel", "remote", - "schemars", "serde", "serde_json", "settings", @@ -13961,9 +14008,9 @@ dependencies = [ "prost 0.9.0", "release_channel", "rpc", - "schemars", "serde", "serde_json", + "settings", "shlex", "smol", "tempfile", @@ -14082,7 +14129,6 @@ dependencies = [ "picker", "project", "runtimelib", - "schemars", "serde", "serde_json", "settings", @@ -14899,13 +14945,25 @@ dependencies = [ "anyhow", "clap", "env_logger 0.11.8", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "theme", "workspace-hack", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.0.1" @@ -14913,7 +14971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 2.9.0", "ref-cast", "schemars_derive", "serde", @@ -15124,7 +15182,7 @@ dependencies = [ "language", "menu", "project", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -15274,7 +15332,7 @@ version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56177480b00303e689183f110b4e727bb4211d692c62d4fcd16d02be93077d40" dependencies = [ - "indexmap", + "indexmap 2.9.0", "itoa", "memchr", "ryu", @@ -15287,7 +15345,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" dependencies = [ - "indexmap", + "indexmap 2.9.0", "itoa", "memchr", "ryu", @@ -15336,6 +15394,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "schemars 0.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "serial2" version = "0.2.29" @@ -15376,11 +15465,13 @@ dependencies = [ "pretty_assertions", "release_channel", "rust-embed", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", "serde_path_to_error", + "serde_repr", + "serde_with", "settings_ui_macros", "smallvec", "tree-sitter", @@ -15412,27 +15503,6 @@ dependencies = [ "zed_actions", ] -[[package]] -name = "settings_ui" -version = "0.1.0" -dependencies = [ - "anyhow", - "command_palette_hooks", - "debugger_ui", - "editor", - "feature_flags", - "gpui", - "menu", - "serde", - "serde_json", - "settings", - "smallvec", - "theme", - "ui", - "workspace", - "workspace-hack", -] - [[package]] name = "settings_ui_macros" version = "0.1.0" @@ -15730,7 +15800,7 @@ dependencies = [ "indoc", "parking_lot", "paths", - "schemars", + "schemars 1.0.1", "serde", "serde_json_lenient", "snippet", @@ -15891,7 +15961,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.3", "hashlink 0.10.0", - "indexmap", + "indexmap 2.9.0", "log", "memchr", "once_cell", @@ -16810,7 +16880,7 @@ dependencies = [ "menu", "picker", "project", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "settings", @@ -16890,7 +16960,7 @@ dependencies = [ "parking_lot", "pretty_assertions", "proto", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", @@ -16996,7 +17066,7 @@ dependencies = [ "rand 0.9.1", "regex", "release_channel", - "schemars", + "schemars 1.0.1", "serde", "settings", "smol", @@ -17042,7 +17112,7 @@ dependencies = [ "project", "rand 0.9.1", "regex", - "schemars", + "schemars 1.0.1", "search", "serde", "serde_json", @@ -17092,17 +17162,16 @@ dependencies = [ "fs", "futures 0.3.31", "gpui", - "indexmap", + "indexmap 2.9.0", "inventory", "log", "palette", "parking_lot", "refineable", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", - "serde_repr", "settings", "strum 0.27.1", "thiserror 2.0.12", @@ -17129,8 +17198,9 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "collections", "gpui", - "indexmap", + "indexmap 2.9.0", "log", "palette", "serde", @@ -17384,7 +17454,7 @@ dependencies = [ "project", "remote", "rpc", - "schemars", + "schemars 1.0.1", "serde", "settings", "smallvec", @@ -17583,7 +17653,7 @@ version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -18194,7 +18264,7 @@ dependencies = [ "icons", "itertools 0.14.0", "menu", - "schemars", + "schemars 1.0.1", "serde", "settings", "smallvec", @@ -18469,7 +18539,7 @@ dependencies = [ "rand 0.9.1", "regex", "rust-embed", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "serde_json_lenient", @@ -18579,7 +18649,7 @@ name = "vercel" version = "0.1.0" dependencies = [ "anyhow", - "schemars", + "schemars 1.0.1", "serde", "strum 0.27.1", "workspace-hack", @@ -18627,7 +18697,7 @@ dependencies = [ "project_panel", "regex", "release_channel", - "schemars", + "schemars 1.0.1", "search", "serde", "serde_json", @@ -18648,10 +18718,7 @@ dependencies = [ name = "vim_mode_setting" version = "0.1.0" dependencies = [ - "anyhow", "gpui", - "schemars", - "serde", "settings", "workspace-hack", ] @@ -18886,7 +18953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.9.0", "serde", "serde_derive", "serde_json", @@ -18904,7 +18971,7 @@ dependencies = [ "anyhow", "auditable-serde", "flate2", - "indexmap", + "indexmap 2.9.0", "serde", "serde_derive", "serde_json", @@ -18934,7 +19001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.9.0", - "indexmap", + "indexmap 2.9.0", "semver", ] @@ -18946,7 +19013,7 @@ checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" dependencies = [ "bitflags 2.9.0", "hashbrown 0.15.3", - "indexmap", + "indexmap 2.9.0", "semver", "serde", ] @@ -18959,7 +19026,7 @@ checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ "bitflags 2.9.0", "hashbrown 0.15.3", - "indexmap", + "indexmap 2.9.0", "semver", ] @@ -18988,7 +19055,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap", + "indexmap 2.9.0", "libc", "log", "mach2 0.4.2", @@ -19112,7 +19179,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli", - "indexmap", + "indexmap 2.9.0", "log", "object", "postcard", @@ -19237,7 +19304,7 @@ checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap", + "indexmap 2.9.0", "wit-parser 0.221.3", ] @@ -20348,7 +20415,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap", + "indexmap 2.9.0", "wasm-metadata 0.201.0", "wit-bindgen-core 0.22.0", "wit-component 0.201.0", @@ -20362,7 +20429,7 @@ checksum = "9d0809dc5ba19e2e98661bf32fc0addc5a3ca5bf3a6a7083aa6ba484085ff3ce" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap", + "indexmap 2.9.0", "prettyplease", "syn 2.0.101", "wasm-metadata 0.227.1", @@ -20407,7 +20474,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.9.0", - "indexmap", + "indexmap 2.9.0", "log", "serde", "serde_derive", @@ -20426,7 +20493,7 @@ checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" dependencies = [ "anyhow", "bitflags 2.9.0", - "indexmap", + "indexmap 2.9.0", "log", "serde", "serde_derive", @@ -20445,7 +20512,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.9.0", "log", "semver", "serde", @@ -20463,7 +20530,7 @@ checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.9.0", "log", "semver", "serde", @@ -20481,7 +20548,7 @@ checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.9.0", "log", "semver", "serde", @@ -20531,7 +20598,7 @@ dependencies = [ "pretty_assertions", "project", "remote", - "schemars", + "schemars 1.0.1", "serde", "serde_json", "session", @@ -20628,7 +20695,7 @@ dependencies = [ "hyper 0.14.32", "hyper-rustls 0.27.5", "idna", - "indexmap", + "indexmap 2.9.0", "inout", "itertools 0.12.1", "itertools 0.13.0", @@ -20773,7 +20840,6 @@ dependencies = [ "pretty_assertions", "rand 0.9.1", "rpc", - "schemars", "serde", "serde_json", "settings", @@ -20851,7 +20917,7 @@ name = "x_ai" version = "0.1.0" dependencies = [ "anyhow", - "schemars", + "schemars 1.0.1", "serde", "strum 0.27.1", "workspace-hack", @@ -21220,7 +21286,6 @@ dependencies = [ "session", "settings", "settings_profile_selector", - "settings_ui", "shellexpand 2.1.2", "smol", "snippet_provider", @@ -21271,7 +21336,7 @@ name = "zed_actions" version = "0.1.0" dependencies = [ "gpui", - "schemars", + "schemars 1.0.1", "serde", "uuid", "workspace-hack", @@ -21605,7 +21670,7 @@ dependencies = [ "crc32fast", "crossbeam-utils", "displaydoc", - "indexmap", + "indexmap 2.9.0", "num_enum", "thiserror 1.0.69", ] @@ -21622,6 +21687,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "collections", "log", "tempfile", "workspace-hack", @@ -21631,10 +21697,8 @@ dependencies = [ name = "zlog_settings" version = "0.1.0" dependencies = [ - "anyhow", + "collections", "gpui", - "schemars", - "serde", "settings", "workspace-hack", "zlog", diff --git a/Cargo.toml b/Cargo.toml index 96a1fc588f03162ba10ed60c52353a1a710b3783..08a9b41315c36d7facd7b9d1751b949a2577395c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,7 +150,6 @@ members = [ "crates/session", "crates/settings", "crates/settings_profile_selector", - "crates/settings_ui", "crates/settings_ui_macros", "crates/snippet", "crates/snippet_provider", @@ -630,6 +629,7 @@ serde_json_lenient = { version = "0.2", features = [ serde_path_to_error = "0.1.17" serde_repr = "0.1" serde_urlencoded = "0.7" +serde_with = "3.4.0" sha2 = "0.10" shellexpand = "2.1.0" shlex = "1.3.0" diff --git a/assets/settings/default.json b/assets/settings/default.json index 6b521f9bcf1adc4fc2ebd85170f3aa187ca5b734..091231521470ebec50cf1351a76063e9205a3d24 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -311,7 +311,7 @@ // bracket, brace, single or double quote characters. // For example, when you select text and type (, Zed will surround the text with (). "use_auto_surround": true, - /// Whether indentation should be adjusted based on the context whilst typing. + // Whether indentation should be adjusted based on the context whilst typing. "auto_indent": true, // Whether indentation of pasted content should be adjusted based on the context. "auto_indent_on_paste": true, @@ -408,6 +408,21 @@ // Whether to show the menus in the titlebar. "show_menus": false }, + "audio": { + // Opt into the new audio system. + "experimental.rodio_audio": false, + // Requires 'rodio_audio: true' + // + // Use the new audio systems automatic gain control for your microphone. + // This affects how loud you sound to others. + "experimental.control_input_volume": false, + // Requires 'rodio_audio: true' + // + // Use the new audio systems automatic gain control on everyone in the + // call. This makes call members who are too quite louder and those who are + // too loud quieter. This only affects how things sound for you. + "experimental.control_output_volume": false + }, // Scrollbar related settings "scrollbar": { // When to show the scrollbar in the editor. @@ -588,6 +603,7 @@ // Toggle certain types of hints on and off, all switched on by default. "show_type_hints": true, "show_parameter_hints": true, + "show_value_hints": true, // Corresponds to null/None LSP hint type value. "show_other_hints": true, // Whether to show a background for inlay hints. @@ -796,7 +812,7 @@ "agent": { // Whether the agent is enabled. "enabled": true, - /// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'. + // What completion mode to start new threads in, if available. Can be 'normal' or 'burn'. "preferred_completion_mode": "normal", // Whether to show the agent panel button in the status bar. "button": true, @@ -806,6 +822,8 @@ "default_width": 640, // Default height when the agent panel is docked to the bottom. "default_height": 320, + // The view to use by default (thread, or text_thread) + "default_view": "thread", // The default model to use when creating new threads. "default_model": { // The provider to use. @@ -907,14 +925,18 @@ // Default: false "play_sound_when_agent_done": false, - /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff. - /// - /// Default: true + // Whether to have edit cards in the agent panel expanded, showing a preview of the full diff. + // + // Default: true "expand_edit_card": true, - /// Whether to have terminal cards in the agent panel expanded, showing the whole command output. - /// - /// Default: true + // Whether to have terminal cards in the agent panel expanded, showing the whole command output. + // + // Default: true "expand_terminal_card": true, + // Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel. + // + // Default: false + "use_modifier_to_send": false, // Minimum number of lines to display in the agent message editor. // // Default: 4 @@ -931,6 +953,7 @@ // // This is typically customized on a per-language basis. "language_servers": ["..."], + // When to automatically save edited buffers. This setting can // take four values. // @@ -1262,7 +1285,13 @@ // }, // Whether edit predictions are enabled when editing text threads. // This setting has no effect if globally disabled. - "enabled_in_text_threads": true + "enabled_in_text_threads": true, + + "copilot": { + "enterprise_uri": null, + "proxy": null, + "proxy_no_verify": null + } }, // Settings specific to journaling "journal": { @@ -1765,6 +1794,7 @@ "anthropic": { "api_url": "https://api.anthropic.com" }, + "bedrock": {}, "google": { "api_url": "https://generativelanguage.googleapis.com" }, @@ -1786,14 +1816,30 @@ }, "mistral": { "api_url": "https://api.mistral.ai/v1" - } + }, + "vercel": { + "api_url": "https://api.v0.dev/v1" + }, + "x_ai": { + "api_url": "https://api.x.ai/v1" + }, + "zed.dev": {} + }, + "session": { + // Whether or not to restore unsaved buffers on restart. + // + // If this is true, user won't be prompted whether to save/discard + // dirty files when closing the application. + // + // Default: true + "restore_unsaved_buffers": true }, // Zed's Prettier integration settings. // Allows to enable/disable formatting with Prettier // and configure default Prettier, used when no project-level Prettier installation is found. "prettier": { // // Whether to consider prettier formatter or not when attempting to format a file. - // "allowed": false, + "allowed": false // // // Use regular Prettier json configuration. // // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if @@ -1826,6 +1872,10 @@ // } // } }, + // DAP Specific settings. + "dap": { + // Specify the DAP name as a key here. + }, // Common language server settings. "global_lsp_settings": { // Whether to show the LSP servers button in the status bar. @@ -1833,7 +1883,8 @@ }, // Jupyter settings "jupyter": { - "enabled": true + "enabled": true, + "kernel_selections": {} // Specify the language name as the key and the kernel name as the value. // "kernel_selections": { // "python": "conda-base" @@ -1964,5 +2015,11 @@ // } // } // } - "profiles": [] + "profiles": [], + + // A map of log scopes to the desired log level. + // Useful for filtering out noisy logs or enabling more verbose logging. + // + // Example: {"log": {"client": "warn"}} + "log": {} } diff --git a/crates/agent/src/agent_profile.rs b/crates/agent/src/agent_profile.rs index c9e73372f60686cf330531926f4129e9c9b25db8..40ba2f07db7ad425a5d0e9befe91499eb746b74e 100644 --- a/crates/agent/src/agent_profile.rs +++ b/crates/agent/src/agent_profile.rs @@ -49,10 +49,10 @@ impl AgentProfile { .unwrap_or_default(), }; - update_settings_file::(fs, cx, { + update_settings_file(fs, cx, { let id = id.clone(); move |settings, _cx| { - settings.create_profile(id, profile_settings).log_err(); + profile_settings.save_to_settings(id, settings).log_err(); } }); diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 7b70fde56ab1e7acb6705aeace82f142dc28a9f3..8b9d489ccf472ca16435934e48a12b70dc783c40 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -3272,7 +3272,7 @@ mod tests { // Test-specific constants const TEST_RATE_LIMIT_RETRY_SECS: u64 = 30; - use agent_settings::{AgentProfileId, AgentSettings, LanguageModelParameters}; + use agent_settings::{AgentProfileId, AgentSettings}; use assistant_tool::ToolRegistry; use assistant_tools; use futures::StreamExt; @@ -3289,7 +3289,7 @@ mod tests { use project::{FakeFs, Project}; use prompt_store::PromptBuilder; use serde_json::json; - use settings::{Settings, SettingsStore}; + use settings::{LanguageModelParameters, Settings, SettingsStore}; use std::sync::Arc; use std::time::Duration; use theme::ThemeSettings; diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index 44d4075b89306b6c6dd81d6de503888c036e6fbf..86fb50242c64917248df5c620782af066e639b54 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -6,7 +6,6 @@ use crate::{HistoryStore, TerminalHandle, ThreadEnvironment, TitleUpdated, Token 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}; use collections::{HashSet, IndexMap}; use fs::Fs; @@ -21,7 +20,7 @@ use project::{Project, ProjectItem, ProjectPath, Worktree}; use prompt_store::{ ProjectContext, PromptId, PromptStore, RulesFileContext, UserRulesContext, WorktreeContext, }; -use settings::update_settings_file; +use settings::{LanguageModelSelection, update_settings_file}; use std::any::Any; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -873,13 +872,17 @@ impl AgentModelSelector for NativeAgentConnection { thread.set_model(model.clone(), cx); }); - update_settings_file::( - self.0.read(cx).fs.clone(), - cx, - move |settings, _cx| { - settings.set_model(model); - }, - ); + update_settings_file(self.0.read(cx).fs.clone(), cx, move |settings, _cx| { + let provider = model.provider_id().0.to_string(); + let model = model.id().0.to_string(); + settings + .agent + .get_or_insert_default() + .set_model(LanguageModelSelection { + provider: provider.into(), + model, + }); + }); Task::ready(Ok(())) } diff --git a/crates/agent2/src/thread.rs b/crates/agent2/src/thread.rs index b19d34adafe4a7b6567be9a1864b88ea2504bf12..18f993cbe33ca8bffc2235906baf76c627da0030 100644 --- a/crates/agent2/src/thread.rs +++ b/crates/agent2/src/thread.rs @@ -2477,8 +2477,11 @@ impl ToolCallEventStream { "always_allow" => { if let Some(fs) = fs.clone() { cx.update(|cx| { - update_settings_file::(fs, cx, |settings, _| { - settings.set_always_allow_tool_actions(true); + update_settings_file(fs, cx, |settings, _| { + settings + .agent + .get_or_insert_default() + .set_always_allow_tool_actions(true); }); })?; } diff --git a/crates/agent2/src/tools/edit_file_tool.rs b/crates/agent2/src/tools/edit_file_tool.rs index 1e725b8f59d1219a0761334c5364940ee0e8bf6a..81f340b0b5c83648b1ec92210986b475b71c5bcf 100644 --- a/crates/agent2/src/tools/edit_file_tool.rs +++ b/crates/agent2/src/tools/edit_file_tool.rs @@ -791,14 +791,11 @@ mod tests { // First, test with format_on_save enabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.format_on_save = Some(FormatOnSave::On); - settings.defaults.formatter = - Some(language::language_settings::SelectedFormatter::Auto); - }, - ); + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On); + settings.project.all_languages.defaults.formatter = + Some(language::language_settings::SelectedFormatter::Auto); + }); }); }); @@ -853,12 +850,10 @@ mod tests { // Next, test with format_on_save disabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.format_on_save = Some(FormatOnSave::Off); - }, - ); + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.format_on_save = + Some(FormatOnSave::Off); + }); }); }); @@ -935,12 +930,13 @@ mod tests { // First, test with remove_trailing_whitespace_on_save enabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.remove_trailing_whitespace_on_save = Some(true); - }, - ); + store.update_user_settings(cx, |settings| { + settings + .project + .all_languages + .defaults + .remove_trailing_whitespace_on_save = Some(true); + }); }); }); @@ -991,12 +987,13 @@ mod tests { // Next, test with remove_trailing_whitespace_on_save disabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.remove_trailing_whitespace_on_save = Some(false); - }, - ); + store.update_user_settings(cx, |settings| { + settings + .project + .all_languages + .defaults + .remove_trailing_whitespace_on_save = Some(false); + }); }); }); diff --git a/crates/agent2/src/tools/grep_tool.rs b/crates/agent2/src/tools/grep_tool.rs index 2bcfbd711bd7507be235481197e16cf76b391b6b..a1cd088c858683429103670c604ed3e08d179483 100644 --- a/crates/agent2/src/tools/grep_tool.rs +++ b/crates/agent2/src/tools/grep_tool.rs @@ -308,7 +308,7 @@ mod tests { use super::*; use gpui::{TestAppContext, UpdateGlobal}; use language::{Language, LanguageConfig, LanguageMatcher}; - use project::{FakeFs, Project, WorktreeSettings}; + use project::{FakeFs, Project}; use serde_json::json; use settings::SettingsStore; use unindent::Unindent; @@ -827,15 +827,14 @@ mod tests { cx.update(|cx| { use gpui::UpdateGlobal; - use project::WorktreeSettings; use settings::SettingsStore; SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.private_files = Some(vec![ + settings.project.worktree.private_files = Some(vec![ "**/.mysecrets".to_string(), "**/*.privatekey".to_string(), "**/*.mysensitive".to_string(), @@ -1062,10 +1061,10 @@ mod tests { // Set global settings cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); }); }); }); diff --git a/crates/agent2/src/tools/list_directory_tool.rs b/crates/agent2/src/tools/list_directory_tool.rs index 0fbe23fe205e6a9bd5a77e737460c17b997f9175..41198269d69c17e028cca250069c2e1000ac8dfe 100644 --- a/crates/agent2/src/tools/list_directory_tool.rs +++ b/crates/agent2/src/tools/list_directory_tool.rs @@ -214,7 +214,7 @@ mod tests { use super::*; use gpui::{TestAppContext, UpdateGlobal}; use indoc::indoc; - use project::{FakeFs, Project, WorktreeSettings}; + use project::{FakeFs, Project}; use serde_json::json; use settings::SettingsStore; use util::path; @@ -421,13 +421,13 @@ mod tests { // Configure settings explicitly cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.secretdir".to_string(), "**/.mymetadata".to_string(), "**/.hidden_subdir".to_string(), ]); - settings.private_files = Some(vec![ + settings.project.worktree.private_files = Some(vec![ "**/.mysecrets".to_string(), "**/*.privatekey".to_string(), "**/*.mysensitive".to_string(), @@ -565,10 +565,10 @@ mod tests { // Set global settings cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); }); }); }); diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index 7be83d8bef174bf9b2799d67eb6240fcae4e5bb6..3a9ddb88fe1a44677e43248184902c12724f6936 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -586,15 +586,14 @@ mod test { cx.update(|cx| { use gpui::UpdateGlobal; - use project::WorktreeSettings; use settings::SettingsStore; SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.private_files = Some(vec![ + settings.project.worktree.private_files = Some(vec![ "**/.mysecrets".to_string(), "**/*.privatekey".to_string(), "**/*.mysensitive".to_string(), @@ -802,10 +801,10 @@ mod test { // Set global settings cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); }); }); }); diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index dafb7ea5b706af8e52562681d634dadf07ae488f..4646b2e8259fa2cd63c0daa67b47f66b5e78af05 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -45,8 +45,13 @@ impl AgentServer for ClaudeCode { } fn set_default_mode(&self, mode_id: Option, fs: Arc, cx: &mut App) { - update_settings_file::(fs, cx, |settings, _| { - settings.claude.get_or_insert_default().default_mode = mode_id.map(|m| m.to_string()) + update_settings_file(fs, cx, |settings, _| { + settings + .agent_servers + .get_or_insert_default() + .claude + .get_or_insert_default() + .default_mode = mode_id.map(|m| m.to_string()) }); } diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index 7c8924a3dd65e9f963b418aa6872b1bc14886040..cb9a6dba3c6376fa5030c21523c86853c9b6d761 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -49,8 +49,14 @@ impl crate::AgentServer for CustomAgentServer { fn set_default_mode(&self, mode_id: Option, fs: Arc, cx: &mut App) { let name = self.name(); - update_settings_file::(fs, cx, move |settings, _| { - settings.custom.get_mut(&name).unwrap().default_mode = mode_id.map(|m| m.to_string()) + update_settings_file(fs, cx, move |settings, _| { + settings + .agent_servers + .get_or_insert_default() + .custom + .get_mut(&name) + .unwrap() + .default_mode = mode_id.map(|m| m.to_string()) }); } diff --git a/crates/agent_settings/src/agent_profile.rs b/crates/agent_settings/src/agent_profile.rs index 42a273e2dcfccfb839e6e7d97efb42dcd7b0bba9..999ddc8083a1a4b4c271ea9bde4c1e45307e9542 100644 --- a/crates/agent_settings/src/agent_profile.rs +++ b/crates/agent_settings/src/agent_profile.rs @@ -1,15 +1,17 @@ use std::sync::Arc; +use anyhow::{Result, bail}; use collections::IndexMap; use convert_case::{Case, Casing as _}; use fs::Fs; use gpui::{App, SharedString}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings as _, update_settings_file}; +use settings::{ + AgentProfileContent, ContextServerPresetContent, Settings as _, SettingsContent, + update_settings_file, +}; use util::ResultExt as _; -use crate::AgentSettings; +use crate::{AgentProfileId, AgentSettings}; pub mod builtin_profiles { use super::AgentProfileId; @@ -23,27 +25,6 @@ pub mod builtin_profiles { } } -#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)] -pub struct AgentProfileId(pub Arc); - -impl AgentProfileId { - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl std::fmt::Display for AgentProfileId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Default for AgentProfileId { - fn default() -> Self { - Self("write".into()) - } -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct AgentProfile { id: AgentProfileId, @@ -87,10 +68,10 @@ impl AgentProfile { .unwrap_or_default(), }; - update_settings_file::(fs, cx, { + update_settings_file(fs, cx, { let id = id.clone(); move |settings, _cx| { - settings.create_profile(id, profile_settings).log_err(); + profile_settings.save_to_settings(id, settings).log_err(); } }); @@ -129,9 +110,71 @@ impl AgentProfileSettings { .get(server_id) .is_some_and(|preset| preset.tools.get(tool_name) == Some(&true)) } + + pub fn save_to_settings( + &self, + profile_id: AgentProfileId, + content: &mut SettingsContent, + ) -> Result<()> { + let profiles = content + .agent + .get_or_insert_default() + .profiles + .get_or_insert_default(); + if profiles.contains_key(&profile_id.0) { + bail!("profile with ID '{profile_id}' already exists"); + } + + profiles.insert( + profile_id.0, + AgentProfileContent { + name: self.name.clone().into(), + tools: self.tools.clone(), + enable_all_context_servers: Some(self.enable_all_context_servers), + context_servers: self + .context_servers + .clone() + .into_iter() + .map(|(server_id, preset)| { + ( + server_id, + ContextServerPresetContent { + tools: preset.tools, + }, + ) + }) + .collect(), + }, + ); + + Ok(()) + } +} + +impl From for AgentProfileSettings { + fn from(content: AgentProfileContent) -> Self { + Self { + name: content.name.into(), + tools: content.tools, + enable_all_context_servers: content.enable_all_context_servers.unwrap_or_default(), + context_servers: content + .context_servers + .into_iter() + .map(|(server_id, preset)| (server_id, preset.into())) + .collect(), + } + } } #[derive(Debug, Clone, Default)] pub struct ContextServerPreset { pub tools: IndexMap, bool>, } + +impl From for ContextServerPreset { + fn from(content: settings::ContextServerPresetContent) -> Self { + Self { + tools: content.tools, + } + } +} diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 71516b568c2db65d8266d4233d6f7d2256379e1e..176e8b1a1aa85214f79be1b8ec2696ab387434c1 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -2,14 +2,16 @@ mod agent_profile; use std::sync::Arc; -use anyhow::{Result, bail}; use collections::IndexMap; -use gpui::{App, Pixels, SharedString}; +use gpui::{App, Pixels, px}; use language_model::LanguageModel; -use schemars::{JsonSchema, json_schema}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; -use std::borrow::Cow; +use settings::{ + DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection, + NotifyWhenAgentWaiting, Settings, SettingsContent, +}; +use util::MergeFrom; pub use crate::agent_profile::*; @@ -22,37 +24,11 @@ pub fn init(cx: &mut App) { AgentSettings::register(cx); } -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AgentDockPosition { - Left, - #[default] - Right, - Bottom, -} - -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DefaultView { - #[default] - Thread, - TextThread, -} - -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum NotifyWhenAgentWaiting { - #[default] - PrimaryScreen, - AllScreens, - Never, -} - -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] pub struct AgentSettings { pub enabled: bool, pub button: bool, - pub dock: AgentDockPosition, + pub dock: DockPosition, pub default_width: Pixels, pub default_height: Pixels, pub default_model: Option, @@ -60,9 +36,8 @@ pub struct AgentSettings { pub commit_message_model: Option, pub thread_summary_model: Option, pub inline_alternatives: Vec, - pub using_outdated_settings_version: bool, pub default_profile: AgentProfileId, - pub default_view: DefaultView, + pub default_view: DefaultAgentView, pub profiles: IndexMap, pub always_allow_tool_actions: bool, pub notify_when_agent_waiting: NotifyWhenAgentWaiting, @@ -81,11 +56,20 @@ pub struct AgentSettings { impl AgentSettings { pub fn temperature_for_model(model: &Arc, cx: &App) -> Option { let settings = Self::get_global(cx); - settings - .model_parameters - .iter() - .rfind(|setting| setting.matches(model)) - .and_then(|m| m.temperature) + for setting in settings.model_parameters.iter().rev() { + if let Some(provider) = &setting.provider + && provider.0 != model.provider_id().0 + { + continue; + } + if let Some(setting_model) = &setting.model + && *setting_model != model.id().0 + { + continue; + } + return setting.temperature; + } + return None; } pub fn set_inline_assistant_model(&mut self, provider: String, model: String) { @@ -114,223 +98,6 @@ impl AgentSettings { } } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct LanguageModelParameters { - pub provider: Option, - pub model: Option, - pub temperature: Option, -} - -impl LanguageModelParameters { - pub fn matches(&self, model: &Arc) -> bool { - if let Some(provider) = &self.provider - && provider.0 != model.provider_id().0 - { - return false; - } - if let Some(setting_model) = &self.model - && *setting_model != model.id().0 - { - return false; - } - true - } -} - -impl AgentSettingsContent { - pub fn set_dock(&mut self, dock: AgentDockPosition) { - self.dock = Some(dock); - } - - pub fn set_model(&mut self, language_model: Arc) { - let model = language_model.id().0.to_string(); - let provider = language_model.provider_id().0.to_string(); - - self.default_model = Some(LanguageModelSelection { - provider: provider.into(), - model, - }); - } - - pub fn set_inline_assistant_model(&mut self, provider: String, model: String) { - self.inline_assistant_model = Some(LanguageModelSelection { - provider: provider.into(), - model, - }); - } - - pub fn set_commit_message_model(&mut self, provider: String, model: String) { - self.commit_message_model = Some(LanguageModelSelection { - provider: provider.into(), - model, - }); - } - - pub fn set_thread_summary_model(&mut self, provider: String, model: String) { - self.thread_summary_model = Some(LanguageModelSelection { - provider: provider.into(), - model, - }); - } - - pub fn set_always_allow_tool_actions(&mut self, allow: bool) { - self.always_allow_tool_actions = Some(allow); - } - - pub fn set_play_sound_when_agent_done(&mut self, allow: bool) { - self.play_sound_when_agent_done = Some(allow); - } - - pub fn set_single_file_review(&mut self, allow: bool) { - self.single_file_review = Some(allow); - } - - pub fn set_use_modifier_to_send(&mut self, always_use: bool) { - self.use_modifier_to_send = Some(always_use); - } - - pub fn set_profile(&mut self, profile_id: AgentProfileId) { - self.default_profile = Some(profile_id); - } - - pub fn create_profile( - &mut self, - profile_id: AgentProfileId, - profile_settings: AgentProfileSettings, - ) -> Result<()> { - let profiles = self.profiles.get_or_insert_default(); - if profiles.contains_key(&profile_id) { - bail!("profile with ID '{profile_id}' already exists"); - } - - profiles.insert( - profile_id, - AgentProfileContent { - name: profile_settings.name.into(), - tools: profile_settings.tools, - enable_all_context_servers: Some(profile_settings.enable_all_context_servers), - context_servers: profile_settings - .context_servers - .into_iter() - .map(|(server_id, preset)| { - ( - server_id, - ContextServerPresetContent { - tools: preset.tools, - }, - ) - }) - .collect(), - }, - ); - - Ok(()) - } -} - -#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi, SettingsKey)] -#[settings_key(key = "agent", fallback_key = "assistant")] -pub struct AgentSettingsContent { - /// Whether the Agent is enabled. - /// - /// Default: true - enabled: Option, - /// Whether to show the agent panel button in the status bar. - /// - /// Default: true - button: Option, - /// Where to dock the agent panel. - /// - /// Default: right - dock: Option, - /// Default width in pixels when the agent panel is docked to the left or right. - /// - /// Default: 640 - default_width: Option, - /// Default height in pixels when the agent panel is docked to the bottom. - /// - /// Default: 320 - default_height: Option, - /// The default model to use when creating new chats and for other features when a specific model is not specified. - default_model: Option, - /// Model to use for the inline assistant. Defaults to default_model when not specified. - inline_assistant_model: Option, - /// Model to use for generating git commit messages. Defaults to default_model when not specified. - commit_message_model: Option, - /// Model to use for generating thread summaries. Defaults to default_model when not specified. - thread_summary_model: Option, - /// Additional models with which to generate alternatives when performing inline assists. - inline_alternatives: Option>, - /// The default profile to use in the Agent. - /// - /// Default: write - default_profile: Option, - /// Which view type to show by default in the agent panel. - /// - /// Default: "thread" - default_view: Option, - /// The available agent profiles. - pub profiles: Option>, - /// Whenever a tool action would normally wait for your confirmation - /// that you allow it, always choose to allow it. - /// - /// This setting has no effect on external agents that support permission modes, such as Claude Code. - /// - /// Set `agent_servers.claude.default_mode` to `bypassPermissions`, to disable all permission requests when using Claude Code. - /// - /// Default: false - always_allow_tool_actions: Option, - /// Where to show a popup notification when the agent is waiting for user input. - /// - /// Default: "primary_screen" - notify_when_agent_waiting: Option, - /// Whether to play a sound when the agent has either completed its response, or needs user input. - /// - /// Default: false - play_sound_when_agent_done: Option, - /// Whether to stream edits from the agent as they are received. - /// - /// Default: false - stream_edits: Option, - /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane. - /// - /// Default: true - single_file_review: Option, - /// Additional parameters for language model requests. When making a request - /// to a model, parameters will be taken from the last entry in this list - /// that matches the model's provider and name. In each entry, both provider - /// and model are optional, so that you can specify parameters for either - /// one. - /// - /// Default: [] - #[serde(default)] - model_parameters: Vec, - /// What completion mode to enable for new threads - /// - /// Default: normal - preferred_completion_mode: Option, - /// Whether to show thumb buttons for feedback in the agent panel. - /// - /// Default: true - enable_feedback: Option, - /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff. - /// - /// Default: true - expand_edit_card: Option, - /// Whether to have terminal cards in the agent panel expanded, showing the whole command output. - /// - /// Default: true - expand_terminal_card: Option, - /// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel. - /// - /// Default: false - use_modifier_to_send: Option, - /// Minimum number of lines of height the agent message editor should have. - /// - /// Default: 4 - message_editor_min_lines: Option, -} - #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] #[serde(rename_all = "snake_case")] pub enum CompletionMode { @@ -349,215 +116,140 @@ impl From for cloud_llm_client::CompletionMode { } } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct LanguageModelSelection { - pub provider: LanguageModelProviderSetting, - pub model: String, +impl From for CompletionMode { + fn from(value: settings::CompletionMode) -> Self { + match value { + settings::CompletionMode::Normal => CompletionMode::Normal, + settings::CompletionMode::Burn => CompletionMode::Burn, + } + } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct LanguageModelProviderSetting(pub String); +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)] +pub struct AgentProfileId(pub Arc); -impl JsonSchema for LanguageModelProviderSetting { - fn schema_name() -> Cow<'static, str> { - "LanguageModelProviderSetting".into() - } - - fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { - // list the builtin providers as a subset so that we still auto complete them in the settings - json_schema!({ - "anyOf": [ - { - "type": "string", - "enum": [ - "amazon-bedrock", - "anthropic", - "copilot_chat", - "deepseek", - "google", - "lmstudio", - "mistral", - "ollama", - "openai", - "openrouter", - "vercel", - "x_ai", - "zed.dev" - ] - }, - { - "type": "string", - } - ] - }) +impl AgentProfileId { + pub fn as_str(&self) -> &str { + &self.0 } } -impl From for LanguageModelProviderSetting { - fn from(provider: String) -> Self { - Self(provider) +impl std::fmt::Display for AgentProfileId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) } } -impl From<&str> for LanguageModelProviderSetting { - fn from(provider: &str) -> Self { - Self(provider.to_string()) +impl Default for AgentProfileId { + fn default() -> Self { + Self("write".into()) } } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] -pub struct AgentProfileContent { - pub name: Arc, - #[serde(default)] - pub tools: IndexMap, bool>, - /// Whether all context servers are enabled by default. - pub enable_all_context_servers: Option, - #[serde(default)] - pub context_servers: IndexMap, ContextServerPresetContent>, -} - -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct ContextServerPresetContent { - pub tools: IndexMap, bool>, -} - impl Settings for AgentSettings { - const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]); - - type FileContent = AgentSettingsContent; - - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - let mut settings = AgentSettings::default(); + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let agent = content.agent.clone().unwrap(); + Self { + enabled: agent.enabled.unwrap(), + button: agent.button.unwrap(), + dock: agent.dock.unwrap(), + default_width: px(agent.default_width.unwrap()), + default_height: px(agent.default_height.unwrap()), + default_model: Some(agent.default_model.unwrap()), + inline_assistant_model: agent.inline_assistant_model, + commit_message_model: agent.commit_message_model, + thread_summary_model: agent.thread_summary_model, + inline_alternatives: agent.inline_alternatives.unwrap_or_default(), + default_profile: AgentProfileId(agent.default_profile.unwrap()), + default_view: agent.default_view.unwrap(), + profiles: agent + .profiles + .unwrap() + .into_iter() + .map(|(key, val)| (AgentProfileId(key), val.into())) + .collect(), + always_allow_tool_actions: agent.always_allow_tool_actions.unwrap(), + notify_when_agent_waiting: agent.notify_when_agent_waiting.unwrap(), + play_sound_when_agent_done: agent.play_sound_when_agent_done.unwrap(), + stream_edits: agent.stream_edits.unwrap(), + single_file_review: agent.single_file_review.unwrap(), + model_parameters: agent.model_parameters, + preferred_completion_mode: agent.preferred_completion_mode.unwrap().into(), + enable_feedback: agent.enable_feedback.unwrap(), + expand_edit_card: agent.expand_edit_card.unwrap(), + expand_terminal_card: agent.expand_terminal_card.unwrap(), + use_modifier_to_send: agent.use_modifier_to_send.unwrap(), + message_editor_min_lines: agent.message_editor_min_lines.unwrap(), + } + } - for value in sources.defaults_and_customizations() { - merge(&mut settings.enabled, value.enabled); - merge(&mut settings.button, value.button); - merge(&mut settings.dock, value.dock); - merge( - &mut settings.default_width, - value.default_width.map(Into::into), - ); - merge( - &mut settings.default_height, - value.default_height.map(Into::into), - ); - settings.default_model = value - .default_model - .clone() - .or(settings.default_model.take()); - settings.inline_assistant_model = value - .inline_assistant_model - .clone() - .or(settings.inline_assistant_model.take()); - settings.commit_message_model = value - .clone() - .commit_message_model - .or(settings.commit_message_model.take()); - settings.thread_summary_model = value - .clone() - .thread_summary_model - .or(settings.thread_summary_model.take()); - merge( - &mut settings.inline_alternatives, - value.inline_alternatives.clone(), - ); - merge( - &mut settings.notify_when_agent_waiting, - value.notify_when_agent_waiting, - ); - merge( - &mut settings.play_sound_when_agent_done, - value.play_sound_when_agent_done, - ); - merge(&mut settings.stream_edits, value.stream_edits); - merge(&mut settings.single_file_review, value.single_file_review); - merge(&mut settings.default_profile, value.default_profile.clone()); - merge(&mut settings.default_view, value.default_view); - merge( - &mut settings.preferred_completion_mode, - value.preferred_completion_mode, - ); - merge(&mut settings.enable_feedback, value.enable_feedback); - merge(&mut settings.expand_edit_card, value.expand_edit_card); - merge( - &mut settings.expand_terminal_card, - value.expand_terminal_card, - ); - merge( - &mut settings.use_modifier_to_send, - value.use_modifier_to_send, - ); - merge( - &mut settings.message_editor_min_lines, - value.message_editor_min_lines, + fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { + let Some(value) = &content.agent else { return }; + self.enabled.merge_from(&value.enabled); + self.button.merge_from(&value.button); + self.dock.merge_from(&value.dock); + self.default_width + .merge_from(&value.default_width.map(Into::into)); + self.default_height + .merge_from(&value.default_height.map(Into::into)); + self.default_model = value.default_model.clone().or(self.default_model.take()); + + self.inline_assistant_model = value + .inline_assistant_model + .clone() + .or(self.inline_assistant_model.take()); + self.commit_message_model = value + .clone() + .commit_message_model + .or(self.commit_message_model.take()); + self.thread_summary_model = value + .clone() + .thread_summary_model + .or(self.thread_summary_model.take()); + self.inline_alternatives + .merge_from(&value.inline_alternatives.clone()); + self.default_profile + .merge_from(&value.default_profile.clone().map(AgentProfileId)); + self.default_view.merge_from(&value.default_view); + self.always_allow_tool_actions + .merge_from(&value.always_allow_tool_actions); + self.notify_when_agent_waiting + .merge_from(&value.notify_when_agent_waiting); + self.play_sound_when_agent_done + .merge_from(&value.play_sound_when_agent_done); + self.stream_edits.merge_from(&value.stream_edits); + self.single_file_review + .merge_from(&value.single_file_review); + self.preferred_completion_mode + .merge_from(&value.preferred_completion_mode.map(Into::into)); + self.enable_feedback.merge_from(&value.enable_feedback); + self.expand_edit_card.merge_from(&value.expand_edit_card); + self.expand_terminal_card + .merge_from(&value.expand_terminal_card); + self.use_modifier_to_send + .merge_from(&value.use_modifier_to_send); + + self.model_parameters + .extend_from_slice(&value.model_parameters); + self.message_editor_min_lines + .merge_from(&value.message_editor_min_lines); + + if let Some(profiles) = value.profiles.as_ref() { + self.profiles.extend( + profiles + .into_iter() + .map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())), ); - - settings - .model_parameters - .extend_from_slice(&value.model_parameters); - - if let Some(profiles) = value.profiles.as_ref() { - settings - .profiles - .extend(profiles.into_iter().map(|(id, profile)| { - ( - id.clone(), - AgentProfileSettings { - name: profile.name.clone().into(), - tools: profile.tools.clone(), - enable_all_context_servers: profile - .enable_all_context_servers - .unwrap_or_default(), - context_servers: profile - .context_servers - .iter() - .map(|(context_server_id, preset)| { - ( - context_server_id.clone(), - ContextServerPreset { - tools: preset.tools.clone(), - }, - ) - }) - .collect(), - }, - ) - })); - } } - - debug_assert!( - !sources.default.always_allow_tool_actions.unwrap_or(false), - "For security, agent.always_allow_tool_actions should always be false in default.json. If it's true, that is a bug that should be fixed!" - ); - - // For security reasons, only trust the user's global settings for whether to always allow tool actions. - // If this could be overridden locally, an attacker could (e.g. by committing to source control and - // convincing you to switch branches) modify your project-local settings to disable the agent's safety checks. - settings.always_allow_tool_actions = sources - .user - .and_then(|setting| setting.always_allow_tool_actions) - .unwrap_or(false); - - Ok(settings) } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(b) = vscode .read_value("chat.agent.enabled") .and_then(|b| b.as_bool()) { - current.enabled = Some(b); - current.button = Some(b); + current.agent.get_or_insert_default().enabled = Some(b); + current.agent.get_or_insert_default().button = Some(b); } } } - -fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; - } -} diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index cf5284f643cfe3d58ff62a4fa549a84f0a62db69..cd72be9b184ded0d53125bfd569da89acff59a48 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -7,7 +7,7 @@ use acp_thread::{AgentConnection, Plan}; use action_log::ActionLog; use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, AgentServerDelegate}; -use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting}; +use agent_settings::{AgentProfileId, AgentSettings, CompletionMode}; use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer}; use anyhow::{Result, anyhow, bail}; use arrayvec::ArrayVec; @@ -35,7 +35,7 @@ use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle}; use project::{Project, ProjectEntryId}; use prompt_store::{PromptId, PromptStore}; use rope::Point; -use settings::{Settings as _, SettingsStore}; +use settings::{NotifyWhenAgentWaiting, Settings as _, SettingsStore}; use std::cell::RefCell; use std::path::Path; use std::sync::Arc; diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index 69044d9c663e6b42b0a73fb9bc29738a3c3fc737..48203e75af3274fa30da826026c65869f96841f2 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -26,12 +26,8 @@ use language_model::{ }; use notifications::status_toast::{StatusToast, ToastIcon}; use project::{ - agent_server_store::{ - AgentServerCommand, AgentServerStore, AllAgentServersSettings, CLAUDE_CODE_NAME, - CustomAgentServerSettings, GEMINI_NAME, - }, + agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, GEMINI_NAME}, context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, - project_settings::{ContextServerSettings, ProjectSettings}, }; use settings::{Settings, SettingsStore, update_settings_file}; use ui::{ @@ -419,8 +415,8 @@ impl AgentConfiguration { always_allow_tool_actions, move |state, _window, cx| { let allow = state == &ToggleState::Selected; - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.set_always_allow_tool_actions(allow); + update_settings_file(fs.clone(), cx, move |settings, _| { + settings.agent.get_or_insert_default().set_always_allow_tool_actions(allow); }); }, ) @@ -437,8 +433,11 @@ impl AgentConfiguration { single_file_review, move |state, _window, cx| { let allow = state == &ToggleState::Selected; - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.set_single_file_review(allow); + update_settings_file(fs.clone(), cx, move |settings, _| { + settings + .agent + .get_or_insert_default() + .set_single_file_review(allow); }); }, ) @@ -457,8 +456,8 @@ impl AgentConfiguration { play_sound_when_agent_done, move |state, _window, cx| { let allow = state == &ToggleState::Selected; - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.set_play_sound_when_agent_done(allow); + update_settings_file(fs.clone(), cx, move |settings, _| { + settings.agent.get_or_insert_default().set_play_sound_when_agent_done(allow); }); }, ) @@ -477,8 +476,8 @@ impl AgentConfiguration { use_modifier_to_send, move |state, _window, cx| { let allow = state == &ToggleState::Selected; - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.set_use_modifier_to_send(allow); + update_settings_file(fs.clone(), cx, move |settings, _| { + settings.agent.get_or_insert_default().set_use_modifier_to_send(allow); }); }, ) @@ -862,14 +861,14 @@ impl AgentConfiguration { async move |cx| { uninstall_extension_task.await?; cx.update(|cx| { - update_settings_file::( + update_settings_file( fs.clone(), cx, { let context_server_id = context_server_id.clone(); move |settings, _| { - settings + settings.project .context_servers .remove(&context_server_id.0); } @@ -944,67 +943,53 @@ impl AgentConfiguration { .flex_none() .child(context_server_configuration_menu) .child( - Switch::new("context-server-switch", is_running.into()) - .color(SwitchColor::Accent) - .on_click({ - let context_server_manager = - self.context_server_store.clone(); - let fs = self.fs.clone(); - - move |state, _window, cx| { - let is_enabled = match state { - ToggleState::Unselected - | ToggleState::Indeterminate => { - context_server_manager.update( - cx, - |this, cx| { - this.stop_server( - &context_server_id, - cx, - ) - .log_err(); - }, - ); - false - } - ToggleState::Selected => { - context_server_manager.update( - cx, - |this, cx| { - if let Some(server) = - this.get_server(&context_server_id) - { - this.start_server(server, cx); - } - }, - ); - true - } - }; - update_settings_file::( - fs.clone(), - cx, - { - let context_server_id = - context_server_id.clone(); - - move |settings, _| { - settings - .context_servers - .entry(context_server_id.0) - .or_insert_with(|| { - ContextServerSettings::Extension { - enabled: is_enabled, - settings: serde_json::json!({}), - } - }) - .set_enabled(is_enabled); + Switch::new("context-server-switch", is_running.into()) + .color(SwitchColor::Accent) + .on_click({ + let context_server_manager = self.context_server_store.clone(); + let fs = self.fs.clone(); + + move |state, _window, cx| { + let is_enabled = match state { + ToggleState::Unselected + | ToggleState::Indeterminate => { + context_server_manager.update(cx, |this, cx| { + this.stop_server(&context_server_id, cx) + .log_err(); + }); + false + } + ToggleState::Selected => { + context_server_manager.update(cx, |this, cx| { + if let Some(server) = + this.get_server(&context_server_id) + { + this.start_server(server, cx); } - }, - ); - } - }), - ), + }); + true + } + }; + update_settings_file(fs.clone(), cx, { + let context_server_id = context_server_id.clone(); + + move |settings, _| { + settings + .project + .context_servers + .entry(context_server_id.0) + .or_insert_with(|| { + settings::ContextServerSettingsContent::Extension { + enabled: is_enabled, + settings: serde_json::json!({}), + } + }) + .set_enabled(is_enabled); + } + }); + } + }), + ), ), ) .map(|parent| { @@ -1236,15 +1221,12 @@ fn show_unable_to_uninstall_extension_with_context_server( let context_server_id = context_server_id.clone(); async move |_workspace_handle, cx| { cx.update(|cx| { - update_settings_file::( - fs, - cx, - move |settings, _| { - settings - .context_servers - .remove(&context_server_id.0); - }, - ); + update_settings_file(fs, cx, move |settings, _| { + settings + .project + .context_servers + .remove(&context_server_id.0); + }); })?; anyhow::Ok(()) } @@ -1282,7 +1264,7 @@ async fn open_new_agent_servers_entry_in_settings_editor( let settings = cx.global::(); let mut unique_server_name = None; - let edits = settings.edits_for_update::(&text, |file| { + let edits = settings.edits_for_update(&text, |settings| { let server_name: Option = (0..u8::MAX) .map(|i| { if i == 0 { @@ -1291,20 +1273,27 @@ async fn open_new_agent_servers_entry_in_settings_editor( format!("your_agent_{}", i).into() } }) - .find(|name| !file.custom.contains_key(name)); + .find(|name| { + !settings + .agent_servers + .as_ref() + .is_some_and(|agent_servers| agent_servers.custom.contains_key(name)) + }); if let Some(server_name) = server_name { unique_server_name = Some(server_name.clone()); - file.custom.insert( - server_name, - CustomAgentServerSettings { - command: AgentServerCommand { + settings + .agent_servers + .get_or_insert_default() + .custom + .insert( + server_name, + settings::CustomAgentServerSettings { path: "path_to_executable".into(), args: vec![], env: Some(HashMap::default()), + default_mode: None, }, - default_mode: None, - }, - ); + ); } }); 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 182831f488870997d175cce0ad7e1c94e392f1ea..373756b2c45ceeb65afebaf1f2d82b1fc16c017d 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 @@ -5,11 +5,8 @@ use collections::HashSet; use fs::Fs; use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, Task}; use language_model::LanguageModelRegistry; -use language_models::{ - AllLanguageModelSettings, OpenAiCompatibleSettingsContent, - provider::open_ai_compatible::{AvailableModel, ModelCapabilities}, -}; -use settings::update_settings_file; +use language_models::provider::open_ai_compatible::{AvailableModel, ModelCapabilities}; +use settings::{OpenAiCompatibleSettingsContent, update_settings_file}; use ui::{ Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*, }; @@ -238,14 +235,19 @@ fn save_provider_to_settings( task.await .map_err(|_| "Failed to write API key to keychain")?; cx.update(|cx| { - update_settings_file::(fs, cx, |settings, _cx| { - settings.openai_compatible.get_or_insert_default().insert( - provider_name, - OpenAiCompatibleSettingsContent { - api_url, - available_models: models, - }, - ); + update_settings_file(fs, cx, |settings, _cx| { + settings + .language_models + .get_or_insert_default() + .openai_compatible + .get_or_insert_default() + .insert( + provider_name, + OpenAiCompatibleSettingsContent { + api_url, + available_models: models, + }, + ); }); }) .ok(); 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 4d338840143fbcf007f7d5c66e2406ef4bb9fc88..ce8e167dab3ed2e4d84c4afd747cb266740f1d42 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 @@ -422,18 +422,17 @@ impl ConfigureContextServerModal { workspace.update(cx, |workspace, cx| { let fs = workspace.app_state().fs.clone(); let original_server_id = self.original_server_id.clone(); - update_settings_file::( - fs.clone(), - cx, - move |project_settings, _| { - if let Some(original_id) = original_server_id { - if original_id != id { - project_settings.context_servers.remove(&original_id.0); - } + update_settings_file(fs.clone(), cx, move |current, _| { + if let Some(original_id) = original_server_id { + if original_id != id { + current.project.context_servers.remove(&original_id.0); } - project_settings.context_servers.insert(id.0, settings); - }, - ); + } + current + .project + .context_servers + .insert(id.0, settings.into()); + }); }); } else if let Some(existing_server) = existing_server { self.context_server_store diff --git a/crates/agent_ui/src/agent_configuration/tool_picker.rs b/crates/agent_ui/src/agent_configuration/tool_picker.rs index 2ba92fa6b7993664d278cfd57d851dcfd9cb0922..c624948944c0624e75e385d1b4b15aa77fea9bcd 100644 --- a/crates/agent_ui/src/agent_configuration/tool_picker.rs +++ b/crates/agent_ui/src/agent_configuration/tool_picker.rs @@ -1,14 +1,11 @@ use std::{collections::BTreeMap, sync::Arc}; -use agent_settings::{ - AgentProfileContent, AgentProfileId, AgentProfileSettings, AgentSettings, AgentSettingsContent, - ContextServerPresetContent, -}; +use agent_settings::{AgentProfileId, AgentProfileSettings}; use assistant_tool::{ToolSource, ToolWorkingSet}; use fs::Fs; use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window}; use picker::{Picker, PickerDelegate}; -use settings::update_settings_file; +use settings::{AgentProfileContent, ContextServerPresetContent, update_settings_file}; use ui::{ListItem, ListItemSpacing, prelude::*}; use util::ResultExt as _; @@ -266,15 +263,19 @@ impl PickerDelegate for ToolPickerDelegate { is_enabled }; - update_settings_file::(self.fs.clone(), cx, { + update_settings_file(self.fs.clone(), cx, { let profile_id = self.profile_id.clone(); let default_profile = self.profile_settings.clone(); let server_id = server_id.clone(); let tool_name = tool_name.clone(); - move |settings: &mut AgentSettingsContent, _cx| { - let profiles = settings.profiles.get_or_insert_default(); + move |settings, _cx| { + let profiles = settings + .agent + .get_or_insert_default() + .profiles + .get_or_insert_default(); let profile = profiles - .entry(profile_id) + .entry(profile_id.0) .or_insert_with(|| AgentProfileContent { name: default_profile.name.into(), tools: default_profile.tools, diff --git a/crates/agent_ui/src/agent_model_selector.rs b/crates/agent_ui/src/agent_model_selector.rs index 58b95d9b1f1b1e8abe7335a2299bee7545b7653e..fe25cadc3c1df785c89318882a246e2209cb42e6 100644 --- a/crates/agent_ui/src/agent_model_selector.rs +++ b/crates/agent_ui/src/agent_model_selector.rs @@ -2,7 +2,6 @@ use crate::{ ModelUsageContext, language_model_selector::{LanguageModelSelector, language_model_selector}, }; -use agent_settings::AgentSettings; use fs::Fs; use gpui::{Entity, FocusHandle, SharedString}; use picker::popover_menu::PickerPopoverMenu; @@ -39,14 +38,12 @@ impl AgentModelSelector { let model_id = model.id().0.to_string(); match &model_usage_context { ModelUsageContext::InlineAssistant => { - update_settings_file::( - fs.clone(), - cx, - move |settings, _cx| { - settings - .set_inline_assistant_model(provider.clone(), model_id); - }, - ); + update_settings_file(fs.clone(), cx, move |settings, _cx| { + settings + .agent + .get_or_insert_default() + .set_inline_assistant_model(provider.clone(), model_id); + }); } } }, diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 7334f1cf1283ad45e0e98d296b3641c9b8812ef5..976dbb411990b4eb647bdec9af83f97c3a9bda84 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -10,6 +10,9 @@ use project::agent_server_store::{ AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, GEMINI_NAME, }; use serde::{Deserialize, Serialize}; +use settings::{ + DefaultAgentView as DefaultView, LanguageModelProviderSetting, LanguageModelSelection, +}; use zed_actions::OpenBrowser; use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent}; @@ -33,7 +36,7 @@ use agent::{ history_store::{HistoryEntryId, HistoryStore}, thread_store::{TextThreadStore, ThreadStore}, }; -use agent_settings::{AgentDockPosition, AgentSettings, DefaultView}; +use agent_settings::AgentSettings; use ai_onboarding::AgentPanelOnboarding; use anyhow::{Result, anyhow}; use assistant_context::{AssistantContext, ContextEvent, ContextSummary}; @@ -1058,17 +1061,14 @@ impl AgentPanel { match self.active_view.which_font_size_used() { WhichFontSize::AgentFont => { if persist { - update_settings_file::( - self.fs.clone(), - cx, - move |settings, cx| { - let agent_font_size = - ThemeSettings::get_global(cx).agent_font_size(cx) + delta; - let _ = settings - .agent_font_size - .insert(Some(theme::clamp_font_size(agent_font_size).into())); - }, - ); + update_settings_file(self.fs.clone(), cx, move |settings, cx| { + let agent_font_size = + ThemeSettings::get_global(cx).agent_font_size(cx) + delta; + let _ = settings + .theme + .agent_font_size + .insert(Some(theme::clamp_font_size(agent_font_size).into())); + }); } else { theme::adjust_agent_font_size(cx, |size| size + delta); } @@ -1089,8 +1089,8 @@ impl AgentPanel { cx: &mut Context, ) { if action.persist { - update_settings_file::(self.fs.clone(), cx, move |settings, _| { - settings.agent_font_size = None; + update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.theme.agent_font_size = None; }); } else { theme::reset_agent_font_size(cx); @@ -1175,11 +1175,17 @@ impl AgentPanel { .is_none_or(|model| model.provider.id() != provider.id()) && 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, _| { + let provider = model.provider_id().0.to_string(); + let model = model.id().0.to_string(); + settings + .agent + .get_or_insert_default() + .set_model(LanguageModelSelection { + provider: LanguageModelProviderSetting(provider), + model, + }) + }); } self.new_thread(&NewThread::default(), window, cx); @@ -1424,11 +1430,7 @@ impl Focusable for AgentPanel { } fn agent_panel_dock_position(cx: &App) -> DockPosition { - match AgentSettings::get_global(cx).dock { - AgentDockPosition::Left => DockPosition::Left, - AgentDockPosition::Bottom => DockPosition::Bottom, - AgentDockPosition::Right => DockPosition::Right, - } + AgentSettings::get_global(cx).dock.into() } impl EventEmitter for AgentPanel {} @@ -1447,13 +1449,11 @@ impl Panel for AgentPanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::(self.fs.clone(), cx, move |settings, _| { - let dock = match position { - DockPosition::Left => AgentDockPosition::Left, - DockPosition::Bottom => AgentDockPosition::Bottom, - DockPosition::Right => AgentDockPosition::Right, - }; - settings.set_dock(dock); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings + .agent + .get_or_insert_default() + .set_dock(position.into()); }); } diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index a1de392158e80be11d69a9d98cbe292f2cadf540..e34789d62d2c95b06f5c4f03b93b60f01c6dbf6a 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -23,7 +23,7 @@ use std::rc::Rc; use std::sync::Arc; use agent::ThreadId; -use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection}; +use agent_settings::{AgentProfileId, AgentSettings}; use assistant_slash_command::SlashCommandRegistry; use client::Client; use command_palette_hooks::CommandPaletteFilter; @@ -39,7 +39,7 @@ use project::agent_server_store::AgentServerCommand; use prompt_store::PromptBuilder; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings as _, SettingsStore}; +use settings::{LanguageModelSelection, Settings as _, SettingsStore}; use std::any::TypeId; use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal}; diff --git a/crates/agent_ui/src/context_server_configuration.rs b/crates/agent_ui/src/context_server_configuration.rs index fe15e8606d4026054ef0d04f1aab08375a440cf7..3a1a8695172fcb68e52b6269b88b7b1576c2a5cb 100644 --- a/crates/agent_ui/src/context_server_configuration.rs +++ b/crates/agent_ui/src/context_server_configuration.rs @@ -5,7 +5,6 @@ use extension::ExtensionManifest; use fs::Fs; use gpui::WeakEntity; use language::LanguageRegistry; -use project::project_settings::ProjectSettings; use settings::update_settings_file; use ui::prelude::*; use util::ResultExt; @@ -69,8 +68,9 @@ fn remove_context_server_settings( fs: Arc, cx: &mut App, ) { - update_settings_file::(fs, cx, move |settings, _| { + update_settings_file(fs, cx, move |settings, _| { settings + .project .context_servers .retain(|server_id, _| !context_server_ids.contains(server_id)); }); diff --git a/crates/agent_ui/src/profile_selector.rs b/crates/agent_ui/src/profile_selector.rs index 85f74a0f7445df03a24bf083f31d56f97e5a07b2..af2354f7a854fd84483889f18e0f51e1c294d8a2 100644 --- a/crates/agent_ui/src/profile_selector.rs +++ b/crates/agent_ui/src/profile_selector.rs @@ -1,11 +1,10 @@ use crate::{ManageProfiles, ToggleProfileSelector}; use agent_settings::{ - AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles, - builtin_profiles, + AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles, builtin_profiles, }; use fs::Fs; use gpui::{Action, Entity, FocusHandle, Subscription, prelude::*}; -use settings::{Settings as _, SettingsStore, update_settings_file}; +use settings::{DockPosition, Settings as _, SettingsStore, update_settings_file}; use std::sync::Arc; use ui::{ ContextMenu, ContextMenuEntry, DocumentationEdge, DocumentationSide, PopoverMenu, @@ -142,10 +141,13 @@ impl ProfileSelector { let fs = self.fs.clone(); let provider = self.provider.clone(); move |_window, cx| { - update_settings_file::(fs.clone(), cx, { + update_settings_file(fs.clone(), cx, { let profile_id = profile_id.clone(); move |settings, _cx| { - settings.set_profile(profile_id); + settings + .agent + .get_or_insert_default() + .set_profile(profile_id.0); } }); @@ -216,10 +218,10 @@ impl Render for ProfileSelector { } } -fn documentation_side(position: AgentDockPosition) -> DocumentationSide { +fn documentation_side(position: DockPosition) -> DocumentationSide { match position { - AgentDockPosition::Left => DocumentationSide::Right, - AgentDockPosition::Bottom => DocumentationSide::Left, - AgentDockPosition::Right => DocumentationSide::Left, + DockPosition::Left => DocumentationSide::Right, + DockPosition::Bottom => DocumentationSide::Left, + DockPosition::Right => DocumentationSide::Left, } } diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index df904b2416cc9b66371a3557b04ff97246f25a41..3c09e47852ffae8f45a5315859a7bb3392b1680d 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -3,7 +3,7 @@ use crate::{ language_model_selector::{LanguageModelSelector, language_model_selector}, ui::BurnModeTooltip, }; -use agent_settings::{AgentSettings, CompletionMode}; +use agent_settings::CompletionMode; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases}; @@ -41,7 +41,10 @@ use project::{Project, Worktree}; use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate}; use rope::Point; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore, update_settings_file}; +use settings::{ + LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore, + update_settings_file, +}; use std::{ any::TypeId, cmp, @@ -294,11 +297,16 @@ impl TextThreadEditor { language_model_selector( |cx| LanguageModelRegistry::read_global(cx).default_model(), move |model, cx| { - update_settings_file::( - fs.clone(), - cx, - move |settings, _| settings.set_model(model.clone()), - ); + update_settings_file(fs.clone(), cx, move |settings, _| { + let provider = model.provider_id().0.to_string(); + let model = model.id().0.to_string(); + settings.agent.get_or_insert_default().set_model( + LanguageModelSelection { + provider: LanguageModelProviderSetting(provider), + model, + }, + ) + }); }, window, cx, diff --git a/crates/anthropic/Cargo.toml b/crates/anthropic/Cargo.toml index 8e82c7cdd6a6a1ac7dd0e8b165f4d924c85d39ab..c8103e5bfb533a0f7f8e88995ac0927073a9793f 100644 --- a/crates/anthropic/Cargo.toml +++ b/crates/anthropic/Cargo.toml @@ -23,6 +23,7 @@ http_client.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true +settings.workspace = true strum.workspace = true thiserror.workspace = true workspace-hack.workspace = true diff --git a/crates/anthropic/src/anthropic.rs b/crates/anthropic/src/anthropic.rs index 7fd0fb4bc5abd983c57507522c2a37dffcbfa258..a9a4fbb6340b59cafe3bbc555efb6ad5c9e11806 100644 --- a/crates/anthropic/src/anthropic.rs +++ b/crates/anthropic/src/anthropic.rs @@ -8,6 +8,7 @@ use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::B use http_client::http::{self, HeaderMap, HeaderValue}; use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, StatusCode}; use serde::{Deserialize, Serialize}; +pub use settings::{AnthropicAvailableModel as AvailableModel, ModelMode}; use strum::{EnumIter, EnumString}; use thiserror::Error; @@ -31,6 +32,24 @@ pub enum AnthropicModelMode { }, } +impl From for AnthropicModelMode { + fn from(value: ModelMode) -> Self { + match value { + ModelMode::Default => AnthropicModelMode::Default, + ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens }, + } + } +} + +impl From for ModelMode { + fn from(value: AnthropicModelMode) -> Self { + match value { + AnthropicModelMode::Default => ModelMode::Default, + AnthropicModelMode::Thinking { budget_tokens } => ModelMode::Thinking { budget_tokens }, + } + } +} + #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)] pub enum Model { diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index d13f9891c3af1933ee49428c223d3e6737871047..1fcd7bbf14fb2e37646902102d51392bc8a470f8 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -1445,8 +1445,8 @@ mod tests { fn init_test_with_config(cx: &mut TestAppContext, data_dir: &Path) { cx.update(|cx| { - // Set custom data directory (config will be under data_dir/config) paths::set_custom_data_dir(data_dir.to_str().unwrap()); + // Set custom data directory (config will be under data_dir/config) let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); @@ -1537,14 +1537,11 @@ mod tests { // First, test with format_on_save enabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.format_on_save = Some(FormatOnSave::On); - settings.defaults.formatter = - Some(language::language_settings::SelectedFormatter::Auto); - }, - ); + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On); + settings.project.all_languages.defaults.formatter = + Some(language::language_settings::SelectedFormatter::Auto); + }); }); }); @@ -1603,12 +1600,10 @@ mod tests { // Next, test with format_on_save disabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.format_on_save = Some(FormatOnSave::Off); - }, - ); + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.format_on_save = + Some(FormatOnSave::Off); + }); }); }); @@ -1679,12 +1674,13 @@ mod tests { // First, test with remove_trailing_whitespace_on_save enabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.remove_trailing_whitespace_on_save = Some(true); - }, - ); + store.update_user_settings(cx, |settings| { + settings + .project + .all_languages + .defaults + .remove_trailing_whitespace_on_save = Some(true); + }); }); }); @@ -1741,12 +1737,13 @@ mod tests { // Next, test with remove_trailing_whitespace_on_save disabled cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::( - cx, - |settings| { - settings.defaults.remove_trailing_whitespace_on_save = Some(false); - }, - ); + store.update_user_settings(cx, |settings| { + settings + .project + .all_languages + .defaults + .remove_trailing_whitespace_on_save = Some(false); + }); }); }); diff --git a/crates/assistant_tools/src/grep_tool.rs b/crates/assistant_tools/src/grep_tool.rs index e43a54661ca146902a49fa1d975e44d486e18587..9f536df995cdfc5860cc3377dce65871386d50e0 100644 --- a/crates/assistant_tools/src/grep_tool.rs +++ b/crates/assistant_tools/src/grep_tool.rs @@ -314,7 +314,7 @@ mod tests { use gpui::{AppContext, TestAppContext, UpdateGlobal}; use language::{Language, LanguageConfig, LanguageMatcher}; use language_model::fake_provider::FakeLanguageModel; - use project::{FakeFs, Project, WorktreeSettings}; + use project::{FakeFs, Project}; use serde_json::json; use settings::SettingsStore; use unindent::Unindent; @@ -849,15 +849,14 @@ mod tests { cx.update(|cx| { use gpui::UpdateGlobal; - use project::WorktreeSettings; use settings::SettingsStore; SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.private_files = Some(vec![ + settings.project.worktree.private_files = Some(vec![ "**/.mysecrets".to_string(), "**/*.privatekey".to_string(), "**/*.mysensitive".to_string(), @@ -1158,10 +1157,10 @@ mod tests { // Set global settings cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); }); }); }); diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 5471d8923b557ac26d06a16c90fdeffb152049d1..9b4b501bfa55c3037bf6658686d92668cac966a6 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -230,7 +230,7 @@ mod tests { use gpui::{AppContext, TestAppContext, UpdateGlobal}; use indoc::indoc; use language_model::fake_provider::FakeLanguageModel; - use project::{FakeFs, Project, WorktreeSettings}; + use project::{FakeFs, Project}; use serde_json::json; use settings::SettingsStore; use util::path; @@ -507,13 +507,13 @@ mod tests { // Configure settings explicitly cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.secretdir".to_string(), "**/.mymetadata".to_string(), "**/.hidden_subdir".to_string(), ]); - settings.private_files = Some(vec![ + settings.project.worktree.private_files = Some(vec![ "**/.mysecrets".to_string(), "**/*.privatekey".to_string(), "**/*.mysensitive".to_string(), @@ -698,10 +698,10 @@ mod tests { // Set global settings cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); }); }); }); diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 7222f061c7caba54ee2e3294378c4a7d957914f5..4ac2ec14ab9dde97b0ff89a40db356fef42d3741 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -299,7 +299,7 @@ mod test { use gpui::{AppContext, TestAppContext, UpdateGlobal}; use language::{Language, LanguageConfig, LanguageMatcher}; use language_model::fake_provider::FakeLanguageModel; - use project::{FakeFs, Project, WorktreeSettings}; + use project::{FakeFs, Project}; use serde_json::json; use settings::SettingsStore; use util::path; @@ -677,15 +677,14 @@ mod test { cx.update(|cx| { use gpui::UpdateGlobal; - use project::WorktreeSettings; use settings::SettingsStore; SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.private_files = Some(vec![ + settings.project.worktree.private_files = Some(vec![ "**/.mysecrets".to_string(), "**/*.privatekey".to_string(), "**/*.mysensitive".to_string(), @@ -968,10 +967,10 @@ mod test { // Set global settings cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); }); }); }); diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 85274f651417f8df91e2f785056e5ee8da0220de..c083c9a659e50aef37acc2cdfc239696bd469c1e 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -21,7 +21,6 @@ gpui.workspace = true log.workspace = true parking_lot.workspace = true rodio = { workspace = true, features = [ "wav", "playback", "wav_output" ] } -schemars.workspace = true serde.workspace = true settings.workspace = true smol.workspace = true diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index ea0ea5f3558e015f5579cca43eeb8c529273cb52..28f97c4b4df6f8741c3fd0552ed883cc7a8838a3 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -1,62 +1,53 @@ use std::sync::atomic::{AtomicBool, Ordering}; -use anyhow::Result; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi}; +use settings::{Settings, SettingsStore}; +use util::MergeFrom as _; -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] +#[derive(Clone, Debug)] pub struct AudioSettings { /// Opt into the new audio system. - #[serde(rename = "experimental.rodio_audio", default)] pub rodio_audio: bool, // default is false /// Requires 'rodio_audio: true' /// /// Use the new audio systems automatic gain control for your microphone. /// This affects how loud you sound to others. - #[serde(rename = "experimental.control_input_volume", default)] pub control_input_volume: bool, /// Requires 'rodio_audio: true' /// /// Use the new audio systems automatic gain control on everyone in the /// call. This makes call members who are too quite louder and those who are /// too loud quieter. This only affects how things sound for you. - #[serde(rename = "experimental.control_output_volume", default)] - pub control_output_volume: bool, -} - -/// Configuration of audio in Zed. -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[serde(default)] -#[settings_key(key = "audio")] -pub struct AudioSettingsContent { - /// Opt into the new audio system. - #[serde(rename = "experimental.rodio_audio", default)] - pub rodio_audio: bool, // default is false - /// Requires 'rodio_audio: true' - /// - /// Use the new audio systems automatic gain control for your microphone. - /// This affects how loud you sound to others. - #[serde(rename = "experimental.control_input_volume", default)] - pub control_input_volume: bool, - /// Requires 'rodio_audio: true' - /// - /// Use the new audio systems automatic gain control on everyone in the - /// call. This makes call members who are too quite louder and those who are - /// too loud quieter. This only affects how things sound for you. - #[serde(rename = "experimental.control_output_volume", default)] pub control_output_volume: bool, } +/// Configuration of audio in Zed impl Settings for AudioSettings { - type FileContent = AudioSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let audio = &content.audio.as_ref().unwrap(); + AudioSettings { + control_input_volume: audio.control_input_volume.unwrap(), + control_output_volume: audio.control_output_volume.unwrap(), + rodio_audio: audio.rodio_audio.unwrap(), + } + } - fn load(sources: SettingsSources, _cx: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(audio) = content.audio.as_ref() else { + return; + }; + self.control_input_volume + .merge_from(&audio.control_input_volume); + self.control_output_volume + .merge_from(&audio.control_output_volume); + self.rodio_audio.merge_from(&audio.rodio_audio); } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { + } } /// See docs on [LIVE_SETTINGS] diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 35cef84f0366b16d9e31b2416e7a6a10173ff5ef..21df028a88f027b1ce3796ef3e04998ca205ce51 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -21,7 +21,6 @@ http_client.workspace = true log.workspace = true paths.workspace = true release_channel.workspace = true -schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index e6274cf3988d4bd35dead9c988a294c922c2aaf3..4e0348575e687c2b4e36fcde7df83b8f329733d0 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -8,9 +8,8 @@ use gpui::{ use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use paths::remote_servers_dir; use release_channel::{AppCommitSha, ReleaseChannel}; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi}; +use settings::{Settings, SettingsContent, SettingsStore}; use smol::{fs, io::AsyncReadExt}; use smol::{fs::File, process::Command}; use std::{ @@ -113,47 +112,27 @@ impl Drop for MacOsUnmounter { } } +#[derive(Clone, Copy, Debug)] struct AutoUpdateSetting(bool); /// Whether or not to automatically check for updates. /// /// Default: true -#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey)] -#[settings_key(None)] -#[settings_ui(group = "Auto Update")] -struct AutoUpdateSettingContent { - pub auto_update: Option, -} - impl Settings for AutoUpdateSetting { - type FileContent = AutoUpdateSettingContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - let auto_update = [ - sources.server, - sources.release_channel, - sources.operating_system, - sources.user, - ] - .into_iter() - .find_map(|value| value.and_then(|val| val.auto_update)) - .or(sources.default.auto_update) - .ok_or_else(Self::missing_default)?; - - Ok(Self(auto_update)) + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + debug_assert_eq!(content.auto_update.unwrap(), true); + Self(content.auto_update.unwrap()) } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - let mut cur = &mut Some(*current); - vscode.enum_setting("update.mode", &mut cur, |s| match s { - "none" | "manual" => Some(AutoUpdateSettingContent { - auto_update: Some(false), - }), - _ => Some(AutoUpdateSettingContent { - auto_update: Some(true), - }), - }); - *current = cur.unwrap(); + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + if let Some(auto_update) = content.auto_update { + self.0 = auto_update; + } + } + + fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) { + // We could match on vscode's update.mode here, but + // I think it's more important to have more people updating zed by default. } } @@ -1002,7 +981,7 @@ mod tests { #[gpui::test] fn test_auto_update_defaults_to_true(cx: &mut TestAppContext) { cx.update(|cx| { - let mut store = SettingsStore::new(cx); + let mut store = SettingsStore::new(cx, &settings::default_settings()); store .set_default_settings(&default_settings(), cx) .expect("Unable to set default settings"); diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 9dfb4acbb424724ce976f918f3bf5124a450e372..1d5fbccb4644d9f168a2afd321a205f01c8f9cdc 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -35,7 +35,6 @@ language.workspace = true log.workspace = true postage.workspace = true project.workspace = true -schemars.workspace = true serde.workspace = true settings.workspace = true telemetry.workspace = true diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index c458c17e9fcd8788aae2661fbaaa0d45b117e10f..cce8142903c17ec41244d346a79feb8f4cad70e6 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -1,36 +1,32 @@ -use anyhow::Result; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; +use util::MergeFrom; -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct CallSettings { pub mute_on_join: bool, pub share_on_join: bool, } -/// Configuration of voice calls in Zed. -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "calls")] -pub struct CallSettingsContent { - /// Whether the microphone should be muted when joining a channel or a call. - /// - /// Default: false - pub mute_on_join: Option, - - /// Whether your current project should be shared when joining an empty channel. - /// - /// Default: false - pub share_on_join: Option, -} - impl Settings for CallSettings { - type FileContent = CallSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let call = content.calls.clone().unwrap(); + CallSettings { + mute_on_join: call.mute_on_join.unwrap(), + share_on_join: call.share_on_join.unwrap(), + } + } - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + if let Some(call) = content.calls.clone() { + self.mute_on_join.merge_from(&call.mute_on_join); + self.share_on_join.merge_from(&call.share_on_join); + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { + } } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 8888c73f4496d39b33cb868444f39234f7d074f7..237eaa11d954db7c95eaa513e8e921a1000faac6 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -31,7 +31,7 @@ use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::{Settings, SettingsContent, SettingsKey, SettingsUi}; use std::{ any::TypeId, convert::TryFrom, @@ -50,7 +50,7 @@ use telemetry::Telemetry; use thiserror::Error; use tokio::net::TcpStream; use url::Url; -use util::{ConnectionResult, ResultExt}; +use util::{ConnectionResult, MergeFrom, ResultExt}; pub use rpc::*; pub use telemetry_events::Event; @@ -96,29 +96,33 @@ actions!( ] ); -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct ClientSettingsContent { - server_url: Option, -} - #[derive(Deserialize)] pub struct ClientSettings { pub server_url: String, } impl Settings for ClientSettings { - type FileContent = ClientSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - let mut result = sources.json_merge::()?; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { if let Some(server_url) = &*ZED_SERVER_URL { - result.server_url.clone_from(server_url) + return Self { + server_url: server_url.clone(), + }; + } + Self { + server_url: content.server_url.clone().unwrap(), } - Ok(result) } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { + if ZED_SERVER_URL.is_some() { + return; + } + if let Some(server_url) = content.server_url.clone() { + self.server_url = server_url; + } + } + + fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] @@ -147,19 +151,19 @@ impl ProxySettings { } impl Settings for ProxySettings { - type FileContent = ProxySettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - Ok(Self { - proxy: sources - .user - .or(sources.server) - .and_then(|value| value.proxy.clone()) - .or(sources.default.proxy.clone()), - }) + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + Self { + proxy: content.proxy.clone(), + } } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { + if let Some(proxy) = content.proxy.clone() { + self.proxy = Some(proxy) + } + } + + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { vscode.string_setting("http.proxy", &mut current.proxy); } } @@ -538,37 +542,41 @@ pub struct TelemetrySettings { pub metrics: bool, } -/// Control what info is collected by Zed. -#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "telemetry")] -pub struct TelemetrySettingsContent { - /// Send debug info like crash reports. - /// - /// Default: true - pub diagnostics: Option, - /// Send anonymized usage data like what languages you're using Zed with. - /// - /// Default: true - pub metrics: Option, -} - impl settings::Settings for TelemetrySettings { - type FileContent = TelemetrySettingsContent; + fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + Self { + diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(), + metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(), + } + } - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { + let Some(telemetry) = &content.telemetry else { + return; + }; + self.diagnostics.merge_from(&telemetry.diagnostics); + self.metrics.merge_from(&telemetry.metrics); } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.enum_setting("telemetry.telemetryLevel", &mut current.metrics, |s| { + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { + let mut telemetry = settings::TelemetrySettingsContent::default(); + vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| { Some(s == "all") }); - vscode.enum_setting("telemetry.telemetryLevel", &mut current.diagnostics, |s| { - Some(matches!(s, "all" | "error" | "crash")) - }); + vscode.enum_setting( + "telemetry.telemetryLevel", + &mut telemetry.diagnostics, + |s| Some(matches!(s, "all" | "error" | "crash")), + ); // we could translate telemetry.telemetryLevel, but just because users didn't want // to send microsoft telemetry doesn't mean they don't want to send it to zed. their // all/error/crash/off correspond to combinations of our "diagnostics" and "metrics". + if let Some(diagnostics) = telemetry.diagnostics { + current.telemetry.get_or_insert_default().diagnostics = Some(diagnostics) + } + if let Some(metrics) = telemetry.metrics { + current.telemetry.get_or_insert_default().metrics = Some(metrics) + } } } diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index a3f63c527693a19bb7ac1cd87c104cee3d5cfa6e..cc84ddc800d7c4c4979dc824d25fa705405a0aa7 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -4,7 +4,7 @@ use crate::{ }; use call::ActiveCall; use editor::{ - DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects, + DocumentColorsRenderMode, Editor, RowInfo, SelectionEffects, actions::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo, @@ -18,20 +18,16 @@ use fs::Fs; use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex}; use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext}; use indoc::indoc; -use language::{ - FakeLspAdapter, - language_settings::{AllLanguageSettings, InlayHintSettings}, -}; +use language::FakeLspAdapter; use lsp::LSP_REQUEST_TIMEOUT; use project::{ ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT, lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro}, - project_settings::{InlineBlameSettings, ProjectSettings}, }; use recent_projects::disconnected_overlay::DisconnectedOverlay; use rpc::RECEIVE_TIMEOUT; use serde_json::json; -use settings::SettingsStore; +use settings::{InlayHintSettingsContent, InlineBlameSettings, SettingsStore}; use std::{ collections::BTreeSet, ops::{Deref as _, Range}, @@ -1789,35 +1785,37 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - enabled: true, - show_value_hints: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: false, - show_other_hints: true, - show_background: false, - toggle_on_modifiers_press: None, - }) + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = + Some(InlayHintSettingsContent { + enabled: Some(true), + show_value_hints: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(false), + show_other_hints: Some(true), + show_background: Some(false), + toggle_on_modifiers_press: None, + }) }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: false, - show_other_hints: true, - show_background: false, - toggle_on_modifiers_press: None, - }) + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = + Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(false), + show_other_hints: Some(true), + show_background: Some(false), + toggle_on_modifiers_press: None, + }) }); }); }); @@ -2039,35 +2037,37 @@ async fn test_inlay_hint_refresh_is_forwarded( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: false, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: false, - show_parameter_hints: false, - show_other_hints: false, - show_background: false, - toggle_on_modifiers_press: None, - }) + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = + Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(false), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(false), + show_parameter_hints: Some(false), + show_other_hints: Some(false), + show_background: Some(false), + toggle_on_modifiers_press: None, + }) }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, - toggle_on_modifiers_press: None, - }) + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = + Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), + toggle_on_modifiers_press: None, + }) }); }); }); @@ -2241,15 +2241,15 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::None); + store.update_user_settings(cx, |settings| { + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None); }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay); + store.update_user_settings(cx, |settings| { + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay); }); }); }); @@ -2422,8 +2422,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo cx_b.update(|_, cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background); + store.update_user_settings(cx, |settings| { + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background); }); }); }); @@ -2450,8 +2450,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo cx_b.update(|_, cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay); + store.update_user_settings(cx, |settings| { + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay); }); }); }); @@ -2478,8 +2478,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo cx_a.update(|_, cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border); + store.update_user_settings(cx, |settings| { + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border); }); }); }); @@ -3306,20 +3306,20 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA cx_b.update(editor::init); // Turn inline-blame-off by default so no state is transferred without us explicitly doing so let inline_blame_off_settings = Some(InlineBlameSettings { - enabled: false, + enabled: Some(false), ..Default::default() }); cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.git.inline_blame = inline_blame_off_settings; + store.update_user_settings(cx, |settings| { + settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings; }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.git.inline_blame = inline_blame_off_settings; + store.update_user_settings(cx, |settings| { + settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings; }); }); }); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0a9a69bfca9cdda3fc446ac48e9c63da5e75fe28..e6bab12934d9c262cbba378e0d2a94143e0a7602 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -12,7 +12,6 @@ use gpui::{ VisualContext, VisualTestContext, point, }; use language::Capability; -use project::WorktreeSettings; use rpc::proto::PeerId; use serde_json::json; use settings::SettingsStore; @@ -1710,8 +1709,9 @@ async fn test_following_into_excluded_file( for cx in [&mut cx_a, &mut cx_b] { cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]); + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = + Some(vec!["**/.git".to_string()]); }); }); }); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 646dbfbd1575756e6955c0d60ae5af64a2760328..de255830cd154e4180afd32538f13df200bc36d5 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -22,9 +22,7 @@ use gpui::{ use language::{ Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, - language_settings::{ - AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, - }, + language_settings::{Formatter, FormatterList, SelectedFormatter}, tree_sitter_rust, tree_sitter_typescript, }; use lsp::{LanguageServerId, OneOf}; @@ -38,7 +36,7 @@ use project::{ use prompt_store::PromptBuilder; use rand::prelude::*; use serde_json::json; -use settings::SettingsStore; +use settings::{PrettierSettingsContent, SettingsStore}; use std::{ cell::{Cell, RefCell}, env, future, mem, @@ -3507,10 +3505,14 @@ async fn test_local_settings( assert_eq!( store .local_settings(worktree_b.read(cx).id()) + .map(|(path, content)| ( + path, + content.all_languages.defaults.tab_size.map(Into::into) + )) .collect::>(), &[ - (Path::new("").into(), r#"{"tab_size":2}"#.to_string()), - (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + (Path::new("").into(), Some(2)), + (Path::new("a").into(), Some(8)), ] ) }); @@ -3526,10 +3528,14 @@ async fn test_local_settings( assert_eq!( store .local_settings(worktree_b.read(cx).id()) + .map(|(path, content)| ( + path, + content.all_languages.defaults.tab_size.map(Into::into) + )) .collect::>(), &[ - (Path::new("").into(), r#"{}"#.to_string()), - (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + (Path::new("").into(), None), + (Path::new("a").into(), Some(8)), ] ) }); @@ -3555,10 +3561,14 @@ async fn test_local_settings( assert_eq!( store .local_settings(worktree_b.read(cx).id()) + .map(|(path, content)| ( + path, + content.all_languages.defaults.tab_size.map(Into::into) + )) .collect::>(), &[ - (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), - (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()), + (Path::new("a").into(), Some(8)), + (Path::new("b").into(), Some(4)), ] ) }); @@ -3587,8 +3597,9 @@ async fn test_local_settings( assert_eq!( store .local_settings(worktree_b.read(cx).id()) + .map(|(path, content)| (path, content.all_languages.defaults.hard_tabs)) .collect::>(), - &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),] + &[(Path::new("a").into(), Some(true))], ) }); } @@ -4598,15 +4609,15 @@ async fn test_formatting_buffer( // host's configuration is honored as opposed to using the guest's settings. cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( - Formatter::External { + store.update_user_settings(cx, |file| { + file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List( + FormatterList::Single(Formatter::External { command: "awk".into(), arguments: Some( vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), ), - }, - ))); + }), + )); }); }); }); @@ -4694,24 +4705,24 @@ async fn test_prettier_formatting_buffer( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::Auto); - file.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() + store.update_user_settings(cx, |file| { + file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto); + file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent { + allowed: Some(true), + ..Default::default() }); }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( - Formatter::LanguageServer { name: None }, - ))); - file.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() + store.update_user_settings(cx, |file| { + file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List( + FormatterList::Single(Formatter::LanguageServer { name: None }), + )); + file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent { + allowed: Some(true), + ..Default::default() }); }); }); diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 6b46459a59b16717d965b42c4e19820f6d1dc062..e2e6d7b724386afafad27e72f867be70671263bc 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -14,10 +14,7 @@ use gpui::{ use http_client::BlockedHttpClient; use language::{ FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry, - language_settings::{ - AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, - language_settings, - }, + language_settings::{Formatter, FormatterList, SelectedFormatter, language_settings}, tree_sitter_typescript, }; use node_runtime::NodeRuntime; @@ -30,7 +27,7 @@ use remote::RemoteClient; use remote_server::{HeadlessAppState, HeadlessProject}; use rpc::proto; use serde_json::json; -use settings::SettingsStore; +use settings::{PrettierSettingsContent, SettingsStore}; use std::{ path::Path, sync::{Arc, atomic::AtomicUsize}, @@ -499,24 +496,24 @@ async fn test_ssh_collaboration_formatting_with_prettier( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::Auto); - file.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() + store.update_user_settings(cx, |file| { + file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto); + file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent { + allowed: Some(true), + ..Default::default() }); }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( - Formatter::LanguageServer { name: None }, - ))); - file.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() + store.update_user_settings(cx, |file| { + file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List( + FormatterList::Single(Formatter::LanguageServer { name: None }), + )); + file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent { + allowed: Some(true), + ..Default::default() }); }); }); @@ -556,11 +553,11 @@ async fn test_ssh_collaboration_formatting_with_prettier( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::Auto); - file.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() + store.update_user_settings(cx, |file| { + file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto); + file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent { + allowed: Some(true), + ..Default::default() }); }); }); diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index b3b0f1b0e37207582293fcc6d8930cc004ad7405..24202445a79b5c906d4f6fe1f1a633422f24772a 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -47,7 +47,6 @@ picker.workspace = true project.workspace = true release_channel.workspace = true rpc.workspace = true -schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 1698fcb89376787bef52c0ee69b5d799976eda3b..6290878483e69d8a7b56ee865672b7f19d74d4de 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2993,11 +2993,9 @@ impl Panel for CollabPanel { _window: &mut Window, cx: &mut Context, ) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| settings.dock = Some(position), - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.collaboration_panel.get_or_insert_default().dock = Some(position.into()) + }); } fn size(&self, _window: &Window, cx: &App) -> Pixels { diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index f75dd663c838c84f167b3070b50a4e1f44e9aa2d..c43e865ef2dcbcd05a9b75cdde5e06bb0679de89 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -11,7 +11,6 @@ use gpui::{ App, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, point, }; -use panel_settings::MessageEditorSettings; pub use panel_settings::{CollaborationPanelSettings, NotificationPanelSettings}; use release_channel::ReleaseChannel; use settings::Settings; @@ -21,7 +20,6 @@ use workspace::AppState; pub fn init(app_state: &Arc, cx: &mut App) { CollaborationPanelSettings::register(cx); NotificationPanelSettings::register(cx); - MessageEditorSettings::register(cx); channel_view::init(cx); collab_panel::init(cx); diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 9731b89521e29ebda21ad5ce2cfca6e0531ae437..3d988c4634ded9bd2c94d8a75886cf452e64eacb 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -621,11 +621,9 @@ impl Panel for NotificationPanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| settings.dock = Some(position), - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.notification_panel.get_or_insert_default().dock = Some(position.into()) + }); } fn size(&self, _: &Window, cx: &App) -> Pixels { diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index 98559ffd34006bf2f65427a899fd1fe5d41a4d11..f1cd24c820a631f86c215f810011cc5870ae1ff9 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -1,102 +1,72 @@ use gpui::Pixels; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; +use ui::px; +use util::MergeFrom as _; use workspace::dock::DockPosition; -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct CollaborationPanelSettings { pub button: bool, pub dock: DockPosition, pub default_width: Pixels, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "collaboration_panel")] -pub struct PanelSettingsContent { - /// Whether to show the panel button in the status bar. - /// - /// Default: true - pub button: Option, - /// Where to dock the panel. - /// - /// Default: left - pub dock: Option, - /// Default width of the panel in pixels. - /// - /// Default: 240 - pub default_width: Option, -} - -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct NotificationPanelSettings { pub button: bool, pub dock: DockPosition, pub default_width: Pixels, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "notification_panel")] -pub struct NotificationPanelSettingsContent { - /// Whether to show the panel button in the status bar. - /// - /// Default: true - pub button: Option, - /// Where to dock the panel. - /// - /// Default: right - pub dock: Option, - /// Default width of the panel in pixels. - /// - /// Default: 300 - pub default_width: Option, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "message_editor")] -pub struct MessageEditorSettings { - /// Whether to automatically replace emoji shortcodes with emoji characters. - /// For example: typing `:wave:` gets replaced with `👋`. - /// - /// Default: false - pub auto_replace_emoji_shortcode: Option, -} - impl Settings for CollaborationPanelSettings { - type FileContent = PanelSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + let panel = content.collaboration_panel.as_ref().unwrap(); - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - sources.json_merge() + Self { + button: panel.button.unwrap(), + dock: panel.dock.unwrap().into(), + default_width: panel.default_width.map(px).unwrap(), + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} -} - -impl Settings for NotificationPanelSettings { - type FileContent = NotificationPanelSettingsContent; - - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { + if let Some(panel) = content.collaboration_panel.as_ref() { + self.button.merge_from(&panel.button); + self.default_width + .merge_from(&panel.default_width.map(Pixels::from)); + self.dock.merge_from(&panel.dock.map(Into::into)); + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _content: &mut settings::SettingsContent, + ) { + } } -impl Settings for MessageEditorSettings { - type FileContent = MessageEditorSettings; +impl Settings for NotificationPanelSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + let panel = content.notification_panel.as_ref().unwrap(); + return Self { + button: panel.button.unwrap(), + dock: panel.dock.unwrap().into(), + default_width: panel.default_width.map(px).unwrap(), + }; + } - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { + let Some(panel) = content.notification_panel.as_ref() else { + return; + }; + self.button.merge_from(&panel.button); + self.dock.merge_from(&panel.dock.map(Into::into)); + self.default_width.merge_from(&panel.default_width.map(px)); } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { + } } diff --git a/crates/context_server/Cargo.toml b/crates/context_server/Cargo.toml index 5e4f8369c45f0edb58efda1618bf8fe0aad55749..1c5745408041ae2f67e91ba0d9365188ab957d4e 100644 --- a/crates/context_server/Cargo.toml +++ b/crates/context_server/Cargo.toml @@ -25,8 +25,9 @@ net.workspace = true parking_lot.workspace = true postage.workspace = true schemars.workspace = true -serde.workspace = true serde_json.workspace = true +serde.workspace = true +settings.workspace = true smol.workspace = true tempfile.workspace = true url = { workspace = true, features = ["serde"] } diff --git a/crates/context_server/src/context_server.rs b/crates/context_server/src/context_server.rs index b126bb393784664692b5de39fee5ed7f66e9948a..52ed524220947430df3e63fced367ca4eb223fff 100644 --- a/crates/context_server/src/context_server.rs +++ b/crates/context_server/src/context_server.rs @@ -12,12 +12,9 @@ use std::{fmt::Display, path::PathBuf}; use anyhow::Result; use client::Client; -use collections::HashMap; use gpui::AsyncApp; use parking_lot::RwLock; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use util::redact::should_redact; +pub use settings::ContextServerCommand; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ContextServerId(pub Arc); @@ -28,32 +25,6 @@ impl Display for ContextServerId { } } -#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)] -pub struct ContextServerCommand { - #[serde(rename = "command")] - pub path: PathBuf, - pub args: Vec, - pub env: Option>, - /// Timeout for tool calls in milliseconds. Defaults to 60000 (60 seconds) if not specified. - pub timeout: Option, -} - -impl std::fmt::Debug for ContextServerCommand { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let filtered_env = self.env.as_ref().map(|env| { - env.iter() - .map(|(k, v)| (k, if should_redact(k) { "[REDACTED]" } else { v })) - .collect::>() - }); - - f.debug_struct("ContextServerCommand") - .field("path", &self.path) - .field("args", &self.args) - .field("env", &filtered_env) - .finish() - } -} - enum ContextServerTransport { Stdio(ContextServerCommand, Option), Custom(Arc), diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 52d75175e5b5ba265bb32c6c15c713e1bd8faecd..c122dccec069ff636d39c64f24ef0aca41145012 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -281,14 +281,11 @@ mod tests { use indoc::indoc; use language::{ Point, - language_settings::{ - AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, LspInsertMode, - WordsCompletionMode, - }, + language_settings::{CompletionSettingsContent, LspInsertMode, WordsCompletionMode}, }; use project::Project; use serde_json::json; - use settings::SettingsStore; + use settings::{AllLanguageSettingsContent, SettingsStore}; use std::future::Future; use util::{ path, @@ -299,12 +296,11 @@ mod tests { async fn test_copilot(executor: BackgroundExecutor, cx: &mut TestAppContext) { // flaky init_test(cx, |settings| { - settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Disabled, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -532,12 +528,11 @@ mod tests { ) { // flaky init_test(cx, |settings| { - settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Disabled, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -1128,7 +1123,7 @@ mod tests { Project::init_settings(cx); workspace::init_settings(cx); SettingsStore::update_global(cx, |store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, f); + store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages)); }); }); } diff --git a/crates/dap/src/debugger_settings.rs b/crates/dap/src/debugger_settings.rs index 4b841450462f1f59787df584cc4ba48eddf792c1..5fa3d60dfc08cd847dd961c322555b5ef54c78f7 100644 --- a/crates/dap/src/debugger_settings.rs +++ b/crates/dap/src/debugger_settings.rs @@ -1,28 +1,12 @@ use dap_types::SteppingGranularity; -use gpui::{App, Global}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use gpui::App; +use settings::{Settings, SettingsContent}; +use util::MergeFrom; -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum DebugPanelDockPosition { - Left, - Bottom, - Right, -} - -#[derive(Serialize, Deserialize, JsonSchema, Clone, SettingsUi, SettingsKey)] -#[serde(default)] -// todo(settings_ui) @ben: I'm pretty sure not having the fields be optional here is a bug, -// it means the defaults will override previously set values if a single key is missing -#[settings_ui(group = "Debugger")] -#[settings_key(key = "debugger")] pub struct DebuggerSettings { /// Determines the stepping granularity. /// /// Default: line - #[settings_ui(skip)] pub stepping_granularity: SteppingGranularity, /// Whether the breakpoints should be reused across Zed sessions. /// @@ -47,31 +31,53 @@ pub struct DebuggerSettings { /// The dock position of the debug panel /// /// Default: Bottom - pub dock: DebugPanelDockPosition, + pub dock: settings::DockPosition, } -impl Default for DebuggerSettings { - fn default() -> Self { +impl Settings for DebuggerSettings { + fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + let content = content.debugger.clone().unwrap(); Self { - button: true, - save_breakpoints: true, - stepping_granularity: SteppingGranularity::Line, - timeout: 2000, - log_dap_communications: true, - format_dap_log_messages: true, - dock: DebugPanelDockPosition::Bottom, + stepping_granularity: dap_granularity_from_settings( + content.stepping_granularity.unwrap(), + ), + save_breakpoints: content.save_breakpoints.unwrap(), + button: content.button.unwrap(), + timeout: content.timeout.unwrap(), + log_dap_communications: content.log_dap_communications.unwrap(), + format_dap_log_messages: content.format_dap_log_messages.unwrap(), + dock: content.dock.unwrap(), } } -} -impl Settings for DebuggerSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - sources.json_merge() + fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { + let Some(content) = &content.debugger else { + return; + }; + self.stepping_granularity.merge_from( + &content + .stepping_granularity + .map(dap_granularity_from_settings), + ); + self.save_breakpoints.merge_from(&content.save_breakpoints); + self.button.merge_from(&content.button); + self.timeout.merge_from(&content.timeout); + self.log_dap_communications + .merge_from(&content.log_dap_communications); + self.format_dap_log_messages + .merge_from(&content.format_dap_log_messages); + self.dock.merge_from(&content.dock); } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } -impl Global for DebuggerSettings {} +fn dap_granularity_from_settings( + granularity: settings::SteppingGranularity, +) -> dap_types::SteppingGranularity { + match granularity { + settings::SteppingGranularity::Instruction => dap_types::SteppingGranularity::Instruction, + settings::SteppingGranularity::Line => dap_types::SteppingGranularity::Line, + settings::SteppingGranularity::Statement => dap_types::SteppingGranularity::Statement, + } +} diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index ef714a1f6710f54c5673eac097e7530b3c605b58..12a31a7360bb6e7fb1ff6fdcaf18f73d679693a3 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -12,7 +12,6 @@ use crate::{ use anyhow::{Context as _, Result, anyhow}; use collections::IndexMap; use dap::adapters::DebugAdapterName; -use dap::debugger_settings::DebugPanelDockPosition; use dap::{DapRegistry, StartDebuggingRequestArguments}; use dap::{client::SessionId, debugger_settings::DebuggerSettings}; use editor::Editor; @@ -1400,11 +1399,7 @@ impl Panel for DebugPanel { } fn position(&self, _window: &Window, cx: &App) -> DockPosition { - match DebuggerSettings::get_global(cx).dock { - DebugPanelDockPosition::Left => DockPosition::Left, - DebugPanelDockPosition::Bottom => DockPosition::Bottom, - DebugPanelDockPosition::Right => DockPosition::Right, - } + DebuggerSettings::get_global(cx).dock.into() } fn position_is_valid(&self, _: DockPosition) -> bool { @@ -1426,18 +1421,9 @@ impl Panel for DebugPanel { }); } - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| { - let dock = match position { - DockPosition::Left => DebugPanelDockPosition::Left, - DockPosition::Bottom => DebugPanelDockPosition::Bottom, - DockPosition::Right => DebugPanelDockPosition::Right, - }; - settings.dock = dock; - }, - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.debugger.get_or_insert_default().dock = Some(position.into()); + }); } fn size(&self, _window: &Window, _: &App) -> Pixels { diff --git a/crates/edit_prediction_button/src/edit_prediction_button.rs b/crates/edit_prediction_button/src/edit_prediction_button.rs index d4c1763c405825f834b640dfb03b38bd3016288d..8822c05f6c4d1842fd74d45d876f64736eb62fbc 100644 --- a/crates/edit_prediction_button/src/edit_prediction_button.rs +++ b/crates/edit_prediction_button/src/edit_prediction_button.rs @@ -910,8 +910,10 @@ async fn open_disabled_globs_setting_in_editor( let settings = cx.global::(); // Ensure that we always have "edit_predictions { "disabled_globs": [] }" - let edits = settings.edits_for_update::(&text, |file| { - file.edit_predictions + let edits = settings.edits_for_update(&text, |file| { + file.project + .all_languages + .edit_predictions .get_or_insert_with(Default::default) .disabled_globs .get_or_insert_with(Vec::new); @@ -948,9 +950,12 @@ async fn open_disabled_globs_setting_in_editor( } fn set_completion_provider(fs: Arc, cx: &mut App, provider: EditPredictionProvider) { - update_settings_file::(fs, cx, move |file, _| { - file.features - .get_or_insert(Default::default()) + update_settings_file(fs, cx, move |settings, _| { + settings + .project + .all_languages + .features + .get_or_insert_default() .edit_prediction_provider = Some(provider); }); } @@ -962,18 +967,24 @@ fn toggle_show_edit_predictions_for_language( ) { let show_edit_predictions = all_language_settings(None, cx).show_edit_predictions(Some(&language), cx); - update_settings_file::(fs, cx, move |file, _| { - file.languages + update_settings_file(fs, cx, move |settings, _| { + settings + .project + .all_languages + .languages .0 - .entry(language.name()) + .entry(language.name().0) .or_default() .show_edit_predictions = Some(!show_edit_predictions); }); } fn hide_copilot(fs: Arc, cx: &mut App) { - update_settings_file::(fs, cx, move |file, _| { - file.features + update_settings_file(fs, cx, move |settings, _| { + settings + .project + .all_languages + .features .get_or_insert(Default::default()) .edit_prediction_provider = Some(EditPredictionProvider::None); }); @@ -984,13 +995,14 @@ fn toggle_edit_prediction_mode(fs: Arc, mode: EditPredictionsMode, cx: & let current_mode = settings.edit_predictions_mode(); if current_mode != mode { - update_settings_file::(fs, cx, move |settings, _cx| { - if let Some(edit_predictions) = settings.edit_predictions.as_mut() { - edit_predictions.mode = mode; + update_settings_file(fs, cx, move |settings, _cx| { + if let Some(edit_predictions) = settings.project.all_languages.edit_predictions.as_mut() + { + edit_predictions.mode = Some(mode); } else { - settings.edit_predictions = - Some(language_settings::EditPredictionSettingsContent { - mode, + settings.project.all_languages.edit_predictions = + Some(settings::EditPredictionSettingsContent { + mode: Some(mode), ..Default::default() }); } diff --git a/crates/editor/src/code_completion_tests.rs b/crates/editor/src/code_completion_tests.rs index a1d9f04a9c590ef1f20779bf19c2fe0be8905709..ec97c0ebb31952da9ad8e9e6f4f75b4b0078c4a3 100644 --- a/crates/editor/src/code_completion_tests.rs +++ b/crates/editor/src/code_completion_tests.rs @@ -1,9 +1,10 @@ -use crate::{code_context_menus::CompletionsMenu, editor_settings::SnippetSortOrder}; +use crate::code_context_menus::CompletionsMenu; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::TestAppContext; use language::CodeLabel; use lsp::{CompletionItem, CompletionItemKind, LanguageServerId}; use project::{Completion, CompletionSource}; +use settings::SnippetSortOrder; use std::sync::Arc; use std::sync::atomic::AtomicBool; use text::Anchor; diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 9384e2efed162e1a21b0b31fdc0e7f65ba25b130..a89125a3aa6aebe23665469f34962dbacddc52d6 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -32,7 +32,6 @@ use ui::{Color, IntoElement, ListItem, Pixels, Popover, Styled, prelude::*}; use util::ResultExt; use crate::CodeActionSource; -use crate::editor_settings::SnippetSortOrder; use crate::hover_popover::{hover_markdown_style, open_markdown_url}; use crate::{ CodeActionProvider, CompletionId, CompletionItemKind, CompletionProvider, DisplayRow, Editor, @@ -40,6 +39,7 @@ use crate::{ actions::{ConfirmCodeAction, ConfirmCompletion}, split_words, styled_runs_for_code_label, }; +use settings::SnippetSortOrder; pub const MENU_GAP: Pixels = px(4.); pub const MENU_ASIDE_X_PADDING: Pixels = px(16.); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 1acbdab7a6646fe46b9ad9d9cb09c1549d64bb1a..cc6bb3571bc11c836fa4d13abb76f6cd4a554755 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1529,12 +1529,11 @@ pub mod tests { use language::{ Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig, LanguageMatcher, - language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, }; use lsp::LanguageServerId; use project::Project; use rand::{Rng, prelude::*}; - use settings::SettingsStore; + use settings::{SettingsContent, SettingsStore}; use smol::stream::StreamExt; use std::{env, sync::Arc}; use text::PointUtf16; @@ -1564,7 +1563,9 @@ pub mod tests { log::info!("wrap width: {:?}", wrap_width); cx.update(|cx| { - init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size)); + init_test(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size) + }); }); let buffer = cx.update(|cx| { @@ -1623,8 +1624,9 @@ pub mod tests { log::info!("setting tab size to {:?}", tab_size); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |s| { - s.defaults.tab_size = NonZeroU32::new(tab_size); + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = + NonZeroU32::new(tab_size); }); }); }); @@ -2084,7 +2086,11 @@ pub mod tests { ); language.set_theme(&theme); - cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap()))); + cx.update(|cx| { + init_test(cx, |s| { + s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap()) + }) + }); let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx)); cx.condition(&buffer, |buf, _| !buf.is_parsing()).await; @@ -2910,7 +2916,7 @@ pub mod tests { chunks } - fn init_test(cx: &mut App, f: impl Fn(&mut AllLanguageSettingsContent)) { + fn init_test(cx: &mut App, f: impl Fn(&mut SettingsContent)) { let settings = SettingsStore::test(cx); cx.set_global(settings); workspace::init_settings(cx); @@ -2919,7 +2925,7 @@ pub mod tests { Project::init_settings(cx); theme::init(LoadThemes::JustBase, cx); cx.update_global::(|store, cx| { - store.update_user_settings::(cx, f); + store.update_user_settings(cx, f); }); } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8492ba60e7d302ab82509a3ece849ce59a6c2a2b..d8501f2104a00183be68dc461f9128a600227aa6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -160,9 +160,7 @@ use project::{ }, git_store::{GitStoreEvent, RepositoryEvent}, lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, - project_settings::{ - DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings, - }, + project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings}, }; use rand::seq::SliceRandom; use rpc::{ErrorCode, ErrorExt, proto::PeerId}; @@ -171,7 +169,7 @@ use selections_collection::{ MutableSelectionsCollection, SelectionsCollection, resolve_selections, }; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file}; +use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file}; use smallvec::{SmallVec, smallvec}; use snippet::Snippet; use std::{ @@ -2189,7 +2187,7 @@ impl Editor { show_selection_menu: None, show_git_blame_inline_delay_task: None, git_blame_inline_enabled: full_mode - && ProjectSettings::get_global(cx).git.inline_blame_enabled(), + && ProjectSettings::get_global(cx).git.inline_blame.enabled, render_diff_hunk_controls: Arc::new(render_diff_hunk_controls), serialize_dirty_buffers: !is_minimap && ProjectSettings::get_global(cx) @@ -5591,8 +5589,9 @@ impl Editor { .language_at(buffer_position) .map(|language| language.name()); - let completion_settings = - language_settings(language.clone(), buffer_snapshot.file(), cx).completions; + let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx) + .completions + .clone(); let show_completion_documentation = buffer_snapshot .settings_at(buffer_position, cx) @@ -19008,8 +19007,8 @@ impl Editor { }; let fs = workspace.read(cx).app_state().fs.clone(); let current_show = TabBarSettings::get_global(cx).show; - update_settings_file::(fs, cx, move |setting, _| { - setting.show = Some(!current_show); + update_settings_file(fs, cx, move |setting, _| { + setting.tab_bar.get_or_insert_default().show = Some(!current_show); }); } @@ -20723,7 +20722,7 @@ impl Editor { if self.mode.is_full() { let show_inline_diagnostics = project_settings.diagnostics.inline.enabled; - let inline_blame_enabled = project_settings.git.inline_blame_enabled(); + let inline_blame_enabled = project_settings.git.inline_blame.enabled; if self.show_inline_diagnostics != show_inline_diagnostics { self.show_inline_diagnostics = show_inline_diagnostics; self.refresh_inline_diagnostics(false, window, cx); @@ -21679,11 +21678,12 @@ impl Editor { } } +// todo(settings_refactor) this should not be! fn vim_enabled(cx: &App) -> bool { cx.global::() .raw_user_settings() - .get("vim_mode") - == Some(&serde_json::Value::Bool(true)) + .and_then(|settings| settings.content.vim_mode) + == Some(true) } fn process_completion_for_edit( @@ -23123,7 +23123,7 @@ impl EditorSnapshot { let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| { matches!( ProjectSettings::get_global(cx).git.git_gutter, - Some(GitGutterSetting::TrackedFiles) + GitGutterSetting::TrackedFiles ) }); let gutter_settings = EditorSettings::get_global(cx).gutter; diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 09f496637498b2535f8836282ffed4ea30950a4f..a6893a10791b1e5ce19d4ad484327d6f16539a74 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -4,15 +4,19 @@ use std::num::NonZeroU32; use gpui::App; use language::CursorShape; use project::project_settings::DiagnosticSeverity; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi, VsCodeSettings}; +pub use settings::{ + CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer, + GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier, + ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder, + VsCodeSettings, +}; +use settings::{Settings, SettingsContent}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; -use util::serde::default_true; +use util::MergeFrom; /// Imports from the VSCode settings at /// https://code.visualstudio.com/docs/reference/default-settings -#[derive(Deserialize, Clone)] +#[derive(Clone)] pub struct EditorSettings { pub cursor_blink: bool, pub cursor_shape: Option, @@ -41,83 +45,22 @@ pub struct EditorSettings { pub expand_excerpt_lines: u32, pub excerpt_context_lines: u32, pub middle_click_paste: bool, - #[serde(default)] pub double_click_in_multibuffer: DoubleClickInMultibuffer, pub search_wrap: bool, - #[serde(default)] pub search: SearchSettings, pub auto_signature_help: bool, pub show_signature_help_after_edits: bool, - #[serde(default)] pub go_to_definition_fallback: GoToDefinitionFallback, pub jupyter: Jupyter, pub hide_mouse: Option, pub snippet_sort_order: SnippetSortOrder, - #[serde(default)] pub diagnostics_max_severity: Option, pub inline_code_actions: bool, pub drag_and_drop_selection: DragAndDropSelection, pub lsp_document_colors: DocumentColorsRenderMode, pub minimum_contrast_for_highlights: f32, } - -/// How to render LSP `textDocument/documentColor` colors in the editor. -#[derive( - Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum DocumentColorsRenderMode { - /// Do not query and render document colors. - None, - /// Render document colors as inlay hints near the color text. - #[default] - Inlay, - /// Draw a border around the color text. - Border, - /// Draw a background behind the color text. - Background, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum CurrentLineHighlight { - // Don't highlight the current line. - None, - // Highlight the gutter area. - Gutter, - // Highlight the editor area. - Line, - // Highlight the full line. - All, -} - -/// When to populate a new search's query based on the text under the cursor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum SeedQuerySetting { - /// Always populate the search query with the word under the cursor. - Always, - /// Only populate the search query when there is text selected. - Selection, - /// Never populate the search query - Never, -} - -/// What to do when multibuffer is double clicked in some of its excerpts (parts of singleton buffers). -#[derive( - Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum DoubleClickInMultibuffer { - /// Behave as a regular buffer and select the whole word. - #[default] - Select, - /// Open the excerpt clicked as a new buffer in the new tab, if no `alt` modifier was pressed during double click. - /// Otherwise, behave as a regular buffer and select the whole word. - Open, -} - -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone)] pub struct Jupyter { /// Whether the Jupyter feature is enabled. /// @@ -125,18 +68,7 @@ pub struct Jupyter { pub enabled: bool, } -#[derive( - Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub struct JupyterContent { - /// Whether the Jupyter feature is enabled. - /// - /// Default: true - pub enabled: Option, -} - -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct StatusBar { /// Whether to display the active language button in the status bar. /// @@ -148,7 +80,7 @@ pub struct StatusBar { pub cursor_position_button: bool, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Toolbar { pub breadcrumbs: bool, pub quick_actions: bool, @@ -157,7 +89,7 @@ pub struct Toolbar { pub code_actions: bool, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Scrollbar { pub show: ShowScrollbar, pub git_diff: bool, @@ -169,7 +101,7 @@ pub struct Scrollbar { pub axes: ScrollbarAxes, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct Minimap { pub show: ShowMinimap, pub display_in: DisplayIn, @@ -197,7 +129,7 @@ impl Minimap { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Gutter { pub min_line_number_digits: usize, pub line_numbers: bool, @@ -206,69 +138,8 @@ pub struct Gutter { pub folds: bool, } -/// When to show the minimap in the editor. -/// -/// Default: never -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ShowMinimap { - /// Follow the visibility of the scrollbar. - Auto, - /// Always show the minimap. - Always, - /// Never show the minimap. - #[default] - Never, -} - -/// Where to show the minimap in the editor. -/// -/// Default: all_editors -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum DisplayIn { - /// Show on all open editors. - AllEditors, - /// Show the minimap on the active editor only. - #[default] - ActiveEditor, -} - -/// When to show the minimap thumb. -/// -/// Default: always -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum MinimapThumb { - /// Show the minimap thumb only when the mouse is hovering over the minimap. - Hover, - /// Always show the minimap thumb. - #[default] - Always, -} - -/// Defines the border style for the minimap's scrollbar thumb. -/// -/// Default: left_open -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum MinimapThumbBorder { - /// Displays a border on all sides of the thumb. - Full, - /// Displays a border on all sides except the left side of the thumb. - #[default] - LeftOpen, - /// Displays a border on all sides except the right side of the thumb. - RightOpen, - /// Displays a border only on the left side of the thumb. - LeftOnly, - /// Displays the thumb without any border. - None, -} - /// Forcefully enable or disable the scrollbar for each axis -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ScrollbarAxes { /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. /// @@ -282,480 +153,30 @@ pub struct ScrollbarAxes { } /// Whether to allow drag and drop text selection in buffer. -#[derive( - Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi, -)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct DragAndDropSelection { /// When true, enables drag and drop text selection in buffer. /// /// Default: true - #[serde(default = "default_true")] pub enabled: bool, /// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created. /// /// Default: 300 - #[serde(default = "default_drag_and_drop_selection_delay_ms")] pub delay: u64, } -fn default_drag_and_drop_selection_delay_ms() -> u64 { - 300 -} - -/// Which diagnostic indicators to show in the scrollbar. -/// -/// Default: all -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum ScrollbarDiagnostics { - /// Show all diagnostic levels: hint, information, warnings, error. - All, - /// Show only the following diagnostic levels: information, warning, error. - Information, - /// Show only the following diagnostic levels: warning, error. - Warning, - /// Show only the following diagnostic level: error. - Error, - /// Do not show diagnostics. - None, -} - -/// The key to use for adding multiple cursors -/// -/// Default: alt -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum MultiCursorModifier { - Alt, - #[serde(alias = "cmd", alias = "ctrl")] - CmdOrCtrl, -} - -/// Whether the editor will scroll beyond the last line. -/// -/// Default: one_page -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum ScrollBeyondLastLine { - /// The editor will not scroll beyond the last line. - Off, - - /// The editor will scroll beyond the last line by one page. - OnePage, - - /// The editor will scroll beyond the last line by the same number of lines as vertical_scroll_margin. - VerticalScrollMargin, -} - /// Default options for buffer and project search items. -#[derive( - Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi, -)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct SearchSettings { /// Whether to show the project search button in the status bar. - #[serde(default = "default_true")] pub button: bool, - #[serde(default)] pub whole_word: bool, - #[serde(default)] pub case_sensitive: bool, - #[serde(default)] pub include_ignored: bool, - #[serde(default)] pub regex: bool, } -/// What to do when go to definition yields no results. -#[derive( - Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum GoToDefinitionFallback { - /// Disables the fallback. - None, - /// Looks up references of the same symbol instead. - #[default] - FindAllReferences, -} - -/// Determines when the mouse cursor should be hidden in an editor or input box. -/// -/// Default: on_typing_and_movement -#[derive( - Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum HideMouseMode { - /// Never hide the mouse cursor - Never, - /// Hide only when typing - OnTyping, - /// Hide on both typing and cursor movement - #[default] - OnTypingAndMovement, -} - -/// Determines how snippets are sorted relative to other completion items. -/// -/// Default: inline -#[derive( - Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum SnippetSortOrder { - /// Place snippets at the top of the completion list - Top, - /// Sort snippets normally using the default comparison logic - #[default] - Inline, - /// Place snippets at the bottom of the completion list - Bottom, - /// Do not show snippets in the completion list - None, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_ui(group = "Editor")] -#[settings_key(None)] -pub struct EditorSettingsContent { - /// Whether the cursor blinks in the editor. - /// - /// Default: true - pub cursor_blink: Option, - /// Cursor shape for the default editor. - /// Can be "bar", "block", "underline", or "hollow". - /// - /// Default: bar - pub cursor_shape: Option, - /// Determines when the mouse cursor should be hidden in an editor or input box. - /// - /// Default: on_typing_and_movement - pub hide_mouse: Option, - /// Determines how snippets are sorted relative to other completion items. - /// - /// Default: inline - pub snippet_sort_order: Option, - /// How to highlight the current line in the editor. - /// - /// Default: all - pub current_line_highlight: Option, - /// Whether to highlight all occurrences of the selected text in an editor. - /// - /// Default: true - pub selection_highlight: Option, - /// Whether the text selection should have rounded corners. - /// - /// Default: true - pub rounded_selection: Option, - /// The debounce delay before querying highlights from the language - /// server based on the current cursor location. - /// - /// Default: 75 - pub lsp_highlight_debounce: Option, - /// Whether to show the informational hover box when moving the mouse - /// over symbols in the editor. - /// - /// Default: true - pub hover_popover_enabled: Option, - /// Time to wait in milliseconds before showing the informational hover box. - /// - /// Default: 300 - pub hover_popover_delay: Option, - /// Status bar related settings - pub status_bar: Option, - /// Toolbar related settings - pub toolbar: Option, - /// Scrollbar related settings - pub scrollbar: Option, - /// Minimap related settings - pub minimap: Option, - /// Gutter related settings - pub gutter: Option, - /// Whether the editor will scroll beyond the last line. - /// - /// Default: one_page - pub scroll_beyond_last_line: Option, - /// The number of lines to keep above/below the cursor when auto-scrolling. - /// - /// Default: 3. - pub vertical_scroll_margin: Option, - /// Whether to scroll when clicking near the edge of the visible text area. - /// - /// Default: false - pub autoscroll_on_clicks: Option, - /// The number of characters to keep on either side when scrolling with the mouse. - /// - /// Default: 5. - pub horizontal_scroll_margin: Option, - /// Scroll sensitivity multiplier. This multiplier is applied - /// to both the horizontal and vertical delta values while scrolling. - /// - /// Default: 1.0 - pub scroll_sensitivity: Option, - /// Scroll sensitivity multiplier for fast scrolling. This multiplier is applied - /// to both the horizontal and vertical delta values while scrolling. Fast scrolling - /// happens when a user holds the alt or option key while scrolling. - /// - /// Default: 4.0 - pub fast_scroll_sensitivity: Option, - /// Whether the line numbers on editors gutter are relative or not. - /// - /// Default: false - pub relative_line_numbers: Option, - /// When to populate a new search's query based on the text under the cursor. - /// - /// Default: always - pub seed_search_query_from_cursor: Option, - pub use_smartcase_search: Option, - /// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier. - /// - /// Default: alt - pub multi_cursor_modifier: Option, - /// Hide the values of variables in `private` files, as defined by the - /// private_files setting. This only changes the visual representation, - /// the values are still present in the file and can be selected / copied / pasted - /// - /// Default: false - pub redact_private_values: Option, - - /// How many lines to expand the multibuffer excerpts by default - /// - /// Default: 3 - pub expand_excerpt_lines: Option, - - /// How many lines of context to provide in multibuffer excerpts by default - /// - /// Default: 2 - pub excerpt_context_lines: Option, - - /// Whether to enable middle-click paste on Linux - /// - /// Default: true - pub middle_click_paste: Option, - - /// What to do when multibuffer is double clicked in some of its excerpts - /// (parts of singleton buffers). - /// - /// Default: select - pub double_click_in_multibuffer: Option, - /// Whether the editor search results will loop - /// - /// Default: true - pub search_wrap: Option, - - /// Defaults to use when opening a new buffer and project search items. - /// - /// Default: nothing is enabled - pub search: Option, - - /// Whether to automatically show a signature help pop-up or not. - /// - /// Default: false - pub auto_signature_help: Option, - - /// Whether to show the signature help pop-up after completions or bracket pairs inserted. - /// - /// Default: false - pub show_signature_help_after_edits: Option, - /// The minimum APCA perceptual contrast to maintain when - /// rendering text over highlight backgrounds in the editor. - /// - /// Values range from 0 to 106. Set to 0 to disable adjustments. - /// Default: 45 - pub minimum_contrast_for_highlights: Option, - - /// Whether to follow-up empty go to definition responses from the language server or not. - /// `FindAllReferences` allows to look up references of the same symbol instead. - /// `None` disables the fallback. - /// - /// Default: FindAllReferences - pub go_to_definition_fallback: Option, - - /// Jupyter REPL settings. - pub jupyter: Option, - - /// Which level to use to filter out diagnostics displayed in the editor. - /// - /// Affects the editor rendering only, and does not interrupt - /// the functionality of diagnostics fetching and project diagnostics editor. - /// Which files containing diagnostic errors/warnings to mark in the tabs. - /// Diagnostics are only shown when file icons are also active. - /// - /// Shows all diagnostics if not specified. - /// - /// Default: warning - #[serde(default)] - pub diagnostics_max_severity: Option, - - /// Whether to show code action button at start of buffer line. - /// - /// Default: true - pub inline_code_actions: Option, - - /// Drag and drop related settings - pub drag_and_drop_selection: Option, - - /// How to render LSP `textDocument/documentColor` colors in the editor. - /// - /// Default: [`DocumentColorsRenderMode::Inlay`] - pub lsp_document_colors: Option, -} - -// Status bar related settings -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)] -pub struct StatusBarContent { - /// Whether to display the active language button in the status bar. - /// - /// Default: true - pub active_language_button: Option, - /// Whether to show the cursor position button in the status bar. - /// - /// Default: true - pub cursor_position_button: Option, -} - -// Toolbar related settings -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)] -pub struct ToolbarContent { - /// Whether to display breadcrumbs in the editor toolbar. - /// - /// Default: true - pub breadcrumbs: Option, - /// Whether to display quick action buttons in the editor toolbar. - /// - /// Default: true - pub quick_actions: Option, - /// Whether to show the selections menu in the editor toolbar. - /// - /// Default: true - pub selections_menu: Option, - /// Whether to display Agent review buttons in the editor toolbar. - /// Only applicable while reviewing a file edited by the Agent. - /// - /// Default: true - pub agent_review: Option, - /// Whether to display code action buttons in the editor toolbar. - /// - /// Default: false - pub code_actions: Option, -} - -/// Scrollbar related settings -#[derive( - Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default, SettingsUi, -)] -pub struct ScrollbarContent { - /// When to show the scrollbar in the editor. - /// - /// Default: auto - pub show: Option, - /// Whether to show git diff indicators in the scrollbar. - /// - /// Default: true - pub git_diff: Option, - /// Whether to show buffer search result indicators in the scrollbar. - /// - /// Default: true - pub search_results: Option, - /// Whether to show selected text occurrences in the scrollbar. - /// - /// Default: true - pub selected_text: Option, - /// Whether to show selected symbol occurrences in the scrollbar. - /// - /// Default: true - pub selected_symbol: Option, - /// Which diagnostic indicators to show in the scrollbar: - /// - /// Default: all - pub diagnostics: Option, - /// Whether to show cursor positions in the scrollbar. - /// - /// Default: true - pub cursors: Option, - /// Forcefully enable or disable the scrollbar for each axis - pub axes: Option, -} - -/// Minimap related settings -#[derive( - Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, SettingsUi, -)] -pub struct MinimapContent { - /// When to show the minimap in the editor. - /// - /// Default: never - pub show: Option, - - /// Where to show the minimap in the editor. - /// - /// Default: [`DisplayIn::ActiveEditor`] - pub display_in: Option, - - /// When to show the minimap thumb. - /// - /// Default: always - pub thumb: Option, - - /// Defines the border style for the minimap's scrollbar thumb. - /// - /// Default: left_open - pub thumb_border: Option, - - /// How to highlight the current line in the minimap. - /// - /// Default: inherits editor line highlights setting - pub current_line_highlight: Option>, - - /// Maximum number of columns to display in the minimap. - /// - /// Default: 80 - pub max_width_columns: Option, -} - -/// Forcefully enable or disable the scrollbar for each axis -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] -pub struct ScrollbarAxesContent { - /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. - /// - /// Default: true - horizontal: Option, - - /// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings. - /// - /// Default: true - vertical: Option, -} - -/// Gutter related settings -#[derive( - Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi, -)] -#[settings_ui(group = "Gutter")] -pub struct GutterContent { - /// Whether to show line numbers in the gutter. - /// - /// Default: true - pub line_numbers: Option, - /// Minimum number of characters to reserve space for in the gutter. - /// - /// Default: 4 - pub min_line_number_digits: Option, - /// Whether to show runnable buttons in the gutter. - /// - /// Default: true - pub runnables: Option, - /// Whether to show breakpoints in the gutter. - /// - /// Default: true - pub breakpoints: Option, - /// Whether to show fold buttons in the gutter. - /// - /// Default: true - pub folds: Option, -} - impl EditorSettings { pub fn jupyter_enabled(cx: &App) -> bool { EditorSettings::get_global(cx).jupyter.enabled @@ -769,16 +190,266 @@ impl ScrollbarVisibility for EditorSettings { } impl Settings for EditorSettings { - type FileContent = EditorSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let editor = content.editor.clone(); + let scrollbar = editor.scrollbar.unwrap(); + let minimap = editor.minimap.unwrap(); + let gutter = editor.gutter.unwrap(); + let axes = scrollbar.axes.unwrap(); + let status_bar = editor.status_bar.unwrap(); + let toolbar = editor.toolbar.unwrap(); + let search = editor.search.unwrap(); + let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap(); + Self { + cursor_blink: editor.cursor_blink.unwrap(), + cursor_shape: editor.cursor_shape.map(Into::into), + current_line_highlight: editor.current_line_highlight.unwrap(), + selection_highlight: editor.selection_highlight.unwrap(), + rounded_selection: editor.rounded_selection.unwrap(), + lsp_highlight_debounce: editor.lsp_highlight_debounce.unwrap(), + hover_popover_enabled: editor.hover_popover_enabled.unwrap(), + hover_popover_delay: editor.hover_popover_delay.unwrap(), + status_bar: StatusBar { + active_language_button: status_bar.active_language_button.unwrap(), + cursor_position_button: status_bar.cursor_position_button.unwrap(), + }, + toolbar: Toolbar { + breadcrumbs: toolbar.breadcrumbs.unwrap(), + quick_actions: toolbar.quick_actions.unwrap(), + selections_menu: toolbar.selections_menu.unwrap(), + agent_review: toolbar.agent_review.unwrap(), + code_actions: toolbar.code_actions.unwrap(), + }, + scrollbar: Scrollbar { + show: scrollbar.show.map(Into::into).unwrap(), + git_diff: scrollbar.git_diff.unwrap(), + selected_text: scrollbar.selected_text.unwrap(), + selected_symbol: scrollbar.selected_symbol.unwrap(), + search_results: scrollbar.search_results.unwrap(), + diagnostics: scrollbar.diagnostics.unwrap(), + cursors: scrollbar.cursors.unwrap(), + axes: ScrollbarAxes { + horizontal: axes.horizontal.unwrap(), + vertical: axes.vertical.unwrap(), + }, + }, + minimap: Minimap { + show: minimap.show.unwrap(), + display_in: minimap.display_in.unwrap(), + thumb: minimap.thumb.unwrap(), + thumb_border: minimap.thumb_border.unwrap(), + current_line_highlight: minimap.current_line_highlight.flatten(), + max_width_columns: minimap.max_width_columns.unwrap(), + }, + gutter: Gutter { + min_line_number_digits: gutter.min_line_number_digits.unwrap(), + line_numbers: gutter.line_numbers.unwrap(), + runnables: gutter.runnables.unwrap(), + breakpoints: gutter.breakpoints.unwrap(), + folds: gutter.folds.unwrap(), + }, + scroll_beyond_last_line: editor.scroll_beyond_last_line.unwrap(), + vertical_scroll_margin: editor.vertical_scroll_margin.unwrap(), + autoscroll_on_clicks: editor.autoscroll_on_clicks.unwrap(), + horizontal_scroll_margin: editor.horizontal_scroll_margin.unwrap(), + scroll_sensitivity: editor.scroll_sensitivity.unwrap(), + fast_scroll_sensitivity: editor.fast_scroll_sensitivity.unwrap(), + relative_line_numbers: editor.relative_line_numbers.unwrap(), + seed_search_query_from_cursor: editor.seed_search_query_from_cursor.unwrap(), + use_smartcase_search: editor.use_smartcase_search.unwrap(), + multi_cursor_modifier: editor.multi_cursor_modifier.unwrap(), + redact_private_values: editor.redact_private_values.unwrap(), + expand_excerpt_lines: editor.expand_excerpt_lines.unwrap(), + excerpt_context_lines: editor.excerpt_context_lines.unwrap(), + middle_click_paste: editor.middle_click_paste.unwrap(), + double_click_in_multibuffer: editor.double_click_in_multibuffer.unwrap(), + search_wrap: editor.search_wrap.unwrap(), + search: SearchSettings { + button: search.button.unwrap(), + whole_word: search.whole_word.unwrap(), + case_sensitive: search.case_sensitive.unwrap(), + include_ignored: search.include_ignored.unwrap(), + regex: search.regex.unwrap(), + }, + auto_signature_help: editor.auto_signature_help.unwrap(), + show_signature_help_after_edits: editor.show_signature_help_after_edits.unwrap(), + go_to_definition_fallback: editor.go_to_definition_fallback.unwrap(), + jupyter: Jupyter { + enabled: editor.jupyter.unwrap().enabled.unwrap(), + }, + hide_mouse: editor.hide_mouse, + snippet_sort_order: editor.snippet_sort_order.unwrap(), + diagnostics_max_severity: editor.diagnostics_max_severity.map(Into::into), + inline_code_actions: editor.inline_code_actions.unwrap(), + drag_and_drop_selection: DragAndDropSelection { + enabled: drag_and_drop_selection.enabled.unwrap(), + delay: drag_and_drop_selection.delay.unwrap(), + }, + lsp_document_colors: editor.lsp_document_colors.unwrap(), + minimum_contrast_for_highlights: editor.minimum_contrast_for_highlights.unwrap(), + } + } - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let editor = &content.editor; + self.cursor_blink.merge_from(&editor.cursor_blink); + if let Some(cursor_shape) = editor.cursor_shape { + self.cursor_shape = Some(cursor_shape.into()) + } + self.current_line_highlight + .merge_from(&editor.current_line_highlight); + self.selection_highlight + .merge_from(&editor.selection_highlight); + self.rounded_selection.merge_from(&editor.rounded_selection); + self.lsp_highlight_debounce + .merge_from(&editor.lsp_highlight_debounce); + self.hover_popover_enabled + .merge_from(&editor.hover_popover_enabled); + self.hover_popover_delay + .merge_from(&editor.hover_popover_delay); + self.scroll_beyond_last_line + .merge_from(&editor.scroll_beyond_last_line); + self.vertical_scroll_margin + .merge_from(&editor.vertical_scroll_margin); + self.autoscroll_on_clicks + .merge_from(&editor.autoscroll_on_clicks); + self.horizontal_scroll_margin + .merge_from(&editor.horizontal_scroll_margin); + self.scroll_sensitivity + .merge_from(&editor.scroll_sensitivity); + self.fast_scroll_sensitivity + .merge_from(&editor.fast_scroll_sensitivity); + self.relative_line_numbers + .merge_from(&editor.relative_line_numbers); + self.seed_search_query_from_cursor + .merge_from(&editor.seed_search_query_from_cursor); + self.use_smartcase_search + .merge_from(&editor.use_smartcase_search); + self.multi_cursor_modifier + .merge_from(&editor.multi_cursor_modifier); + self.redact_private_values + .merge_from(&editor.redact_private_values); + self.expand_excerpt_lines + .merge_from(&editor.expand_excerpt_lines); + self.excerpt_context_lines + .merge_from(&editor.excerpt_context_lines); + self.middle_click_paste + .merge_from(&editor.middle_click_paste); + self.double_click_in_multibuffer + .merge_from(&editor.double_click_in_multibuffer); + self.search_wrap.merge_from(&editor.search_wrap); + self.auto_signature_help + .merge_from(&editor.auto_signature_help); + self.show_signature_help_after_edits + .merge_from(&editor.show_signature_help_after_edits); + self.go_to_definition_fallback + .merge_from(&editor.go_to_definition_fallback); + if let Some(hide_mouse) = editor.hide_mouse { + self.hide_mouse = Some(hide_mouse) + } + self.snippet_sort_order + .merge_from(&editor.snippet_sort_order); + if let Some(diagnostics_max_severity) = editor.diagnostics_max_severity { + self.diagnostics_max_severity = Some(diagnostics_max_severity.into()); + } + self.inline_code_actions + .merge_from(&editor.inline_code_actions); + self.lsp_document_colors + .merge_from(&editor.lsp_document_colors); + self.minimum_contrast_for_highlights + .merge_from(&editor.minimum_contrast_for_highlights); + + if let Some(status_bar) = &editor.status_bar { + self.status_bar + .active_language_button + .merge_from(&status_bar.active_language_button); + self.status_bar + .cursor_position_button + .merge_from(&status_bar.cursor_position_button); + } + if let Some(toolbar) = &editor.toolbar { + self.toolbar.breadcrumbs.merge_from(&toolbar.breadcrumbs); + self.toolbar + .quick_actions + .merge_from(&toolbar.quick_actions); + self.toolbar + .selections_menu + .merge_from(&toolbar.selections_menu); + self.toolbar.agent_review.merge_from(&toolbar.agent_review); + self.toolbar.code_actions.merge_from(&toolbar.code_actions); + } + if let Some(scrollbar) = &editor.scrollbar { + self.scrollbar + .show + .merge_from(&scrollbar.show.map(Into::into)); + self.scrollbar.git_diff.merge_from(&scrollbar.git_diff); + self.scrollbar + .selected_text + .merge_from(&scrollbar.selected_text); + self.scrollbar + .selected_symbol + .merge_from(&scrollbar.selected_symbol); + self.scrollbar + .search_results + .merge_from(&scrollbar.search_results); + self.scrollbar + .diagnostics + .merge_from(&scrollbar.diagnostics); + self.scrollbar.cursors.merge_from(&scrollbar.cursors); + if let Some(axes) = &scrollbar.axes { + self.scrollbar.axes.horizontal.merge_from(&axes.horizontal); + self.scrollbar.axes.vertical.merge_from(&axes.vertical); + } + } + if let Some(minimap) = &editor.minimap { + self.minimap.show.merge_from(&minimap.show); + self.minimap.display_in.merge_from(&minimap.display_in); + self.minimap.thumb.merge_from(&minimap.thumb); + self.minimap.thumb_border.merge_from(&minimap.thumb_border); + self.minimap + .current_line_highlight + .merge_from(&minimap.current_line_highlight); + self.minimap + .max_width_columns + .merge_from(&minimap.max_width_columns); + } + if let Some(gutter) = &editor.gutter { + self.gutter + .min_line_number_digits + .merge_from(&gutter.min_line_number_digits); + self.gutter.line_numbers.merge_from(&gutter.line_numbers); + self.gutter.runnables.merge_from(&gutter.runnables); + self.gutter.breakpoints.merge_from(&gutter.breakpoints); + self.gutter.folds.merge_from(&gutter.folds); + } + if let Some(search) = &editor.search { + self.search.button.merge_from(&search.button); + self.search.whole_word.merge_from(&search.whole_word); + self.search + .case_sensitive + .merge_from(&search.case_sensitive); + self.search + .include_ignored + .merge_from(&search.include_ignored); + self.search.regex.merge_from(&search.regex); + } + if let Some(enabled) = editor.jupyter.as_ref().and_then(|jupyter| jupyter.enabled) { + self.jupyter.enabled = enabled; + } + if let Some(drag_and_drop_selection) = &editor.drag_and_drop_selection { + self.drag_and_drop_selection + .enabled + .merge_from(&drag_and_drop_selection.enabled); + self.drag_and_drop_selection + .delay + .merge_from(&drag_and_drop_selection.delay); + } } - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) { vscode.enum_setting( "editor.cursorBlinking", - &mut current.cursor_blink, + &mut current.editor.cursor_blink, |s| match s { "blink" | "phase" | "expand" | "smooth" => Some(true), "solid" => Some(false), @@ -787,19 +458,19 @@ impl Settings for EditorSettings { ); vscode.enum_setting( "editor.cursorStyle", - &mut current.cursor_shape, + &mut current.editor.cursor_shape, |s| match s { - "block" => Some(CursorShape::Block), - "block-outline" => Some(CursorShape::Hollow), - "line" | "line-thin" => Some(CursorShape::Bar), - "underline" | "underline-thin" => Some(CursorShape::Underline), + "block" => Some(settings::CursorShape::Block), + "block-outline" => Some(settings::CursorShape::Hollow), + "line" | "line-thin" => Some(settings::CursorShape::Bar), + "underline" | "underline-thin" => Some(settings::CursorShape::Underline), _ => None, }, ); vscode.enum_setting( "editor.renderLineHighlight", - &mut current.current_line_highlight, + &mut current.editor.current_line_highlight, |s| match s { "gutter" => Some(CurrentLineHighlight::Gutter), "line" => Some(CurrentLineHighlight::Line), @@ -810,13 +481,22 @@ impl Settings for EditorSettings { vscode.bool_setting( "editor.selectionHighlight", - &mut current.selection_highlight, + &mut current.editor.selection_highlight, + ); + vscode.bool_setting( + "editor.roundedSelection", + &mut current.editor.rounded_selection, + ); + vscode.bool_setting( + "editor.hover.enabled", + &mut current.editor.hover_popover_enabled, + ); + vscode.u64_setting( + "editor.hover.delay", + &mut current.editor.hover_popover_delay, ); - vscode.bool_setting("editor.roundedSelection", &mut current.rounded_selection); - vscode.bool_setting("editor.hover.enabled", &mut current.hover_popover_enabled); - vscode.u64_setting("editor.hover.delay", &mut current.hover_popover_delay); - let mut gutter = GutterContent::default(); + let mut gutter = settings::GutterContent::default(); vscode.enum_setting( "editor.showFoldingControls", &mut gutter.folds, @@ -835,25 +515,25 @@ impl Settings for EditorSettings { _ => None, }, ); - if let Some(old_gutter) = current.gutter.as_mut() { + if let Some(old_gutter) = current.editor.gutter.as_mut() { if gutter.folds.is_some() { old_gutter.folds = gutter.folds } 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 != settings::GutterContent::default() { + current.editor.gutter = Some(gutter) } if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") { - current.scroll_beyond_last_line = Some(if b { + current.editor.scroll_beyond_last_line = Some(if b { ScrollBeyondLastLine::OnePage } else { ScrollBeyondLastLine::Off }) } - let mut scrollbar_axes = ScrollbarAxesContent::default(); + let mut scrollbar_axes = settings::ScrollbarAxesContent::default(); vscode.enum_setting( "editor.scrollbar.horizontal", &mut scrollbar_axes.horizontal, @@ -873,8 +553,8 @@ impl Settings for EditorSettings { }, ); - if scrollbar_axes != ScrollbarAxesContent::default() { - let scrollbar_settings = current.scrollbar.get_or_insert_default(); + if scrollbar_axes != settings::ScrollbarAxesContent::default() { + let scrollbar_settings = current.editor.scrollbar.get_or_insert_default(); let axes_settings = scrollbar_settings.axes.get_or_insert_default(); if let Some(vertical) = scrollbar_axes.vertical { @@ -888,23 +568,23 @@ impl Settings for EditorSettings { // TODO: check if this does the int->float conversion? vscode.f32_setting( "editor.cursorSurroundingLines", - &mut current.vertical_scroll_margin, + &mut current.editor.vertical_scroll_margin, ); vscode.f32_setting( "editor.mouseWheelScrollSensitivity", - &mut current.scroll_sensitivity, + &mut current.editor.scroll_sensitivity, ); vscode.f32_setting( "editor.fastScrollSensitivity", - &mut current.fast_scroll_sensitivity, + &mut current.editor.fast_scroll_sensitivity, ); if Some("relative") == vscode.read_string("editor.lineNumbers") { - current.relative_line_numbers = Some(true); + current.editor.relative_line_numbers = Some(true); } vscode.enum_setting( "editor.find.seedSearchStringFromSelection", - &mut current.seed_search_query_from_cursor, + &mut current.editor.seed_search_query_from_cursor, |s| match s { "always" => Some(SeedQuerySetting::Always), "selection" => Some(SeedQuerySetting::Selection), @@ -912,10 +592,10 @@ impl Settings for EditorSettings { _ => None, }, ); - vscode.bool_setting("search.smartCase", &mut current.use_smartcase_search); + vscode.bool_setting("search.smartCase", &mut current.editor.use_smartcase_search); vscode.enum_setting( "editor.multiCursorModifier", - &mut current.multi_cursor_modifier, + &mut current.editor.multi_cursor_modifier, |s| match s { "ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl), "alt" => Some(MultiCursorModifier::Alt), @@ -925,19 +605,19 @@ impl Settings for EditorSettings { vscode.bool_setting( "editor.parameterHints.enabled", - &mut current.auto_signature_help, + &mut current.editor.auto_signature_help, ); vscode.bool_setting( "editor.parameterHints.enabled", - &mut current.show_signature_help_after_edits, + &mut current.editor.show_signature_help_after_edits, ); if let Some(use_ignored) = vscode.read_bool("search.useIgnoreFiles") { - let search = current.search.get_or_insert_default(); - search.include_ignored = use_ignored; + let search = current.editor.search.get_or_insert_default(); + search.include_ignored = Some(use_ignored); } - let mut minimap = MinimapContent::default(); + let mut minimap = settings::MinimapContent::default(); let minimap_enabled = vscode.read_bool("editor.minimap.enabled").unwrap_or(true); let autohide = vscode.read_bool("editor.minimap.autohide"); let mut max_width_columns: Option = None; @@ -965,8 +645,8 @@ impl Settings for EditorSettings { }, ); - if minimap != MinimapContent::default() { - current.minimap = Some(minimap) + if minimap != settings::MinimapContent::default() { + current.editor.minimap = Some(minimap) } } } diff --git a/crates/editor/src/editor_settings_controls.rs b/crates/editor/src/editor_settings_controls.rs index 91022d94a8843a2e9b7e9c77137d4d2ba57bfa7f..20393f6cbb0ddca9d8561c7023fd0d9e5e863a9d 100644 --- a/crates/editor/src/editor_settings_controls.rs +++ b/crates/editor/src/editor_settings_controls.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use gpui::{App, FontFeatures, FontWeight}; -use project::project_settings::{InlineBlameSettings, ProjectSettings}; -use settings::{EditableSettingControl, Settings}; +use project::project_settings::ProjectSettings; +use settings::{EditableSettingControl, Settings, SettingsContent}; use theme::{FontFamilyCache, FontFamilyName, ThemeSettings}; use ui::{ CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, @@ -59,7 +59,6 @@ struct BufferFontFamilyControl; impl EditableSettingControl for BufferFontFamilyControl { type Value = SharedString; - type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Family".into() @@ -70,12 +69,8 @@ impl EditableSettingControl for BufferFontFamilyControl { settings.buffer_font.family.clone() } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.buffer_font_family = Some(FontFamilyName(value.into())); + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { + settings.theme.buffer_font_family = Some(FontFamilyName(value.into())); } } @@ -118,7 +113,6 @@ struct BufferFontSizeControl; impl EditableSettingControl for BufferFontSizeControl { type Value = Pixels; - type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Size".into() @@ -128,12 +122,8 @@ impl EditableSettingControl for BufferFontSizeControl { ThemeSettings::get_global(cx).buffer_font_size(cx) } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.buffer_font_size = Some(value.into()); + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { + settings.theme.buffer_font_size = Some(value.into()); } } @@ -162,7 +152,6 @@ struct BufferFontWeightControl; impl EditableSettingControl for BufferFontWeightControl { type Value = FontWeight; - type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Weight".into() @@ -173,12 +162,8 @@ impl EditableSettingControl for BufferFontWeightControl { settings.buffer_font.weight } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.buffer_font_weight = Some(value.0); + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { + settings.theme.buffer_font_weight = Some(value.0); } } @@ -215,7 +200,6 @@ struct BufferFontLigaturesControl; impl EditableSettingControl for BufferFontLigaturesControl { type Value = bool; - type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Ligatures".into() @@ -230,14 +214,11 @@ impl EditableSettingControl for BufferFontLigaturesControl { .unwrap_or(true) } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { let value = if value { 1 } else { 0 }; let mut features = settings + .theme .buffer_font_features .as_ref() .map(|features| features.tag_value_list().to_vec()) @@ -249,7 +230,7 @@ impl EditableSettingControl for BufferFontLigaturesControl { features.push(("calt".into(), value)); } - settings.buffer_font_features = Some(FontFeatures(Arc::new(features))); + settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features))); } } @@ -279,7 +260,6 @@ struct InlineGitBlameControl; impl EditableSettingControl for InlineGitBlameControl { type Value = bool; - type Settings = ProjectSettings; fn name(&self) -> SharedString { "Inline Git Blame".into() @@ -287,22 +267,16 @@ impl EditableSettingControl for InlineGitBlameControl { fn read(cx: &App) -> Self::Value { let settings = ProjectSettings::get_global(cx); - settings.git.inline_blame_enabled() + settings.git.inline_blame.enabled } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - if let Some(inline_blame) = settings.git.inline_blame.as_mut() { - inline_blame.enabled = value; - } else { - settings.git.inline_blame = Some(InlineBlameSettings { - enabled: false, - ..Default::default() - }); - } + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { + settings + .git + .get_or_insert_default() + .inline_blame + .get_or_insert_default() + .enabled = Some(value) } } @@ -332,7 +306,6 @@ struct LineNumbersControl; impl EditableSettingControl for LineNumbersControl { type Value = bool; - type Settings = EditorSettings; fn name(&self) -> SharedString { "Line Numbers".into() @@ -343,19 +316,8 @@ impl EditableSettingControl for LineNumbersControl { settings.gutter.line_numbers } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - if let Some(gutter) = settings.gutter.as_mut() { - gutter.line_numbers = Some(value); - } else { - settings.gutter = Some(crate::editor_settings::GutterContent { - line_numbers: Some(value), - ..Default::default() - }); - } + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { + settings.editor.gutter.get_or_insert_default().line_numbers = Some(value); } } @@ -385,7 +347,6 @@ struct RelativeLineNumbersControl; impl EditableSettingControl for RelativeLineNumbersControl { type Value = bool; - type Settings = EditorSettings; fn name(&self) -> SharedString { "Relative Line Numbers".into() @@ -396,12 +357,8 @@ impl EditableSettingControl for RelativeLineNumbersControl { settings.relative_line_numbers } - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.relative_line_numbers = Some(value); + fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) { + settings.editor.relative_line_numbers = Some(value); } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a8be7e020c01671d6ed1eeb9e3968cba1252f6df..f18187d558f1cb90e137d06591ec5b2ecb7b1654 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -22,15 +22,15 @@ use indoc::indoc; use language::{ BracketPairConfig, Capability::ReadWrite, - DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, - LanguageName, Override, Point, + DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig, + LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point, language_settings::{ - AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList, - LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter, + CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode, + SelectedFormatter, }, tree_sitter_python, }; -use language_settings::{Formatter, IndentGuideSettings}; +use language_settings::Formatter; use lsp::CompletionParams; use multi_buffer::{IndentGuide, PathKey}; use parking_lot::Mutex; @@ -38,9 +38,13 @@ use pretty_assertions::{assert_eq, assert_ne}; use project::{ FakeFs, debugger::breakpoint_store::{BreakpointState, SourceBreakpoint}, - project_settings::{LspSettings, ProjectSettings}, + project_settings::LspSettings, }; use serde_json::{self, json}; +use settings::{ + AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring, + ProjectSettingsContent, +}; use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant}; use std::{ iter, @@ -11699,10 +11703,7 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) { update_test_language_settings(cx, |settings| { // Enable Prettier formatting for the same buffer, and ensure // LSP is called instead of Prettier. - settings.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() - }); + settings.defaults.prettier.get_or_insert_default().allowed = Some(true); }); let mut fake_servers = language_registry.register_fake_lsp( "Rust", @@ -12088,10 +12089,7 @@ async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) { Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()), ))); update_test_language_settings(cx, |settings| { - settings.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() - }); + settings.defaults.prettier.get_or_insert_default().allowed = Some(true); }); let mut fake_servers = language_registry.register_fake_lsp( "TypeScript", @@ -12402,8 +12400,8 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true( cx.update(|cx| { cx.update_global::(|settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(true); + settings.update_user_settings(cx, |settings| { + settings.editor.auto_signature_help = Some(true); }); }); }); @@ -12542,9 +12540,9 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestA cx.update(|cx| { cx.update_global::(|settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(false); - settings.show_signature_help_after_edits = Some(false); + settings.update_user_settings(cx, |settings| { + settings.editor.auto_signature_help = Some(false); + settings.editor.show_signature_help_after_edits = Some(false); }); }); }); @@ -12669,9 +12667,9 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestA // Ensure that signature_help is called when enabled afte edits cx.update(|_, cx| { cx.update_global::(|settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(false); - settings.show_signature_help_after_edits = Some(true); + settings.update_user_settings(cx, |settings| { + settings.editor.auto_signature_help = Some(false); + settings.editor.show_signature_help_after_edits = Some(true); }); }); }); @@ -12711,9 +12709,9 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestA // Ensure that signature_help is called when auto signature help override is enabled cx.update(|_, cx| { cx.update_global::(|settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(true); - settings.show_signature_help_after_edits = Some(false); + settings.update_user_settings(cx, |settings| { + settings.editor.auto_signature_help = Some(true); + settings.editor.show_signature_help_after_edits = Some(false); }); }); }); @@ -12755,8 +12753,8 @@ async fn test_signature_help(cx: &mut TestAppContext) { init_test(cx, |_| {}); cx.update(|cx| { cx.update_global::(|settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(true); + settings.update_user_settings(cx, |settings| { + settings.editor.auto_signature_help = Some(true); }); }); }); @@ -13346,12 +13344,11 @@ async fn test_completion_mode(cx: &mut TestAppContext) { ); update_test_language_settings(&mut cx, |settings| { - settings.defaults.completions = Some(CompletionSettings { - lsp_insert_mode, - words: WordsCompletionMode::Disabled, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 0, + settings.defaults.completions = Some(CompletionSettingsContent { + lsp_insert_mode: Some(lsp_insert_mode), + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), + ..Default::default() }); }); @@ -13406,13 +13403,12 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) let expected_with_replace_mode = "SubscriptionErrorˇ"; update_test_language_settings(&mut cx, |settings| { - settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Disabled, - words_min_length: 0, + settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), // set the opposite here to ensure that the action is overriding the default behavior - lsp_insert_mode: LspInsertMode::Insert, - lsp: true, - lsp_fetch_timeout_ms: 0, + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -13443,13 +13439,12 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) apply_additional_edits.await.unwrap(); update_test_language_settings(&mut cx, |settings| { - settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Disabled, - words_min_length: 0, + settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), // set the opposite here to ensure that the action is overriding the default behavior - lsp_insert_mode: LspInsertMode::Replace, - lsp: true, - lsp_fetch_timeout_ms: 0, + lsp_insert_mode: Some(LspInsertMode::Replace), + ..Default::default() }); }); @@ -14185,12 +14180,11 @@ async fn test_completion_reuse(cx: &mut TestAppContext) { async fn test_word_completion(cx: &mut TestAppContext) { let lsp_fetch_timeout_ms = 10; init_test(cx, |language_settings| { - language_settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Fallback, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 10, - lsp_insert_mode: LspInsertMode::Insert, + language_settings.defaults.completions = Some(CompletionSettingsContent { + words_min_length: Some(0), + lsp_fetch_timeout_ms: Some(10), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -14282,12 +14276,11 @@ async fn test_word_completion(cx: &mut TestAppContext) { #[gpui::test] async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) { init_test(cx, |language_settings| { - language_settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Enabled, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + language_settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Enabled), + words_min_length: Some(0), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -14346,12 +14339,11 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext #[gpui::test] async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) { init_test(cx, |language_settings| { - language_settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Disabled, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + language_settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -14420,12 +14412,11 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) { #[gpui::test] async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) { init_test(cx, |language_settings| { - language_settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Fallback, - words_min_length: 0, - lsp: false, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + language_settings.defaults.completions = Some(CompletionSettingsContent { + words_min_length: Some(0), + lsp: Some(false), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -14483,12 +14474,11 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) { #[gpui::test] async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) { init_test(cx, |language_settings| { - language_settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Enabled, - words_min_length: 3, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + language_settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Enabled), + words_min_length: Some(3), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -14553,12 +14543,11 @@ async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppCont #[gpui::test] async fn test_word_completions_disabled(cx: &mut TestAppContext) { init_test(cx, |language_settings| { - language_settings.defaults.completions = Some(CompletionSettings { - words: WordsCompletionMode::Enabled, - words_min_length: 0, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::Insert, + language_settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Enabled), + words_min_length: Some(0), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() }); }); @@ -17067,7 +17056,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon let _fake_server = fake_servers.next().await.unwrap(); update_test_language_settings(cx, |language_settings| { language_settings.languages.0.insert( - language_name.clone(), + language_name.clone().0, LanguageSettingsContent { tab_size: NonZeroU32::new(8), ..Default::default() @@ -17991,10 +17980,7 @@ async fn test_document_format_with_prettier(cx: &mut TestAppContext) { Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()), ))); update_test_language_settings(cx, |settings| { - settings.defaults.prettier = Some(PrettierSettings { - allowed: true, - ..PrettierSettings::default() - }); + settings.defaults.prettier.get_or_insert_default().allowed = Some(true); }); let test_plugin = "test_plugin"; @@ -19982,7 +19968,8 @@ fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) - enabled: true, line_width: 1, active_line_width: 1, - ..Default::default() + coloring: IndentGuideColoring::default(), + background_coloring: IndentGuideBackgroundColoring::default(), }, } } @@ -23672,8 +23659,8 @@ println!("5"); }); cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.restore_on_file_reopen = Some(false); + store.update_user_settings(cx, |s| { + s.workspace.restore_on_file_reopen = Some(false); }); }); editor.update_in(cx, |editor, window, cx| { @@ -23697,8 +23684,8 @@ println!("5"); assert!(pane.active_item().is_none()); }); cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.restore_on_file_reopen = Some(true); + store.update_user_settings(cx, |s| { + s.workspace.restore_on_file_reopen = Some(true); }); }); @@ -25120,18 +25107,18 @@ pub(crate) fn update_test_language_settings( ) { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, f); + store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages)); }); }); } pub(crate) fn update_test_project_settings( cx: &mut TestAppContext, - f: impl Fn(&mut ProjectSettings), + f: impl Fn(&mut ProjectSettingsContent), ) { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, f); + store.update_user_settings(cx, |settings| f(&mut settings.project)); }); }); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 37ae90d70b91017016e46b409f67c7000c2b0f91..3bc05ccd537710c34c1c4e8e6d63c26440360f2e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -51,9 +51,7 @@ use gpui::{ transparent_black, }; use itertools::Itertools; -use language::language_settings::{ - IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting, -}; +use language::{IndentGuideSettings, language_settings::ShowWhitespaceSetting}; use markdown::Markdown; use multi_buffer::{ Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint, @@ -63,9 +61,12 @@ use multi_buffer::{ use project::{ Entry, ProjectPath, debugger::breakpoint_store::{Breakpoint, BreakpointSessionState}, - project_settings::{GitGutterSetting, GitHunkStyleSetting, ProjectSettings}, + project_settings::ProjectSettings, +}; +use settings::{ + GitGutterSetting, GitHunkStyleSetting, IndentGuideBackgroundColoring, IndentGuideColoring, + Settings, }; -use settings::Settings; use smallvec::{SmallVec, smallvec}; use std::{ any::TypeId, @@ -2099,10 +2100,7 @@ impl EditorElement { .display_diff_hunks_for_rows(display_rows, folded_buffers) .map(|hunk| (hunk, None)) .collect::>(); - let git_gutter_setting = ProjectSettings::get_global(cx) - .git - .git_gutter - .unwrap_or_default(); + let git_gutter_setting = ProjectSettings::get_global(cx).git.git_gutter; if let GitGutterSetting::TrackedFiles = git_gutter_setting { for (hunk, hitbox) in &mut display_hunks { if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) { @@ -2454,11 +2452,7 @@ impl EditorElement { let padding = { const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.; - let mut padding = ProjectSettings::get_global(cx) - .git - .inline_blame - .unwrap_or_default() - .padding as f32; + let mut padding = ProjectSettings::get_global(cx).git.inline_blame.padding as f32; if let Some(edit_prediction) = editor.active_edit_prediction.as_ref() && let EditPrediction::Edit { @@ -2492,12 +2486,10 @@ impl EditorElement { let padded_line_end = line_end + padding; - let min_column_in_pixels = ProjectSettings::get_global(cx) - .git - .inline_blame - .map(|settings| settings.min_column) - .map(|col| self.column_pixels(col as usize, window)) - .unwrap_or(px(0.)); + let min_column_in_pixels = self.column_pixels( + ProjectSettings::get_global(cx).git.inline_blame.min_column as usize, + window, + ); let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels; cmp::max(padded_line_end, min_start) @@ -5673,7 +5665,7 @@ impl EditorElement { for indent_guide in indent_guides { let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth); - let settings = indent_guide.settings; + let settings = &indent_guide.settings; // TODO fixed for now, expose them through themes later const INDENT_AWARE_ALPHA: f32 = 0.2; @@ -6008,7 +6000,7 @@ impl EditorElement { .unwrap_or_else(|| { matches!( ProjectSettings::get_global(cx).git.git_gutter, - Some(GitGutterSetting::TrackedFiles) + GitGutterSetting::TrackedFiles ) }); if show_git_gutter { @@ -7303,10 +7295,10 @@ impl EditorElement { fn diff_hunk_hollow(status: DiffHunkStatus, cx: &mut App) -> bool { let unstaged = status.has_secondary_hunk(); - let unstaged_hollow = ProjectSettings::get_global(cx) - .git - .hunk_style - .is_some_and(|style| matches!(style, GitHunkStyleSetting::UnstagedHollow)); + let unstaged_hollow = matches!( + ProjectSettings::get_global(cx).git.hunk_style, + GitHunkStyleSetting::UnstagedHollow + ); unstaged == unstaged_hollow } @@ -8843,13 +8835,9 @@ impl Element for EditorElement { }) .flatten()?; let mut element = render_inline_blame_entry(blame_entry, style, cx)?; - let inline_blame_padding = ProjectSettings::get_global(cx) - .git - .inline_blame - .unwrap_or_default() - .padding - as f32 - * em_advance; + let inline_blame_padding = + ProjectSettings::get_global(cx).git.inline_blame.padding as f32 + * em_advance; Some( element .layout_as_root(AvailableSpace::min_size(), window, cx) diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index ba0b6f88683969aca3818a2795aa6b8454de3bb8..d5a3f17822ff7f0f2324414aeaa9819b8605f53b 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -931,8 +931,8 @@ mod tests { use futures::StreamExt; use gpui::Modifiers; use indoc::indoc; - use language::language_settings::InlayHintSettings; use lsp::request::{GotoDefinition, GotoTypeDefinition}; + use settings::InlayHintSettingsContent; use util::{assert_set_eq, path}; use workspace::item::Item; @@ -1280,15 +1280,15 @@ mod tests { #[gpui::test] async fn test_inlay_hover_links(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - enabled: true, - show_value_hints: false, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + enabled: Some(true), + show_value_hints: Some(false), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 51be0d234eca9c2e6b908c0aba6f3746b3eff460..1815029207cf485292277861cd8d18ed482d6077 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1004,8 +1004,8 @@ mod tests { use collections::BTreeSet; use gpui::App; use indoc::indoc; - use language::language_settings::InlayHintSettings; use markdown::parser::MarkdownEvent; + use settings::InlayHintSettingsContent; use smol::stream::StreamExt; use std::sync::atomic; use std::sync::atomic::AtomicUsize; @@ -1551,15 +1551,15 @@ mod tests { #[gpui::test] async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index c1b0a7640c155fff02f0b778e8996a9b68ea452e..59c52e4341a74e85236d99da4bf6ff1195266f68 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -20,12 +20,14 @@ use anyhow::Context as _; use clock::Global; use futures::future; use gpui::{AppContext as _, AsyncApp, Context, Entity, Task, Window}; -use language::{Buffer, BufferSnapshot, language_settings::InlayHintKind}; +use language::{ + Buffer, BufferSnapshot, + language_settings::{InlayHintKind, InlayHintSettings}, +}; use parking_lot::RwLock; use project::{InlayHint, ResolveState}; use collections::{HashMap, HashSet, hash_map}; -use language::language_settings::InlayHintSettings; use smol::lock::Semaphore; use sum_tree::Bias; use text::{BufferId, ToOffset, ToPoint}; @@ -1301,13 +1303,13 @@ pub mod tests { use futures::StreamExt; use gpui::{AppContext as _, Context, SemanticVersion, TestAppContext, WindowHandle}; use itertools::Itertools as _; - use language::{Capability, FakeLspAdapter, language_settings::AllLanguageSettingsContent}; + use language::{Capability, FakeLspAdapter}; use language::{Language, LanguageConfig, LanguageMatcher}; use lsp::FakeLanguageServer; use parking_lot::Mutex; use project::{FakeFs, Project}; use serde_json::json; - use settings::SettingsStore; + use settings::{AllLanguageSettingsContent, InlayHintSettingsContent, SettingsStore}; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use text::Point; use util::path; @@ -1318,15 +1320,17 @@ pub mod tests { async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), - show_other_hints: allowed_hint_kinds.contains(&None), - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(allowed_hint_kinds.contains(&Some(InlayHintKind::Type))), + show_parameter_hints: Some( + allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + ), + show_other_hints: Some(allowed_hint_kinds.contains(&None)), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -1428,15 +1432,15 @@ pub mod tests { #[gpui::test] async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -1535,15 +1539,15 @@ pub mod tests { #[gpui::test] async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -1765,15 +1769,17 @@ pub mod tests { async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), - show_other_hints: allowed_hint_kinds.contains(&None), - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(allowed_hint_kinds.contains(&Some(InlayHintKind::Type))), + show_parameter_hints: Some( + allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + ), + show_other_hints: Some(allowed_hint_kinds.contains(&None)), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -1926,16 +1932,19 @@ pub mod tests { ), ] { update_test_language_settings(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: new_allowed_hint_kinds - .contains(&Some(InlayHintKind::Parameter)), - show_other_hints: new_allowed_hint_kinds.contains(&None), - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some( + new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + ), + show_parameter_hints: Some( + new_allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + ), + show_other_hints: Some(new_allowed_hint_kinds.contains(&None)), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -1970,16 +1979,19 @@ pub mod tests { let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); update_test_language_settings(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: false, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: another_allowed_hint_kinds - .contains(&Some(InlayHintKind::Parameter)), - show_other_hints: another_allowed_hint_kinds.contains(&None), - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(false), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some( + another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + ), + show_parameter_hints: Some( + another_allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + ), + show_other_hints: Some(another_allowed_hint_kinds.contains(&None)), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -2027,16 +2039,19 @@ pub mod tests { let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); update_test_language_settings(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: final_allowed_hint_kinds - .contains(&Some(InlayHintKind::Parameter)), - show_other_hints: final_allowed_hint_kinds.contains(&None), - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some( + final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + ), + show_parameter_hints: Some( + final_allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + ), + show_other_hints: Some(final_allowed_hint_kinds.contains(&None)), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -2102,15 +2117,15 @@ pub mod tests { #[gpui::test] async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -2239,15 +2254,15 @@ pub mod tests { #[gpui::test(iterations = 10)] async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -2540,15 +2555,15 @@ pub mod tests { #[gpui::test] async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -2864,15 +2879,15 @@ pub mod tests { #[gpui::test] async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: false, - show_parameter_hints: false, - show_other_hints: false, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(false), + show_parameter_hints: Some(false), + show_other_hints: Some(false), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -3041,15 +3056,15 @@ pub mod tests { .unwrap(); update_test_language_settings(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -3074,15 +3089,15 @@ pub mod tests { #[gpui::test] async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -3167,15 +3182,15 @@ pub mod tests { #[gpui::test] async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: false, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(false), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -3244,15 +3259,15 @@ pub mod tests { .unwrap(); update_test_language_settings(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); @@ -3305,15 +3320,15 @@ pub mod tests { #[gpui::test] async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - show_value_hints: true, - enabled: true, - edit_debounce_ms: 0, - scroll_debounce_ms: 0, - show_type_hints: true, - show_parameter_hints: true, - show_other_hints: true, - show_background: false, + settings.defaults.inlay_hints = Some(InlayHintSettingsContent { + show_value_hints: Some(true), + enabled: Some(true), + edit_debounce_ms: Some(0), + scroll_debounce_ms: Some(0), + show_type_hints: Some(true), + show_parameter_hints: Some(true), + show_other_hints: Some(true), + show_background: Some(false), toggle_on_modifiers_press: None, }) }); diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index c1c6ccf2f549042a3defc84d8628f7f614244d44..0e32bc686ad98a45b83712841c13fffc07421acb 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -328,7 +328,7 @@ pub(crate) fn refresh_enabled_in_any_buffer( snapshot.file(), cx, ); - if language_settings.jsx_tag_auto_close.enabled { + if language_settings.jsx_tag_auto_close { found_enabled = true; } } @@ -406,7 +406,7 @@ pub(crate) fn handle_from( }; let language_settings = snapshot.settings_at(edit.new.end, cx); - if !language_settings.jsx_tag_auto_close.enabled { + if !language_settings.jsx_tag_auto_close { continue; } @@ -620,14 +620,17 @@ mod jsx_tag_autoclose_tests { use super::*; use gpui::{AppContext as _, TestAppContext}; - use language::language_settings::JsxTagAutoCloseSettings; use languages::language; use multi_buffer::ExcerptRange; use text::Selection; async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext { init_test(cx, |settings| { - settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true }); + settings + .defaults + .jsx_tag_auto_close + .get_or_insert_default() + .enabled = Some(true); }); let mut cx = EditorTestContext::new(cx).await; @@ -789,7 +792,11 @@ mod jsx_tag_autoclose_tests { #[gpui::test] async fn test_multibuffer(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true }); + settings + .defaults + .jsx_tag_auto_close + .get_or_insert_default() + .enabled = Some(true); }); let buffer_a = cx.new(|cx| { diff --git a/crates/eval/src/eval.rs b/crates/eval/src/eval.rs index 32399e26c020b315f948e7e9013c7000ef9bd899..40d8c14f4f7ddc441f31581951ee4d6c26376a04 100644 --- a/crates/eval/src/eval.rs +++ b/crates/eval/src/eval.rs @@ -340,10 +340,7 @@ pub fn init(cx: &mut App) -> Arc { release_channel::init(app_version, cx); gpui_tokio::init(cx); - let mut settings_store = SettingsStore::new(cx); - settings_store - .set_default_settings(settings::default_settings().as_ref(), cx) - .unwrap(); + let settings_store = SettingsStore::new(cx, &settings::default_settings()); cx.set_global(settings_store); client::init_settings(cx); diff --git a/crates/extension_host/Cargo.toml b/crates/extension_host/Cargo.toml index c933d253c65b525b29eb072ce6910514b15e5932..ba7f056866f41dbc61e7aea38dac8d8aca35979f 100644 --- a/crates/extension_host/Cargo.toml +++ b/crates/extension_host/Cargo.toml @@ -37,7 +37,6 @@ paths.workspace = true project.workspace = true remote.workspace = true release_channel.workspace = true -schemars.workspace = true semantic_version.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/extension_host/src/extension_settings.rs b/crates/extension_host/src/extension_settings.rs index fa5a613c55a76a0b5660b114d49acc17fcf79120..b9e1609b42d6e7fa2f4dab63486a77a13f29824f 100644 --- a/crates/extension_host/src/extension_settings.rs +++ b/crates/extension_host/src/extension_settings.rs @@ -1,13 +1,9 @@ -use anyhow::Result; use collections::HashMap; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; use std::sync::Arc; -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] +#[derive(Debug, Default, Clone)] pub struct ExtensionSettings { /// The extensions that should be automatically installed by Zed. /// @@ -15,9 +11,7 @@ pub struct ExtensionSettings { /// available out-of-the-box. /// /// Default: { "html": true } - #[serde(default)] pub auto_install_extensions: HashMap, bool>, - #[serde(default)] pub auto_update_extensions: HashMap, bool>, } @@ -39,18 +33,24 @@ impl ExtensionSettings { } impl Settings for ExtensionSettings { - type FileContent = Self; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + Self { + auto_install_extensions: content.extension.auto_install_extensions.clone(), + auto_update_extensions: content.extension.auto_update_extensions.clone(), + } + } - fn load(sources: SettingsSources, _cx: &mut App) -> Result { - SettingsSources::::json_merge_with( - [sources.default] - .into_iter() - .chain(sources.user) - .chain(sources.server), - ) + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + self.auto_install_extensions + .extend(content.extension.auto_install_extensions.clone()); + self.auto_update_extensions + .extend(content.extension.auto_update_extensions.clone()); } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) { + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { // settingsSync.ignoredExtensions controls autoupdate for vscode extensions, but we // don't have a mapping to zed-extensions. there's also extensions.autoCheckUpdates // and extensions.autoUpdate which are global switches, we don't support those yet diff --git a/crates/extensions_ui/src/extension_version_selector.rs b/crates/extensions_ui/src/extension_version_selector.rs index fe7a419fbe8001b99d5c3ebf16dfc38cda3fc713..d38c27375f6c32324d4832d308768af8473869eb 100644 --- a/crates/extensions_ui/src/extension_version_selector.rs +++ b/crates/extensions_ui/src/extension_version_selector.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::sync::Arc; use client::ExtensionMetadata; -use extension_host::{ExtensionSettings, ExtensionStore}; +use extension_host::ExtensionStore; use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; use gpui::{App, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, prelude::*}; @@ -183,10 +183,13 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate { let extension_id = extension_version.id.clone(); let version = extension_version.manifest.version.clone(); - update_settings_file::(self.fs.clone(), cx, { + update_settings_file(self.fs.clone(), cx, { let extension_id = extension_id.clone(); move |settings, _| { - settings.auto_update_extensions.insert(extension_id, false); + settings + .extension + .auto_update_extensions + .insert(extension_id, false); } }); diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index dab238dc8be1ae6f2243e94fc7c00ce363a79469..dc636d92cfc6badf5ff62513e58ebea9ad13a9b4 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -20,7 +20,7 @@ use gpui::{ use num_format::{Locale, ToFormattedString}; use project::DirectoryLister; use release_channel::ReleaseChannel; -use settings::Settings; +use settings::{Settings, SettingsContent}; use strum::IntoEnumIterator as _; use theme::ThemeSettings; use ui::{ @@ -1269,17 +1269,17 @@ impl ExtensionsPage { Label::new(message) } - fn update_settings( + fn update_settings( &mut self, selection: &ToggleState, cx: &mut Context, - callback: impl 'static + Send + Fn(&mut T::FileContent, bool), + callback: impl 'static + Send + Fn(&mut SettingsContent, bool), ) { if let Some(workspace) = self.workspace.upgrade() { let fs = workspace.read(cx).app_state().fs.clone(); let selection = *selection; - settings::update_settings_file::(fs, cx, move |settings, _| { + settings::update_settings_file(fs, cx, move |settings, _| { let value = match selection { ToggleState::Unselected => false, ToggleState::Selected => true, @@ -1340,11 +1340,9 @@ impl ExtensionsPage { }, cx.listener(move |this, selection, _, cx| { telemetry::event!("Vim Mode Toggled", source = "Feature Upsell"); - this.update_settings::( - selection, - cx, - |setting, value| setting.vim_mode = Some(value), - ); + this.update_settings(selection, cx, |setting, value| { + setting.vim_mode = Some(value) + }); }), )), Feature::LanguageBash => FeatureUpsell::new("Shell support is built-in to Zed!") diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 53cf0552f22a59c31ab2422d86eb8cbb76145908..eda01466f6dda2f90fbdbd9f92f3cf812b083026 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -347,16 +347,16 @@ impl FileFinder { }) } - pub fn modal_max_width(width_setting: Option, window: &mut Window) -> Pixels { + pub fn modal_max_width(width_setting: FileFinderWidth, window: &mut Window) -> Pixels { let window_width = window.viewport_size().width; let small_width = rems(34.).to_pixels(window.rem_size()); match width_setting { - None | Some(FileFinderWidth::Small) => small_width, - Some(FileFinderWidth::Full) => window_width, - Some(FileFinderWidth::XLarge) => (window_width - Pixels(512.)).max(small_width), - Some(FileFinderWidth::Large) => (window_width - Pixels(768.)).max(small_width), - Some(FileFinderWidth::Medium) => (window_width - Pixels(1024.)).max(small_width), + FileFinderWidth::Small => small_width, + FileFinderWidth::Full => window_width, + FileFinderWidth::XLarge => (window_width - Pixels(512.)).max(small_width), + FileFinderWidth::Large => (window_width - Pixels(768.)).max(small_width), + FileFinderWidth::Medium => (window_width - Pixels(1024.)).max(small_width), } } } diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 05f77ee55e79efb25a822d0aab4e051deeeafce7..3f974b7412d740dd30e96fdd61487ac8774daa0e 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -1,55 +1,41 @@ -use anyhow::Result; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; +use util::MergeFrom; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct FileFinderSettings { pub file_icons: bool, - pub modal_max_width: Option, + pub modal_max_width: FileFinderWidth, pub skip_focus_for_active_in_search: bool, pub include_ignored: Option, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "file_finder")] -pub struct FileFinderSettingsContent { - /// Whether to show file icons in the file finder. - /// - /// Default: true - pub file_icons: Option, - /// Determines how much space the file finder can take up in relation to the available window width. - /// - /// Default: small - pub modal_max_width: Option, - /// Determines whether the file finder should skip focus for the active file in search results. - /// - /// Default: true - pub skip_focus_for_active_in_search: Option, - /// Determines whether to show the git status in the file finder - /// - /// Default: true - pub git_status: Option, - /// Whether to use gitignored files when searching. - /// Only the file Zed had indexed will be used, not necessary all the gitignored files. - /// - /// Can accept 3 values: - /// * `Some(true)`: Use all gitignored files - /// * `Some(false)`: Use only the files Zed had indexed - /// * `None`: Be smart and search for ignored when called from a gitignored worktree - /// - /// Default: None - pub include_ignored: Option>, -} - impl Settings for FileFinderSettings { - type FileContent = FileFinderSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + let file_finder = content.file_finder.as_ref().unwrap(); - fn load(sources: SettingsSources, _: &mut gpui::App) -> Result { - sources.json_merge() + Self { + file_icons: file_finder.file_icons.unwrap(), + modal_max_width: file_finder.modal_max_width.unwrap().into(), + skip_focus_for_active_in_search: file_finder.skip_focus_for_active_in_search.unwrap(), + include_ignored: file_finder.include_ignored.flatten(), + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { + let Some(file_finder) = content.file_finder.as_ref() else { + return; + }; + + self.file_icons.merge_from(&file_finder.file_icons); + self.modal_max_width + .merge_from(&file_finder.modal_max_width.map(Into::into)); + self.skip_focus_for_active_in_search + .merge_from(&file_finder.skip_focus_for_active_in_search); + self.include_ignored + .merge_from(&file_finder.include_ignored); + } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] @@ -62,3 +48,15 @@ pub enum FileFinderWidth { XLarge, Full, } + +impl From for FileFinderWidth { + fn from(content: settings::FileFinderWidthContent) -> Self { + match content { + settings::FileFinderWidthContent::Small => FileFinderWidth::Small, + settings::FileFinderWidthContent::Medium => FileFinderWidth::Medium, + settings::FileFinderWidthContent::Large => FileFinderWidth::Large, + settings::FileFinderWidthContent::XLarge => FileFinderWidth::XLarge, + settings::FileFinderWidthContent::Full => FileFinderWidth::Full, + } + } +} diff --git a/crates/git_hosting_providers/Cargo.toml b/crates/git_hosting_providers/Cargo.toml index cce7ea439ecfb7ea51cbf5bd89dae5e6c6a6f350..64c7e701a4dbdcec405a6fb8b37eba8663a4d8db 100644 --- a/crates/git_hosting_providers/Cargo.toml +++ b/crates/git_hosting_providers/Cargo.toml @@ -19,7 +19,6 @@ git.workspace = true gpui.workspace = true http_client.workspace = true regex.workspace = true -schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/git_hosting_providers/src/settings.rs b/crates/git_hosting_providers/src/settings.rs index 3249981db91015479bab728484341519db357683..b6aabc47f3fba9fca6c9b908de87ca7319afa616 100644 --- a/crates/git_hosting_providers/src/settings.rs +++ b/crates/git_hosting_providers/src/settings.rs @@ -1,11 +1,8 @@ use std::sync::Arc; -use anyhow::Result; use git::GitHostingProviderRegistry; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsStore, SettingsUi}; +use settings::{GitHostingProviderConfig, GitHostingProviderKind, Settings, SettingsStore}; use url::Url; use util::ResultExt as _; @@ -55,43 +52,23 @@ fn update_git_hosting_providers_from_settings(cx: &mut App) { provider_registry.set_setting_providers(iter); } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitHostingProviderKind { - Github, - Gitlab, - Bitbucket, -} - -/// A custom Git hosting provider. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct GitHostingProviderConfig { - /// The type of the provider. - /// - /// Must be one of `github`, `gitlab`, or `bitbucket`. - pub provider: GitHostingProviderKind, - - /// The base URL for the provider (e.g., "https://code.corp.big.com"). - pub base_url: String, - - /// The display name for the provider (e.g., "BigCorp GitHub"). - pub name: String, -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] +#[derive(Debug, Clone)] pub struct GitHostingProviderSettings { - /// The list of custom Git hosting providers. - #[serde(default)] pub git_hosting_providers: Vec, } impl Settings for GitHostingProviderSettings { - type FileContent = Self; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + Self { + git_hosting_providers: content.project.git_hosting_providers.clone().unwrap(), + } + } - fn load(sources: settings::SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { + if let Some(more) = &content.project.git_hosting_providers { + self.git_hosting_providers.extend_from_slice(&more.clone()); + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} } diff --git a/crates/git_ui/src/blame_ui.rs b/crates/git_ui/src/blame_ui.rs index 6229c80c739ee73b20fe2640f30f9f751a1b4411..4e2c7377ffd795fd64b65621cbb9671e2ec98049 100644 --- a/crates/git_ui/src/blame_ui.rs +++ b/crates/git_ui/src/blame_ui.rs @@ -139,7 +139,8 @@ impl BlameRenderer for GitBlameRenderer { let author = blame_entry.author.as_deref().unwrap_or_default(); let summary_enabled = ProjectSettings::get_global(cx) .git - .show_inline_commit_summary(); + .inline_blame + .show_commit_summary; let text = match blame_entry.summary.as_ref() { Some(summary) if summary_enabled => { diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 9a0751c8eded6bbb2bb61e62723b03ef8a0a2149..b9a8dfea9ea167bf7ee807ee2b459444f4fa4f4d 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -564,7 +564,6 @@ impl PickerDelegate for BranchListDelegate { let show_author_name = ProjectSettings::get_global(cx) .git .branch_picker - .unwrap_or_default() .show_author_name; subject.map_or("no commits found".into(), |subject| { diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index e8306efc0fe6f6c554f16d3b873a0fd3c659c0b7..76671eba7b577e86d5049add743e965d11acd6c4 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -2,7 +2,6 @@ use crate::askpass_modal::AskPassModal; use crate::commit_modal::CommitModal; use crate::commit_tooltip::CommitTooltip; use crate::commit_view::CommitView; -use crate::git_panel_settings::StatusStyle; use crate::project_diff::{self, Diff, ProjectDiff}; use crate::remote_output::{self, RemoteAction, SuccessMessage}; use crate::{branch_picker, picker_prompt, render_remote_button}; @@ -51,7 +50,7 @@ use project::{ git_store::{GitStoreEvent, Repository, RepositoryEvent, RepositoryId}, }; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::{Settings, SettingsStore, StatusStyle}; use std::future::Future; use std::ops::Range; use std::path::{Path, PathBuf}; @@ -2439,8 +2438,9 @@ impl GitPanel { let workspace = workspace.read(cx); let fs = workspace.app_state().fs.clone(); cx.update_global::(|store, _cx| { - store.update_settings_file::(fs, move |settings, _cx| { - settings.sort_by_path = Some(!current_setting); + store.update_settings_file(fs, move |settings, _cx| { + settings.git_panel.get_or_insert_default().sort_by_path = + Some(!current_setting); }); }); } @@ -4356,11 +4356,9 @@ impl Panel for GitPanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| settings.dock = Some(position), - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.git_panel.get_or_insert_default().dock = Some(position.into()) + }); } fn size(&self, _: &Window, cx: &App) -> Pixels { diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index be26061b0d2cbeee9a0a75f52daf56fb9fcb94f4..c82ff469857f084ce59201b359a79883056fee43 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -2,8 +2,12 @@ use editor::EditorSettings; use gpui::Pixels; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; -use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; +use settings::{Settings, SettingsContent, StatusStyle}; +use ui::{ + px, + scrollbars::{ScrollbarVisibility, ShowScrollbar}, +}; +use util::MergeFrom; use workspace::dock::DockPosition; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -19,67 +23,7 @@ pub struct ScrollbarSettings { pub show: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -// Style of the git status indicator in the panel. -// -// Default: icon -pub enum StatusStyleContent { - Icon, - LabelColor, -} - -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum StatusStyle { - #[default] - Icon, - LabelColor, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "git_panel")] -pub struct GitPanelSettingsContent { - /// Whether to show the panel button in the status bar. - /// - /// Default: true - pub button: Option, - /// Where to dock the panel. - /// - /// Default: left - pub dock: Option, - /// Default width of the panel in pixels. - /// - /// Default: 360 - pub default_width: Option, - /// How entry statuses are displayed. - /// - /// Default: icon - pub status_style: Option, - /// How and when the scrollbar should be displayed. - /// - /// Default: inherits editor scrollbar settings - pub scrollbar: Option, - - /// What the default branch name should be when - /// `init.defaultBranch` is not set in git - /// - /// Default: main - pub fallback_branch_name: Option, - - /// Whether to sort entries in the panel by path - /// or by status (the default). - /// - /// Default: false - pub sort_by_path: Option, - - /// Whether to collapse untracked files in the diff panel. - /// - /// Default: false - pub collapse_untracked_diff: Option, -} - -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct GitPanelSettings { pub button: bool, pub dock: DockPosition, @@ -108,17 +52,50 @@ impl ScrollbarVisibility for GitPanelSettings { } impl Settings for GitPanelSettings { - type FileContent = GitPanelSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + let git_panel = content.git_panel.clone().unwrap(); + Self { + button: git_panel.button.unwrap(), + dock: git_panel.dock.unwrap().into(), + default_width: px(git_panel.default_width.unwrap()), + status_style: git_panel.status_style.unwrap(), + scrollbar: ScrollbarSettings { + show: git_panel.scrollbar.unwrap().show.map(Into::into), + }, + fallback_branch_name: git_panel.fallback_branch_name.unwrap(), + sort_by_path: git_panel.sort_by_path.unwrap(), + collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(), + } + } - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { + let Some(git_panel) = &content.git_panel else { + return; + }; + self.button.merge_from(&git_panel.button); + self.dock.merge_from(&git_panel.dock.map(Into::into)); + self.default_width + .merge_from(&git_panel.default_width.map(px)); + self.status_style.merge_from(&git_panel.status_style); + self.fallback_branch_name + .merge_from(&git_panel.fallback_branch_name); + self.sort_by_path.merge_from(&git_panel.sort_by_path); + self.collapse_untracked_diff + .merge_from(&git_panel.collapse_untracked_diff); + if let Some(show) = git_panel.scrollbar.as_ref().and_then(|s| s.show) { + self.scrollbar.show = Some(show.into()) + } } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.bool_setting("git.enabled", &mut current.button); - vscode.string_setting("git.defaultBranchName", &mut current.fallback_branch_name); + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { + if let Some(git_enabled) = vscode.read_bool("git.enabled") { + current.git_panel.get_or_insert_default().button = Some(git_enabled); + } + if let Some(default_branch) = vscode.read_string("git.defaultBranchName") { + current + .git_panel + .get_or_insert_default() + .fallback_branch_name = Some(default_branch.to_string()); + } } } diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 57438910fbef39f2b0308a3f4706d5e01df0f832..54a9b4d37c7a237cdedf20e0cc683895384caa03 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -13,12 +13,10 @@ path = "src/go_to_line.rs" doctest = false [dependencies] -anyhow.workspace = true editor.workspace = true gpui.workspace = true language.workspace = true menu.workspace = true -schemars.workspace = true serde.workspace = true settings.workspace = true text.workspace = true diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 5d7e0d27d362f8b6245d8ce25774342495a90427..9c109db8de6e092da5513317d9bc5b686146edf8 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -1,15 +1,13 @@ use editor::{Editor, EditorSettings, MultiBufferSnapshot}; use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; use std::{fmt::Write, num::NonZeroU32, time::Duration}; use text::{Point, Selection}; use ui::{ Button, ButtonCommon, Clickable, Context, FluentBuilder, IntoElement, LabelSize, ParentElement, Render, Tooltip, Window, div, }; -use util::paths::FILE_ROW_COLUMN_DELIMITER; +use util::{MergeFrom, paths::FILE_ROW_COLUMN_DELIMITER}; use workspace::{StatusItemView, Workspace, item::ItemHandle}; #[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)] @@ -293,41 +291,27 @@ impl StatusItemView for CursorPosition { } } -#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub(crate) enum LineIndicatorFormat { +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum LineIndicatorFormat { Short, - #[default] Long, } -#[derive( - Clone, Copy, Default, Debug, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey, -)] -#[settings_key(None)] -pub(crate) struct LineIndicatorFormatContent { - line_indicator_format: Option, +impl From for LineIndicatorFormat { + fn from(format: settings::LineIndicatorFormat) -> Self { + match format { + settings::LineIndicatorFormat::Short => LineIndicatorFormat::Short, + settings::LineIndicatorFormat::Long => LineIndicatorFormat::Long, + } + } } impl Settings for LineIndicatorFormat { - type FileContent = LineIndicatorFormatContent; - - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - let format = [ - sources.release_channel, - sources.profile, - sources.user, - sources.operating_system, - Some(sources.default), - ] - .into_iter() - .flatten() - .filter_map(|val| val.line_indicator_format) - .next() - .ok_or_else(Self::missing_default)?; - - Ok(format) + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + content.line_indicator_format.unwrap().into() } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + self.merge_from(&content.line_indicator_format.map(Into::into)); + } } diff --git a/crates/google_ai/Cargo.toml b/crates/google_ai/Cargo.toml index bc9213ca8f81d8f1bccfd84cbffe95aa61923cd8..ce759698ed5f986663fe1cae4a83b65cd76a8e4f 100644 --- a/crates/google_ai/Cargo.toml +++ b/crates/google_ai/Cargo.toml @@ -21,5 +21,6 @@ http_client.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true +settings.workspace = true strum.workspace = true workspace-hack.workspace = true diff --git a/crates/google_ai/src/google_ai.rs b/crates/google_ai/src/google_ai.rs index 92fd53189327fabccdc1472ac0fa2a20dc665646..9b7e5ec8d1c42fc846d131cfd063de5bba8287ae 100644 --- a/crates/google_ai/src/google_ai.rs +++ b/crates/google_ai/src/google_ai.rs @@ -4,6 +4,7 @@ use anyhow::{Result, anyhow, bail}; use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream}; use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +pub use settings::ModelMode as GoogleModelMode; pub const API_URL: &str = "https://generativelanguage.googleapis.com"; @@ -295,16 +296,6 @@ pub struct ThinkingConfig { pub thinking_budget: u32, } -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -pub enum GoogleModelMode { - #[default] - Default, - Thinking { - budget_tokens: Option, - }, -} - #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GenerationConfig { diff --git a/crates/image_viewer/Cargo.toml b/crates/image_viewer/Cargo.toml index 254c916789df2cdfa3e3458ed30572e84153ad61..1afa2c5f9dd90956b93c2e9dfac3537c7253a610 100644 --- a/crates/image_viewer/Cargo.toml +++ b/crates/image_viewer/Cargo.toml @@ -24,7 +24,6 @@ gpui.workspace = true language.workspace = true log.workspace = true project.workspace = true -schemars.workspace = true serde.workspace = true settings.workspace = true theme.workspace = true diff --git a/crates/image_viewer/src/image_viewer_settings.rs b/crates/image_viewer/src/image_viewer_settings.rs index 510de69b522fbb07cb8eedba43edfe3a95e4a591..e093a5fc826fa23c9894ecdb56466dcb71e03465 100644 --- a/crates/image_viewer/src/image_viewer_settings.rs +++ b/crates/image_viewer/src/image_viewer_settings.rs @@ -1,40 +1,30 @@ use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +pub use settings::ImageFileSizeUnit; +use settings::Settings; +use util::MergeFrom; /// The settings for the image viewer. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, SettingsUi, SettingsKey)] -#[settings_key(key = "image_viewer")] +#[derive(Clone, Debug, Default)] pub struct ImageViewerSettings { /// The unit to use for displaying image file sizes. /// /// Default: "binary" - #[serde(default)] pub unit: ImageFileSizeUnit, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)] -#[serde(rename_all = "snake_case")] -pub enum ImageFileSizeUnit { - /// Displays file size in binary units (e.g., KiB, MiB). - #[default] - Binary, - /// Displays file size in decimal units (e.g., KB, MB). - Decimal, -} - impl Settings for ImageViewerSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - SettingsSources::::json_merge_with( - [sources.default] - .into_iter() - .chain(sources.user) - .chain(sources.server), - ) + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + Self { + unit: content.image_viewer.clone().unwrap().unit.unwrap(), + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + self.unit.merge_from( + &content + .image_viewer + .as_ref() + .and_then(|image_viewer| image_viewer.unit), + ); + } } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 041badd10490a8ea876eb43975a911ca6811fa05..3c98e4a712a4e53358d2d31e7df981dccb86fa3c 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -18,12 +18,12 @@ chrono.workspace = true editor.workspace = true gpui.workspace = true log.workspace = true -schemars.workspace = true serde.workspace = true settings.workspace = true shellexpand.workspace = true workspace.workspace = true workspace-hack.workspace = true +util.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 5cdfa6c1df034deaf06e1c99ea99415757b84c29..24c3fecf78c2ea3ce1cd036e8b90f88a5d889ccd 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,16 +1,15 @@ -use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; use editor::scroll::Autoscroll; use editor::{Editor, SelectionEffects}; use gpui::{App, AppContext as _, Context, Window, actions}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +pub use settings::HourFormat; +use settings::Settings; use std::{ fs::OpenOptions, path::{Path, PathBuf}, sync::Arc, }; +use util::MergeFrom; use workspace::{AppState, OpenVisible, Workspace}; actions!( @@ -22,44 +21,35 @@ actions!( ); /// Settings specific to journaling -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "journal")] +#[derive(Clone, Debug)] pub struct JournalSettings { /// The path of the directory where journal entries are stored. /// /// Default: `~` - pub path: Option, + pub path: String, /// What format to display the hours in. /// /// Default: hour12 - pub hour_format: Option, + pub hour_format: HourFormat, } -impl Default for JournalSettings { - fn default() -> Self { +impl settings::Settings for JournalSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let journal = content.journal.clone().unwrap(); + Self { - path: Some("~".into()), - hour_format: Some(Default::default()), + path: journal.path.unwrap(), + hour_format: journal.hour_format.unwrap(), } } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HourFormat { - #[default] - Hour12, - Hour24, -} -impl settings::Settings for JournalSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(journal) = content.journal.as_ref() else { + return; + }; + self.path.merge_from(&journal.path); + self.hour_format.merge_from(&journal.hour_format); } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} } pub fn init(_: Arc, cx: &mut App) { @@ -77,7 +67,7 @@ pub fn init(_: Arc, cx: &mut App) { pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut App) { let settings = JournalSettings::get_global(cx); - let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) { + let journal_dir = match journal_dir(&settings.path) { Some(journal_dir) => journal_dir, None => { log::error!("Can't determine journal directory"); @@ -199,13 +189,13 @@ fn journal_dir(path: &str) -> Option { .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal")) } -fn heading_entry(now: NaiveTime, hour_format: &Option) -> String { +fn heading_entry(now: NaiveTime, hour_format: &HourFormat) -> String { match hour_format { - Some(HourFormat::Hour24) => { + HourFormat::Hour24 => { let hour = now.hour(); format!("# {}:{:02}", hour, now.minute()) } - _ => { + HourFormat::Hour12 => { let (pm, hour) = now.hour12(); let am_or_pm = if pm { "PM" } else { "AM" }; format!("# {}:{:02} {}", hour, now.minute(), am_or_pm) @@ -221,7 +211,7 @@ mod tests { #[test] fn test_heading_entry_defaults_to_hour_12() { let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap(); - let actual_heading_entry = heading_entry(naive_time, &None); + let actual_heading_entry = heading_entry(naive_time, &HourFormat::Hour12); let expected_heading_entry = "# 3:00 PM"; assert_eq!(actual_heading_entry, expected_heading_entry); @@ -230,7 +220,7 @@ mod tests { #[test] fn test_heading_entry_is_hour_12() { let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap(); - let actual_heading_entry = heading_entry(naive_time, &Some(HourFormat::Hour12)); + let actual_heading_entry = heading_entry(naive_time, &HourFormat::Hour12); let expected_heading_entry = "# 3:00 PM"; assert_eq!(actual_heading_entry, expected_heading_entry); @@ -239,7 +229,7 @@ mod tests { #[test] fn test_heading_entry_is_hour_24() { let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap(); - let actual_heading_entry = heading_entry(naive_time, &Some(HourFormat::Hour24)); + let actual_heading_entry = heading_entry(naive_time, &HourFormat::Hour24); let expected_heading_entry = "# 15:00"; assert_eq!(actual_heading_entry, expected_heading_entry); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index c94153ba00a29b40544b3c685ad4cfb1add3db1b..1a7fca79f64c2c253117a3acde8c4d7519a9c282 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -30,10 +30,9 @@ use gpui::{ use lsp::{LanguageServerId, NumberOrString}; use parking_lot::Mutex; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use settings::{SettingsUi, WorktreeId}; +use settings::WorktreeId; use smallvec::SmallVec; use smol::future::yield_now; use std::{ @@ -174,10 +173,7 @@ pub enum IndentKind { } /// The shape of a selection cursor. -#[derive( - Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum CursorShape { /// A vertical bar #[default] @@ -190,6 +186,17 @@ pub enum CursorShape { Hollow, } +impl From for CursorShape { + fn from(shape: settings::CursorShape) -> Self { + match shape { + settings::CursorShape::Bar => CursorShape::Bar, + settings::CursorShape::Block => CursorShape::Block, + settings::CursorShape::Underline => CursorShape::Underline, + settings::CursorShape::Hollow => CursorShape::Hollow, + } + } +} + #[derive(Clone, Debug)] struct SelectionSet { line_mode: bool, diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index fcd93390c891f1d65b2f424a5bc70cd7f23c7912..e079029b478af1c70ff626acea0791cdb204421f 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1,8 +1,5 @@ use super::*; use crate::Buffer; -use crate::language_settings::{ - AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, -}; use clock::ReplicaId; use collections::BTreeMap; use futures::FutureExt as _; @@ -13,6 +10,7 @@ use proto::deserialize_operation; use rand::prelude::*; use regex::RegexBuilder; use settings::SettingsStore; +use settings::{AllLanguageSettingsContent, LanguageSettingsContent}; use std::collections::BTreeSet; use std::{ env, @@ -3849,7 +3847,7 @@ fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) { cx.set_global(settings_store); crate::init(cx); cx.update_global::(|settings, cx| { - settings.update_user_settings::(cx, f); + settings.update_user_settings(cx, |content| f(&mut content.project.all_languages)); }); } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3c951e50ff231a72e284da743bb3e5d409eb9c5e..291b5d3957cc7274bdd07b5260890c23b7a253d1 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,8 +22,8 @@ mod toolchain; #[cfg(test)] pub mod buffer_tests; -pub use crate::language_settings::EditPredictionsMode; use crate::language_settings::SoftWrap; +pub use crate::language_settings::{EditPredictionsMode, IndentGuideSettings}; use anyhow::{Context as _, Result}; use async_trait::async_trait; use collections::{HashMap, HashSet, IndexSet}; diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 5d9d5529c145a8769d142a1f943b6ae00aeaaeb8..9ec57b90b39f40b9530bef7ff02753ea8f37d2e5 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -1,14 +1,11 @@ use crate::{ CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter, ManifestName, PLAIN_TEXT, ToolchainLister, - language_settings::{ - AllLanguageSettingsContent, LanguageSettingsContent, all_language_settings, - }, - task_context::ContextProvider, - with_parser, + language_settings::all_language_settings, task_context::ContextProvider, with_parser, }; use anyhow::{Context as _, Result, anyhow}; use collections::{FxHashMap, HashMap, HashSet, hash_map}; +use settings::{AllLanguageSettingsContent, LanguageSettingsContent}; use futures::{ Future, @@ -1175,7 +1172,7 @@ impl LanguageRegistryState { language.set_theme(theme.syntax()); } self.language_settings.languages.0.insert( - language.name(), + language.name().0, LanguageSettingsContent { tab_size: language.config.tab_size, hard_tabs: language.config.hard_tabs, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index af9e6edbfa4ed2ef44d7a5789069a83b7db829c7..ff7b432c5f26043799d94458621aa35f969ade06 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,29 +1,30 @@ //! Provides `language`-related settings. use crate::{File, Language, LanguageName, LanguageServerName}; -use anyhow::Result; use collections::{FxHashMap, HashMap, HashSet}; use ec4rs::{ Properties as EditorconfigProperties, property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs}, }; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; -use gpui::{App, Modifiers, SharedString}; +use gpui::{App, Modifiers}; use itertools::{Either, Itertools}; -use schemars::{JsonSchema, json_schema}; -use serde::{ - Deserialize, Deserializer, Serialize, - de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor}, -}; +use schemars::json_schema; +pub use settings::{ + CompletionSettingsContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave, + Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode, + RewrapBehavior, SelectedFormatter, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode, +}; use settings::{ - ParameterizedJsonSchema, Settings, SettingsKey, SettingsLocation, SettingsSources, - SettingsStore, SettingsUi, + IndentGuideSettingsContent, LanguageTaskSettingsContent, ParameterizedJsonSchema, + PrettierSettingsContent, Settings, SettingsContent, SettingsLocation, SettingsStore, + SettingsUi, }; use shellexpand; -use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc}; -use util::schemars::replace_subschema; -use util::serde::default_true; +use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; +use util::MergeFrom; +use util::{ResultExt, schemars::replace_subschema}; /// Initializes the language settings. pub fn init(cx: &mut App) { @@ -63,10 +64,11 @@ pub struct AllLanguageSettings { pub defaults: LanguageSettings, languages: HashMap, pub(crate) file_types: FxHashMap, GlobSet>, + pub(crate) file_globs: FxHashMap, Vec>, } /// The settings for a particular language. -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone)] pub struct LanguageSettings { /// How many columns a tab should occupy. pub tab_size: NonZeroU32, @@ -74,7 +76,7 @@ pub struct LanguageSettings { /// spaces. pub hard_tabs: bool, /// How to soft-wrap long lines of text. - pub soft_wrap: SoftWrap, + pub soft_wrap: settings::SoftWrap, /// The column at which to soft-wrap lines, for buffers where soft-wrap /// is enabled. pub preferred_line_length: u32, @@ -96,11 +98,11 @@ pub struct LanguageSettings { /// when saving it. pub ensure_final_newline_on_save: bool, /// How to perform a buffer format. - pub formatter: SelectedFormatter, + pub formatter: settings::SelectedFormatter, /// Zed's Prettier integration settings. pub prettier: PrettierSettings, /// Whether to automatically close JSX tags. - pub jsx_tag_auto_close: JsxTagAutoCloseSettings, + pub jsx_tag_auto_close: bool, /// Whether to use language servers to provide code intelligence. pub enable_language_server: bool, /// The list of language servers to use (or disable) for this language. @@ -122,9 +124,9 @@ pub struct LanguageSettings { /// scopes. pub edit_predictions_disabled_in: Vec, /// Whether to show tabs and spaces in the editor. - pub show_whitespaces: ShowWhitespaceSetting, + pub show_whitespaces: settings::ShowWhitespaceSetting, /// Visible characters used to render whitespace when show_whitespaces is enabled. - pub whitespace_map: WhitespaceMap, + pub whitespace_map: settings::WhitespaceMap, /// Whether to start a new line with a comment when a previous line is a comment as well. pub extend_comment_on_newline: bool, /// Inlay hint related settings. @@ -147,7 +149,7 @@ pub struct LanguageSettings { /// Whether to perform linked edits pub linked_edits: bool, /// Task configuration for this language. - pub tasks: LanguageTaskConfig, + pub tasks: LanguageTaskSettings, /// Whether to pop the completions menu while typing in an editor without /// explicitly requesting it. pub show_completions_on_input: bool, @@ -160,965 +162,210 @@ pub struct LanguageSettings { pub debuggers: Vec, } -impl LanguageSettings { - /// A token representing the rest of the available language servers. - const REST_OF_LANGUAGE_SERVERS: &'static str = "..."; - - /// Returns the customized list of language servers from the list of - /// available language servers. - pub fn customized_language_servers( - &self, - available_language_servers: &[LanguageServerName], - ) -> Vec { - Self::resolve_language_servers(&self.language_servers, available_language_servers) - } - - pub(crate) fn resolve_language_servers( - configured_language_servers: &[String], - available_language_servers: &[LanguageServerName], - ) -> Vec { - let (disabled_language_servers, enabled_language_servers): ( - Vec, - Vec, - ) = configured_language_servers.iter().partition_map( - |language_server| match language_server.strip_prefix('!') { - Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())), - None => Either::Right(LanguageServerName(language_server.clone().into())), - }, - ); - - let rest = available_language_servers - .iter() - .filter(|&available_language_server| { - !disabled_language_servers.contains(available_language_server) - && !enabled_language_servers.contains(available_language_server) - }) - .cloned() - .collect::>(); - - enabled_language_servers - .into_iter() - .flat_map(|language_server| { - if language_server.0.as_ref() == Self::REST_OF_LANGUAGE_SERVERS { - rest.clone() - } else { - vec![language_server] - } - }) - .collect::>() - } -} - -/// The provider that supplies edit predictions. -#[derive( - Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum EditPredictionProvider { - None, - #[default] - Copilot, - Supermaven, - Zed, -} - -impl EditPredictionProvider { - pub fn is_zed(&self) -> bool { - match self { - EditPredictionProvider::Zed => true, - EditPredictionProvider::None - | EditPredictionProvider::Copilot - | EditPredictionProvider::Supermaven => false, - } - } -} - -/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot) -/// or [Supermaven](https://supermaven.com). -#[derive(Clone, Debug, Default, SettingsUi)] -pub struct EditPredictionSettings { - /// The provider that supplies edit predictions. - pub provider: EditPredictionProvider, - /// A list of globs representing files that edit predictions should be disabled for. - /// This list adds to a pre-existing, sensible default set of globs. - /// Any additional ones you add are combined with them. - #[settings_ui(skip)] - pub disabled_globs: Vec, - /// Configures how edit predictions are displayed in the buffer. - pub mode: EditPredictionsMode, - /// Settings specific to GitHub Copilot. - pub copilot: CopilotSettings, - /// Whether edit predictions are enabled in the assistant panel. - /// This setting has no effect if globally disabled. - pub enabled_in_text_threads: bool, -} - -impl EditPredictionSettings { - /// Returns whether edit predictions are enabled for the given path. - pub fn enabled_for_file(&self, file: &Arc, cx: &App) -> bool { - !self.disabled_globs.iter().any(|glob| { - if glob.is_absolute { - file.as_local() - .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx))) - } else { - glob.matcher.is_match(file.path()) - } - }) - } -} - -#[derive(Clone, Debug)] -pub struct DisabledGlob { - matcher: GlobMatcher, - is_absolute: bool, -} - -/// The mode in which edit predictions should be displayed. -#[derive( - Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum EditPredictionsMode { - /// If provider supports it, display inline when holding modifier key (e.g., alt). - /// Otherwise, eager preview is used. - #[serde(alias = "auto")] - Subtle, - /// Display inline when there are no language server completions available. - #[default] - #[serde(alias = "eager_preview")] - Eager, -} - -#[derive(Clone, Debug, Default, SettingsUi)] -pub struct CopilotSettings { - /// HTTP/HTTPS proxy to use for Copilot. - #[settings_ui(skip)] - pub proxy: Option, - /// Disable certificate verification for proxy (not recommended). - pub proxy_no_verify: Option, - /// Enterprise URI for Copilot. - #[settings_ui(skip)] - pub enterprise_uri: Option, -} - -/// The settings for all languages. -#[derive( - Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey, -)] -#[settings_key(None)] -#[settings_ui(group = "Default Language Settings")] -pub struct AllLanguageSettingsContent { - /// The settings for enabling/disabling features. - #[serde(default)] - pub features: Option, - /// The edit prediction settings. - #[serde(default)] - pub edit_predictions: Option, - /// The default language settings. - #[serde(flatten)] - pub defaults: LanguageSettingsContent, - /// The settings for individual languages. - #[serde(default)] - pub languages: LanguageToSettingsMap, - /// Settings for associating file extensions and filenames - /// with languages. - #[serde(default)] - #[settings_ui(skip)] - pub file_types: HashMap, Vec>, -} - -/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language -/// names in the keys. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct LanguageToSettingsMap(pub HashMap); - -impl SettingsUi for LanguageToSettingsMap { - fn settings_ui_item() -> settings::SettingsUiItem { - settings::SettingsUiItem::DynamicMap(settings::SettingsUiItemDynamicMap { - item: LanguageSettingsContent::settings_ui_item, - defaults_path: &[], - determine_items: |settings_value, cx| { - use settings::SettingsUiEntryMetaData; - - // todo(settings_ui): We should be using a global LanguageRegistry, but it's not implemented yet - _ = cx; - - let Some(settings_language_map) = settings_value.as_object() else { - return Vec::new(); - }; - let mut languages = Vec::with_capacity(settings_language_map.len()); - - for language_name in settings_language_map.keys().map(gpui::SharedString::from) { - languages.push(SettingsUiEntryMetaData { - title: language_name.clone(), - path: language_name, - // todo(settings_ui): Implement documentation for each language - // ideally based on the language's official docs from extension or builtin info - documentation: None, - }); - } - return languages; - }, - }) - } -} - -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - let language_settings_content_ref = generator - .subschema_for::() - .to_value(); - replace_subschema::(generator, || json_schema!({ - "type": "object", - "properties": params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - language_settings_content_ref.clone(), - ) - }) - .collect::>() - })) - } - } -} - -/// Controls how completions are processed for this language. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Clone)] pub struct CompletionSettings { /// Controls how words are completed. /// For large documents, not all words may be fetched for completion. /// /// Default: `fallback` - #[serde(default = "default_words_completion_mode")] pub words: WordsCompletionMode, /// How many characters has to be in the completions query to automatically show the words-based completions. /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command. /// /// Default: 3 - #[serde(default = "default_3")] pub words_min_length: usize, /// Whether to fetch LSP completions or not. /// /// Default: true - #[serde(default = "default_true")] pub lsp: bool, /// When fetching LSP completions, determines how long to wait for a response of a particular server. /// When set to 0, waits indefinitely. /// /// Default: 0 - #[serde(default)] pub lsp_fetch_timeout_ms: u64, /// Controls how LSP completions are inserted. /// /// Default: "replace_suffix" - #[serde(default = "default_lsp_insert_mode")] pub lsp_insert_mode: LspInsertMode, } -/// Controls how document's words are completed. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WordsCompletionMode { - /// Always fetch document's words for completions along with LSP completions. - Enabled, - /// Only if LSP response errors or times out, - /// use document's words to show completions. - Fallback, - /// Never fetch or complete document's words for completions. - /// (Word-based completions can still be queried via a separate action) - Disabled, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum LspInsertMode { - /// Replaces text before the cursor, using the `insert` range described in the LSP specification. - Insert, - /// Replaces text before and after the cursor, using the `replace` range described in the LSP specification. - Replace, - /// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text, - /// and like `"insert"` otherwise. - ReplaceSubsequence, - /// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like - /// `"insert"` otherwise. - ReplaceSuffix, -} - -fn default_words_completion_mode() -> WordsCompletionMode { - WordsCompletionMode::Fallback -} - -fn default_lsp_insert_mode() -> LspInsertMode { - LspInsertMode::ReplaceSuffix -} - -fn default_3() -> usize { - 3 +impl CompletionSettings { + pub fn merge_from(&mut self, src: &Option) { + let Some(src) = src else { return }; + self.words.merge_from(&src.words); + self.words_min_length.merge_from(&src.words_min_length); + self.lsp.merge_from(&src.lsp); + self.lsp_fetch_timeout_ms + .merge_from(&src.lsp_fetch_timeout_ms); + self.lsp_insert_mode.merge_from(&src.lsp_insert_mode); + } } -/// The settings for a particular language. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi)] -#[settings_ui(group = "Default")] -pub struct LanguageSettingsContent { - /// How many columns a tab should occupy. - /// - /// Default: 4 - #[serde(default)] - #[settings_ui(skip)] - pub tab_size: Option, - /// Whether to indent lines using tab characters, as opposed to multiple - /// spaces. - /// - /// Default: false - #[serde(default)] - pub hard_tabs: Option, - /// How to soft-wrap long lines of text. - /// - /// Default: none - #[serde(default)] - pub soft_wrap: Option, - /// The column at which to soft-wrap lines, for buffers where soft-wrap - /// is enabled. - /// - /// Default: 80 - #[serde(default)] - pub preferred_line_length: Option, - /// Whether to show wrap guides in the editor. Setting this to true will - /// show a guide at the 'preferred_line_length' value if softwrap is set to - /// 'preferred_line_length', and will show any additional guides as specified - /// by the 'wrap_guides' setting. - /// - /// Default: true - #[serde(default)] - pub show_wrap_guides: Option, - /// Character counts at which to show wrap guides in the editor. - /// - /// Default: [] - #[serde(default)] - #[settings_ui(skip)] - pub wrap_guides: Option>, - /// Indent guide related settings. - #[serde(default)] - pub indent_guides: Option, - /// Whether or not to perform a buffer format before saving. - /// - /// Default: on - #[serde(default)] - pub format_on_save: Option, - /// Whether or not to remove any trailing whitespace from lines of a buffer - /// before saving it. - /// - /// Default: true - #[serde(default)] - pub remove_trailing_whitespace_on_save: Option, - /// Whether or not to ensure there's a single newline at the end of a buffer - /// when saving it. - /// - /// Default: true - #[serde(default)] - pub ensure_final_newline_on_save: Option, - /// How to perform a buffer format. - /// - /// Default: auto - #[serde(default)] - #[settings_ui(skip)] - pub formatter: Option, - /// Zed's Prettier integration settings. - /// Allows to enable/disable formatting with Prettier - /// and configure default Prettier, used when no project-level Prettier installation is found. - /// - /// Default: off - #[serde(default)] - pub prettier: Option, - /// Whether to automatically close JSX tags. - #[serde(default)] - pub jsx_tag_auto_close: Option, - /// Whether to use language servers to provide code intelligence. - /// - /// Default: true - #[serde(default)] - pub enable_language_server: Option, - /// The list of language servers to use (or disable) for this language. - /// - /// This array should consist of language server IDs, as well as the following - /// special tokens: - /// - `"!"` - A language server ID prefixed with a `!` will be disabled. - /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language. - /// - /// Default: ["..."] - #[serde(default)] - #[settings_ui(skip)] - pub language_servers: Option>, - /// Controls where the `editor::Rewrap` action is allowed for this language. - /// - /// Note: This setting has no effect in Vim mode, as rewrap is already - /// allowed everywhere. - /// - /// Default: "in_comments" - #[serde(default)] - pub allow_rewrap: Option, - /// Controls whether edit predictions are shown immediately (true) - /// or manually by triggering `editor::ShowEditPrediction` (false). - /// - /// Default: true - #[serde(default)] - pub show_edit_predictions: Option, - /// Controls whether edit predictions are shown in the given language - /// scopes. - /// - /// Example: ["string", "comment"] - /// - /// Default: [] - #[serde(default)] - #[settings_ui(skip)] - pub edit_predictions_disabled_in: Option>, - /// Whether to show tabs and spaces in the editor. - #[serde(default)] - pub show_whitespaces: Option, - /// Visible characters used to render whitespace when show_whitespaces is enabled. - /// - /// Default: "•" for spaces, "→" for tabs. - #[serde(default)] - pub whitespace_map: Option, - /// Whether to start a new line with a comment when a previous line is a comment as well. - /// - /// Default: true - #[serde(default)] - pub extend_comment_on_newline: Option, - /// Inlay hint related settings. - #[serde(default)] - pub inlay_hints: Option, - /// Whether to automatically type closing characters for you. For example, - /// when you type (, Zed will automatically add a closing ) at the correct position. - /// - /// Default: true - pub use_autoclose: Option, - /// Whether to automatically surround text with characters for you. For example, - /// when you select text and type (, Zed will automatically surround text with (). - /// - /// Default: true - pub use_auto_surround: Option, - /// Controls how the editor handles the autoclosed characters. - /// When set to `false`(default), skipping over and auto-removing of the closing characters - /// happen only for auto-inserted characters. - /// Otherwise(when `true`), the closing characters are always skipped over and auto-removed - /// no matter how they were inserted. - /// - /// Default: false - pub always_treat_brackets_as_autoclosed: Option, - /// Whether to use additional LSP queries to format (and amend) the code after - /// every "trigger" symbol input, defined by LSP server capabilities. - /// - /// Default: true - pub use_on_type_format: Option, - /// Which code actions to run on save after the formatter. - /// These are not run if formatting is off. - /// - /// Default: {} (or {"source.organizeImports": true} for Go). - #[settings_ui(skip)] - pub code_actions_on_format: Option>, - /// Whether to perform linked edits of associated ranges, if the language server supports it. - /// For example, when editing opening tag, the contents of the closing tag will be edited as well. - /// - /// Default: true - pub linked_edits: Option, - /// Whether indentation should be adjusted based on the context whilst typing. - /// - /// Default: true - pub auto_indent: Option, - /// Whether indentation of pasted content should be adjusted based on the context. +/// The settings for indent guides. +#[derive(Debug, Clone, PartialEq)] +pub struct IndentGuideSettings { + /// Whether to display indent guides in the editor. /// /// Default: true - pub auto_indent_on_paste: Option, - /// Task configuration for this language. + pub enabled: bool, + /// The width of the indent guides in pixels, between 1 and 10. /// - /// Default: {} - pub tasks: Option, - /// Whether to pop the completions menu while typing in an editor without - /// explicitly requesting it. + /// Default: 1 + pub line_width: u32, + /// The width of the active indent guide in pixels, between 1 and 10. /// - /// Default: true - pub show_completions_on_input: Option, - /// Whether to display inline and alongside documentation for items in the - /// completions menu. + /// Default: 1 + pub active_line_width: u32, + /// Determines how indent guides are colored. /// - /// Default: true - pub show_completion_documentation: Option, - /// Controls how completions are processed for this language. - pub completions: Option, - /// Preferred debuggers for this language. + /// Default: Fixed + pub coloring: settings::IndentGuideColoring, + /// Determines how indent guide backgrounds are colored. /// - /// Default: [] - #[settings_ui(skip)] - pub debuggers: Option>, + /// Default: Disabled + pub background_coloring: settings::IndentGuideBackgroundColoring, } -/// The behavior of `editor::Rewrap`. -#[derive( - Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, SettingsUi, -)] -#[serde(rename_all = "snake_case")] -pub enum RewrapBehavior { - /// Only rewrap within comments. - #[default] - InComments, - /// Only rewrap within the current selection(s). - InSelections, - /// Allow rewrapping anywhere. - Anywhere, -} +impl IndentGuideSettings { + pub fn merge_from(&mut self, src: &Option) { + let Some(src) = src else { return }; -/// The contents of the edit prediction settings. -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, SettingsUi)] -pub struct EditPredictionSettingsContent { - /// A list of globs representing files that edit predictions should be disabled for. - /// This list adds to a pre-existing, sensible default set of globs. - /// Any additional ones you add are combined with them. - #[serde(default)] - #[settings_ui(skip)] - pub disabled_globs: Option>, - /// The mode used to display edit predictions in the buffer. - /// Provider support required. - #[serde(default)] - pub mode: EditPredictionsMode, - /// Settings specific to GitHub Copilot. - #[serde(default)] - pub copilot: CopilotSettingsContent, - /// Whether edit predictions are enabled in the assistant prompt editor. - /// This has no effect if globally disabled. - #[serde(default = "default_true")] - pub enabled_in_text_threads: bool, + self.enabled.merge_from(&src.enabled); + self.line_width.merge_from(&src.line_width); + self.active_line_width.merge_from(&src.active_line_width); + self.coloring.merge_from(&src.coloring); + self.background_coloring + .merge_from(&src.background_coloring); + } } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, SettingsUi)] -pub struct CopilotSettingsContent { - /// HTTP/HTTPS proxy to use for Copilot. - /// - /// Default: none - #[serde(default)] - #[settings_ui(skip)] - pub proxy: Option, - /// Disable certificate verification for the proxy (not recommended). - /// - /// Default: false - #[serde(default)] - pub proxy_no_verify: Option, - /// Enterprise URI for Copilot. +#[derive(Debug, Clone)] +pub struct LanguageTaskSettings { + /// Extra task variables to set for a particular language. + pub variables: HashMap, + pub enabled: bool, + /// Use LSP tasks over Zed language extension ones. + /// If no LSP tasks are returned due to error/timeout or regular execution, + /// Zed language extension tasks will be used instead. /// - /// Default: none - #[serde(default)] - #[settings_ui(skip)] - pub enterprise_uri: Option, -} - -/// The settings for enabling/disabling features. -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -#[settings_ui(group = "Features")] -pub struct FeaturesContent { - /// Determines which edit prediction provider to use. - pub edit_prediction_provider: Option, -} - -/// Controls the soft-wrapping behavior in the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum SoftWrap { - /// Prefer a single line generally, unless an overly long line is encountered. - None, - /// Deprecated: use None instead. Left to avoid breaking existing users' configs. - /// Prefer a single line generally, unless an overly long line is encountered. - PreferLine, - /// Soft wrap lines that exceed the editor width. - EditorWidth, - /// Soft wrap lines at the preferred line length. - PreferredLineLength, - /// Soft wrap line at the preferred line length or the editor width (whichever is smaller). - Bounded, -} - -/// Controls the behavior of formatting files when they are saved. -#[derive(Debug, Clone, PartialEq, Eq, SettingsUi)] -pub enum FormatOnSave { - /// Files should be formatted on save. - On, - /// Files should not be formatted on save. - Off, - List(FormatterList), -} - -impl JsonSchema for FormatOnSave { - fn schema_name() -> Cow<'static, str> { - "OnSaveFormatter".into() - } - - fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { - let formatter_schema = Formatter::json_schema(generator); - - json_schema!({ - "oneOf": [ - { - "type": "array", - "items": formatter_schema - }, - { - "type": "string", - "enum": ["on", "off", "language_server"] - }, - formatter_schema - ] - }) - } + /// Other Zed tasks will still be shown: + /// * Zed task from either of the task config file + /// * Zed task from history (e.g. one-off task was spawned before) + pub prefer_lsp: bool, } -impl Serialize for FormatOnSave { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - match self { - Self::On => serializer.serialize_str("on"), - Self::Off => serializer.serialize_str("off"), - Self::List(list) => list.serialize(serializer), - } +impl LanguageTaskSettings { + pub fn merge_from(&mut self, src: &Option) { + let Some(src) = src.clone() else { + return; + }; + self.variables.extend(src.variables); + self.enabled.merge_from(&src.enabled); + self.prefer_lsp.merge_from(&src.prefer_lsp); } } -impl<'de> Deserialize<'de> for FormatOnSave { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - struct FormatDeserializer; - - impl<'d> Visitor<'d> for FormatDeserializer { - type Value = FormatOnSave; +/// Allows to enable/disable formatting with Prettier +/// and configure default Prettier, used when no project-level Prettier installation is found. +/// Prettier formatting is disabled by default. +#[derive(Debug, Clone)] +pub struct PrettierSettings { + /// Enables or disables formatting with Prettier for a given language. + pub allowed: bool, - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid on-save formatter kind") - } - fn visit_str(self, v: &str) -> std::result::Result - where - E: serde::de::Error, - { - if v == "on" { - Ok(Self::Value::On) - } else if v == "off" { - Ok(Self::Value::Off) - } else if v == "language_server" { - Ok(Self::Value::List(FormatterList::Single( - Formatter::LanguageServer { name: None }, - ))) - } else { - let ret: Result = - Deserialize::deserialize(v.into_deserializer()); - ret.map(Self::Value::List) - } - } - fn visit_map(self, map: A) -> Result - where - A: MapAccess<'d>, - { - let ret: Result = - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); - ret.map(Self::Value::List) - } - fn visit_seq(self, map: A) -> Result - where - A: SeqAccess<'d>, - { - let ret: Result = - Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); - ret.map(Self::Value::List) - } - } - deserializer.deserialize_any(FormatDeserializer) - } -} + /// Forces Prettier integration to use a specific parser name when formatting files with the language. + pub parser: Option, -/// Controls how whitespace should be displayedin the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum ShowWhitespaceSetting { - /// Draw whitespace only for the selected text. - Selection, - /// Do not draw any tabs or spaces. - None, - /// Draw all invisible symbols. - All, - /// Draw whitespaces at boundaries only. - /// - /// For a whitespace to be on a boundary, any of the following conditions need to be met: - /// - It is a tab - /// - It is adjacent to an edge (start or end) - /// - It is adjacent to a whitespace (left or right) - Boundary, - /// Draw whitespaces only after non-whitespace characters. - Trailing, -} + /// Forces Prettier integration to use specific plugins when formatting files with the language. + /// The default Prettier will be installed with these plugins. + pub plugins: HashSet, -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, SettingsUi)] -pub struct WhitespaceMap { - #[serde(default)] - pub space: Option, - #[serde(default)] - pub tab: Option, + /// Default Prettier options, in the format as in package.json section for Prettier. + /// If project installs Prettier via its package.json, these options will be ignored. + pub options: HashMap, } -impl WhitespaceMap { - pub fn space(&self) -> SharedString { - self.space - .as_ref() - .map_or_else(|| SharedString::from("•"), |s| SharedString::from(s)) - } - - pub fn tab(&self) -> SharedString { - self.tab - .as_ref() - .map_or_else(|| SharedString::from("→"), |s| SharedString::from(s)) +impl PrettierSettings { + pub fn merge_from(&mut self, src: &Option) { + let Some(src) = src.clone() else { return }; + self.allowed.merge_from(&src.allowed); + self.parser = src.parser.clone(); + self.plugins.extend(src.plugins); + self.options.extend(src.options); } } -/// Controls which formatter should be used when formatting code. -#[derive(Clone, Debug, Default, PartialEq, Eq, SettingsUi)] -pub enum SelectedFormatter { - /// Format files using Zed's Prettier integration (if applicable), - /// or falling back to formatting via language server. - #[default] - Auto, - List(FormatterList), -} - -impl JsonSchema for SelectedFormatter { - fn schema_name() -> Cow<'static, str> { - "Formatter".into() - } - - fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { - let formatter_schema = Formatter::json_schema(generator); - - json_schema!({ - "oneOf": [ - { - "type": "array", - "items": formatter_schema - }, - { - "type": "string", - "enum": ["auto", "language_server"] - }, - formatter_schema - ] - }) - } -} +impl LanguageSettings { + /// A token representing the rest of the available language servers. + const REST_OF_LANGUAGE_SERVERS: &'static str = "..."; -impl Serialize for SelectedFormatter { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - match self { - SelectedFormatter::Auto => serializer.serialize_str("auto"), - SelectedFormatter::List(list) => list.serialize(serializer), - } + /// Returns the customized list of language servers from the list of + /// available language servers. + pub fn customized_language_servers( + &self, + available_language_servers: &[LanguageServerName], + ) -> Vec { + Self::resolve_language_servers(&self.language_servers, available_language_servers) } -} -impl<'de> Deserialize<'de> for SelectedFormatter { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - struct FormatDeserializer; + pub(crate) fn resolve_language_servers( + configured_language_servers: &[String], + available_language_servers: &[LanguageServerName], + ) -> Vec { + let (disabled_language_servers, enabled_language_servers): ( + Vec, + Vec, + ) = configured_language_servers.iter().partition_map( + |language_server| match language_server.strip_prefix('!') { + Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())), + None => Either::Right(LanguageServerName(language_server.clone().into())), + }, + ); - impl<'d> Visitor<'d> for FormatDeserializer { - type Value = SelectedFormatter; + let rest = available_language_servers + .iter() + .filter(|&available_language_server| { + !disabled_language_servers.contains(available_language_server) + && !enabled_language_servers.contains(available_language_server) + }) + .cloned() + .collect::>(); - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid formatter kind") - } - fn visit_str(self, v: &str) -> std::result::Result - where - E: serde::de::Error, - { - if v == "auto" { - Ok(Self::Value::Auto) - } else if v == "language_server" { - Ok(Self::Value::List(FormatterList::Single( - Formatter::LanguageServer { name: None }, - ))) + enabled_language_servers + .into_iter() + .flat_map(|language_server| { + if language_server.0.as_ref() == Self::REST_OF_LANGUAGE_SERVERS { + rest.clone() } else { - let ret: Result = - Deserialize::deserialize(v.into_deserializer()); - ret.map(SelectedFormatter::List) + vec![language_server] } - } - fn visit_map(self, map: A) -> Result - where - A: MapAccess<'d>, - { - let ret: Result = - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); - ret.map(SelectedFormatter::List) - } - fn visit_seq(self, map: A) -> Result - where - A: SeqAccess<'d>, - { - let ret: Result = - Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); - ret.map(SelectedFormatter::List) - } - } - deserializer.deserialize_any(FormatDeserializer) - } -} - -/// Controls which formatters should be used when formatting code. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(untagged)] -pub enum FormatterList { - Single(Formatter), - Vec(#[settings_ui(skip)] Vec), -} - -impl Default for FormatterList { - fn default() -> Self { - Self::Single(Formatter::default()) - } -} - -impl AsRef<[Formatter]> for FormatterList { - fn as_ref(&self) -> &[Formatter] { - match &self { - Self::Single(single) => slice::from_ref(single), - Self::Vec(v) => v, - } + }) + .collect::>() } } -/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration. -#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum Formatter { - /// Format code using the current language server. - LanguageServer { - #[settings_ui(skip)] - name: Option, - }, - /// Format code using Zed's Prettier integration. - #[default] - Prettier, - /// Format code using an external command. - External { - /// The external program to run. - #[settings_ui(skip)] - command: Arc, - /// The arguments to pass to the program. - #[settings_ui(skip)] - arguments: Option>, - }, - /// Files should be formatted using code actions executed by language servers. - CodeActions(#[settings_ui(skip)] HashMap), -} - -/// The settings for indent guides. -#[derive( - Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi, -)] -pub struct IndentGuideSettings { - /// Whether to display indent guides in the editor. - /// - /// Default: true - #[serde(default = "default_true")] - pub enabled: bool, - /// The width of the indent guides in pixels, between 1 and 10. - /// - /// Default: 1 - #[serde(default = "line_width")] - pub line_width: u32, - /// The width of the active indent guide in pixels, between 1 and 10. - /// - /// Default: 1 - #[serde(default = "active_line_width")] - pub active_line_width: u32, - /// Determines how indent guides are colored. - /// - /// Default: Fixed - #[serde(default)] - pub coloring: IndentGuideColoring, - /// Determines how indent guide backgrounds are colored. - /// - /// Default: Disabled - #[serde(default)] - pub background_coloring: IndentGuideBackgroundColoring, -} - -fn line_width() -> u32 { - 1 -} - -fn active_line_width() -> u32 { - line_width() -} - -/// Determines how indent guides are colored. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum IndentGuideColoring { - /// Do not render any lines for indent guides. - Disabled, - /// Use the same color for all indentation levels. - #[default] - Fixed, - /// Use a different color for each indentation level. - IndentAware, -} - -/// Determines how indent guide backgrounds are colored. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum IndentGuideBackgroundColoring { - /// Do not render any background for indent guides. - #[default] - Disabled, - /// Use a different color for each indentation level. - IndentAware, -} - -/// The settings for inlay hints. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)] +// The settings for inlay hints. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct InlayHintSettings { /// Global switch to toggle hints on and off. /// /// Default: false - #[serde(default)] pub enabled: bool, /// Global switch to toggle inline values on and off when debugging. /// /// Default: true - #[serde(default = "default_true")] pub show_value_hints: bool, /// Whether type hints should be shown. /// /// Default: true - #[serde(default = "default_true")] pub show_type_hints: bool, /// Whether parameter hints should be shown. /// /// Default: true - #[serde(default = "default_true")] pub show_parameter_hints: bool, /// Whether other hints should be shown. /// /// Default: true - #[serde(default = "default_true")] pub show_other_hints: bool, /// Whether to show a background for inlay hints. /// @@ -1126,72 +373,116 @@ pub struct InlayHintSettings { /// from the current theme. /// /// Default: false - #[serde(default)] pub show_background: bool, /// Whether or not to debounce inlay hints updates after buffer edits. /// /// Set to 0 to disable debouncing. /// /// Default: 700 - #[serde(default = "edit_debounce_ms")] pub edit_debounce_ms: u64, /// Whether or not to debounce inlay hints updates after buffer scrolls. /// /// Set to 0 to disable debouncing. /// /// Default: 50 - #[serde(default = "scroll_debounce_ms")] pub scroll_debounce_ms: u64, /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified. /// If only a subset of the modifiers specified is pressed, hints are not toggled. /// If no modifiers are specified, this is equivalent to `None`. /// /// Default: None - #[serde(default)] pub toggle_on_modifiers_press: Option, } -fn edit_debounce_ms() -> u64 { - 700 +impl InlayHintSettings { + /// Returns the kinds of inlay hints that are enabled based on the settings. + pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { + let mut kinds = HashSet::default(); + if self.show_type_hints { + kinds.insert(Some(InlayHintKind::Type)); + } + if self.show_parameter_hints { + kinds.insert(Some(InlayHintKind::Parameter)); + } + if self.show_other_hints { + kinds.insert(None); + } + kinds + } +} + +/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot) +/// or [Supermaven](https://supermaven.com). +#[derive(Clone, Debug, Default, SettingsUi)] +pub struct EditPredictionSettings { + /// The provider that supplies edit predictions. + pub provider: settings::EditPredictionProvider, + /// A list of globs representing files that edit predictions should be disabled for. + /// This list adds to a pre-existing, sensible default set of globs. + /// Any additional ones you add are combined with them. + #[settings_ui(skip)] + pub disabled_globs: Vec, + /// Configures how edit predictions are displayed in the buffer. + pub mode: settings::EditPredictionsMode, + /// Settings specific to GitHub Copilot. + pub copilot: CopilotSettings, + /// Whether edit predictions are enabled in the assistant panel. + /// This setting has no effect if globally disabled. + pub enabled_in_text_threads: bool, +} + +impl EditPredictionSettings { + /// Returns whether edit predictions are enabled for the given path. + pub fn enabled_for_file(&self, file: &Arc, cx: &App) -> bool { + !self.disabled_globs.iter().any(|glob| { + if glob.is_absolute { + file.as_local() + .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx))) + } else { + glob.matcher.is_match(file.path()) + } + }) + } } -fn scroll_debounce_ms() -> u64 { - 50 +#[derive(Clone, Debug)] +pub struct DisabledGlob { + matcher: GlobMatcher, + is_absolute: bool, } -/// The task settings for a particular language. -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema, SettingsUi)] -pub struct LanguageTaskConfig { - /// Extra task variables to set for a particular language. - #[serde(default)] - pub variables: HashMap, - #[serde(default = "default_true")] - pub enabled: bool, - /// Use LSP tasks over Zed language extension ones. - /// If no LSP tasks are returned due to error/timeout or regular execution, - /// Zed language extension tasks will be used instead. - /// - /// Other Zed tasks will still be shown: - /// * Zed task from either of the task config file - /// * Zed task from history (e.g. one-off task was spawned before) - #[serde(default = "default_true")] - pub prefer_lsp: bool, +#[derive(Clone, Debug, Default, SettingsUi)] +pub struct CopilotSettings { + /// HTTP/HTTPS proxy to use for Copilot. + #[settings_ui(skip)] + pub proxy: Option, + /// Disable certificate verification for proxy (not recommended). + pub proxy_no_verify: Option, + /// Enterprise URI for Copilot. + #[settings_ui(skip)] + pub enterprise_uri: Option, } -impl InlayHintSettings { - /// Returns the kinds of inlay hints that are enabled based on the settings. - pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { - let mut kinds = HashSet::default(); - if self.show_type_hints { - kinds.insert(Some(InlayHintKind::Type)); - } - if self.show_parameter_hints { - kinds.insert(Some(InlayHintKind::Parameter)); - } - if self.show_other_hints { - kinds.insert(None); +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, params, _cx| { + let language_settings_content_ref = generator + .subschema_for::() + .to_value(); + replace_subschema::(generator, || json_schema!({ + "type": "object", + "properties": params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + language_settings_content_ref.clone(), + ) + }) + .collect::>() + })) } - kinds } } @@ -1282,186 +573,142 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr ); } -/// The kind of an inlay hint. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum InlayHintKind { - /// An inlay hint for a type. - Type, - /// An inlay hint for a parameter. - Parameter, -} - -impl InlayHintKind { - /// Returns the [`InlayHintKind`] from the given name. - /// - /// Returns `None` if `name` does not match any of the expected - /// string representations. - pub fn from_name(name: &str) -> Option { - match name { - "type" => Some(InlayHintKind::Type), - "parameter" => Some(InlayHintKind::Parameter), - _ => None, - } - } - - /// Returns the name of this [`InlayHintKind`]. - pub fn name(&self) -> &'static str { - match self { - InlayHintKind::Type => "type", - InlayHintKind::Parameter => "parameter", - } - } -} - impl settings::Settings for AllLanguageSettings { - type FileContent = AllLanguageSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - let default_value = sources.default; - - // A default is provided for all settings. - let mut defaults: LanguageSettings = - serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let all_languages = &content.project.all_languages; + let defaults = all_languages.defaults.clone(); + let inlay_hints = defaults.inlay_hints.unwrap(); + let completions = defaults.completions.unwrap(); + let prettier = defaults.prettier.unwrap(); + let indent_guides = defaults.indent_guides.unwrap(); + let tasks = defaults.tasks.unwrap(); + + let default_language_settings = LanguageSettings { + tab_size: defaults.tab_size.unwrap(), + hard_tabs: defaults.hard_tabs.unwrap(), + soft_wrap: defaults.soft_wrap.unwrap(), + preferred_line_length: defaults.preferred_line_length.unwrap(), + show_wrap_guides: defaults.show_wrap_guides.unwrap(), + wrap_guides: defaults.wrap_guides.unwrap(), + indent_guides: IndentGuideSettings { + enabled: indent_guides.enabled.unwrap(), + line_width: indent_guides.line_width.unwrap(), + active_line_width: indent_guides.active_line_width.unwrap(), + coloring: indent_guides.coloring.unwrap(), + background_coloring: indent_guides.background_coloring.unwrap(), + }, + format_on_save: defaults.format_on_save.unwrap(), + remove_trailing_whitespace_on_save: defaults + .remove_trailing_whitespace_on_save + .unwrap(), + ensure_final_newline_on_save: defaults.ensure_final_newline_on_save.unwrap(), + formatter: defaults.formatter.unwrap(), + prettier: PrettierSettings { + allowed: prettier.allowed.unwrap(), + parser: prettier.parser, + plugins: prettier.plugins, + options: prettier.options, + }, + jsx_tag_auto_close: defaults.jsx_tag_auto_close.unwrap().enabled.unwrap(), + enable_language_server: defaults.enable_language_server.unwrap(), + language_servers: defaults.language_servers.unwrap(), + allow_rewrap: defaults.allow_rewrap.unwrap(), + show_edit_predictions: defaults.show_edit_predictions.unwrap(), + edit_predictions_disabled_in: defaults.edit_predictions_disabled_in.unwrap(), + show_whitespaces: defaults.show_whitespaces.unwrap(), + whitespace_map: defaults.whitespace_map.unwrap(), + extend_comment_on_newline: defaults.extend_comment_on_newline.unwrap(), + inlay_hints: InlayHintSettings { + enabled: inlay_hints.enabled.unwrap(), + show_value_hints: inlay_hints.show_value_hints.unwrap(), + show_type_hints: inlay_hints.show_type_hints.unwrap(), + show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(), + show_other_hints: inlay_hints.show_other_hints.unwrap(), + show_background: inlay_hints.show_background.unwrap(), + edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(), + scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(), + toggle_on_modifiers_press: inlay_hints.toggle_on_modifiers_press, + }, + use_autoclose: defaults.use_autoclose.unwrap(), + use_auto_surround: defaults.use_auto_surround.unwrap(), + use_on_type_format: defaults.use_on_type_format.unwrap(), + auto_indent: defaults.auto_indent.unwrap(), + auto_indent_on_paste: defaults.auto_indent_on_paste.unwrap(), + always_treat_brackets_as_autoclosed: defaults + .always_treat_brackets_as_autoclosed + .unwrap(), + code_actions_on_format: defaults.code_actions_on_format.unwrap(), + linked_edits: defaults.linked_edits.unwrap(), + tasks: LanguageTaskSettings { + variables: tasks.variables, + enabled: tasks.enabled.unwrap(), + prefer_lsp: tasks.prefer_lsp.unwrap(), + }, + show_completions_on_input: defaults.show_completions_on_input.unwrap(), + show_completion_documentation: defaults.show_completion_documentation.unwrap(), + completions: CompletionSettings { + words: completions.words.unwrap(), + words_min_length: completions.words_min_length.unwrap(), + lsp: completions.lsp.unwrap(), + lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(), + lsp_insert_mode: completions.lsp_insert_mode.unwrap(), + }, + debuggers: defaults.debuggers.unwrap(), + }; let mut languages = HashMap::default(); - for (language_name, settings) in &default_value.languages.0 { - let mut language_settings = defaults.clone(); + for (language_name, settings) in &all_languages.languages.0 { + let mut language_settings = default_language_settings.clone(); merge_settings(&mut language_settings, settings); - languages.insert(language_name.clone(), language_settings); + languages.insert(LanguageName(language_name.clone()), language_settings); } - let mut edit_prediction_provider = default_value + let edit_prediction_provider = all_languages .features .as_ref() .and_then(|f| f.edit_prediction_provider); - let mut edit_predictions_mode = default_value - .edit_predictions - .as_ref() - .map(|edit_predictions| edit_predictions.mode) - .ok_or_else(Self::missing_default)?; - let mut completion_globs: HashSet<&String> = default_value - .edit_predictions - .as_ref() - .and_then(|c| c.disabled_globs.as_ref()) - .map(|globs| globs.iter().collect()) - .ok_or_else(Self::missing_default)?; + let edit_predictions = all_languages.edit_predictions.clone().unwrap(); + let edit_predictions_mode = edit_predictions.mode.unwrap(); - let mut copilot_settings = default_value - .edit_predictions + let disabled_globs: HashSet<&String> = edit_predictions + .disabled_globs .as_ref() - .map(|settings| CopilotSettings { - proxy: settings.copilot.proxy.clone(), - proxy_no_verify: settings.copilot.proxy_no_verify, - enterprise_uri: settings.copilot.enterprise_uri.clone(), - }) - .unwrap_or_default(); + .unwrap() + .iter() + .collect(); - let mut enabled_in_text_threads = default_value - .edit_predictions - .as_ref() - .map(|settings| settings.enabled_in_text_threads) - .unwrap_or(true); + let copilot = edit_predictions.copilot.unwrap(); + let copilot_settings = CopilotSettings { + proxy: copilot.proxy, + proxy_no_verify: copilot.proxy_no_verify, + enterprise_uri: copilot.enterprise_uri, + }; + + let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap(); let mut file_types: FxHashMap, GlobSet> = FxHashMap::default(); + let mut file_globs: FxHashMap, Vec> = FxHashMap::default(); - for (language, patterns) in &default_value.file_types { + for (language, patterns) in &all_languages.file_types { let mut builder = GlobSetBuilder::new(); for pattern in patterns { - builder.add(Glob::new(pattern)?); - } - - file_types.insert(language.clone(), builder.build()?); - } - - for user_settings in sources.customizations() { - if let Some(provider) = user_settings - .features - .as_ref() - .and_then(|f| f.edit_prediction_provider) - { - edit_prediction_provider = Some(provider); - } - - if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() { - edit_predictions_mode = edit_predictions.mode; - enabled_in_text_threads = edit_predictions.enabled_in_text_threads; - - if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() { - completion_globs.extend(disabled_globs.iter()); - } - } - - if let Some(proxy) = user_settings - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.proxy.clone()) - { - copilot_settings.proxy = Some(proxy); - } - - if let Some(proxy_no_verify) = user_settings - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.proxy_no_verify) - { - copilot_settings.proxy_no_verify = Some(proxy_no_verify); - } - - if let Some(enterprise_uri) = user_settings - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.enterprise_uri.clone()) - { - copilot_settings.enterprise_uri = Some(enterprise_uri); - } - - // A user's global settings override the default global settings and - // all default language-specific settings. - merge_settings(&mut defaults, &user_settings.defaults); - for language_settings in languages.values_mut() { - merge_settings(language_settings, &user_settings.defaults); - } - - // A user's language-specific settings override default language-specific settings. - for (language_name, user_language_settings) in &user_settings.languages.0 { - merge_settings( - languages - .entry(language_name.clone()) - .or_insert_with(|| defaults.clone()), - user_language_settings, - ); + builder.add(Glob::new(pattern).unwrap()); } - for (language, patterns) in &user_settings.file_types { - let mut builder = GlobSetBuilder::new(); - - let default_value = default_value.file_types.get(&language.clone()); - - // Merge the default value with the user's value. - if let Some(patterns) = default_value { - for pattern in patterns { - builder.add(Glob::new(pattern)?); - } - } - - for pattern in patterns { - builder.add(Glob::new(pattern)?); - } - - file_types.insert(language.clone(), builder.build()?); - } + file_types.insert(language.clone(), builder.build().unwrap()); + file_globs.insert(language.clone(), patterns.clone()); } - Ok(Self { + Self { edit_predictions: EditPredictionSettings { provider: if let Some(provider) = edit_prediction_provider { provider } else { EditPredictionProvider::None }, - disabled_globs: completion_globs + disabled_globs: disabled_globs .iter() .filter_map(|g| { let expanded_g = shellexpand::tilde(g).into_owned(); @@ -1475,14 +722,118 @@ impl settings::Settings for AllLanguageSettings { copilot: copilot_settings, enabled_in_text_threads, }, - defaults, + defaults: default_language_settings, languages, file_types, - }) + file_globs, + } + } + + fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { + let all_languages = &content.project.all_languages; + if let Some(provider) = all_languages + .features + .as_ref() + .and_then(|f| f.edit_prediction_provider) + { + self.edit_predictions.provider = provider; + } + + if let Some(edit_predictions) = all_languages.edit_predictions.as_ref() { + self.edit_predictions + .mode + .merge_from(&edit_predictions.mode); + self.edit_predictions + .enabled_in_text_threads + .merge_from(&edit_predictions.enabled_in_text_threads); + + if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() { + self.edit_predictions + .disabled_globs + .extend(disabled_globs.iter().filter_map(|g| { + let expanded_g = shellexpand::tilde(g).into_owned(); + Some(DisabledGlob { + matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(), + is_absolute: Path::new(&expanded_g).is_absolute(), + }) + })); + } + } + + if let Some(proxy) = all_languages + .edit_predictions + .as_ref() + .and_then(|settings| settings.copilot.as_ref()?.proxy.clone()) + { + self.edit_predictions.copilot.proxy = Some(proxy); + } + + if let Some(proxy_no_verify) = all_languages + .edit_predictions + .as_ref() + .and_then(|settings| settings.copilot.as_ref()?.proxy_no_verify) + { + self.edit_predictions.copilot.proxy_no_verify = Some(proxy_no_verify); + } + + if let Some(enterprise_uri) = all_languages + .edit_predictions + .as_ref() + .and_then(|settings| settings.copilot.as_ref()?.enterprise_uri.clone()) + { + self.edit_predictions.copilot.enterprise_uri = Some(enterprise_uri); + } + + // A user's global settings override the default global settings and + // all default language-specific settings. + merge_settings(&mut self.defaults, &all_languages.defaults); + for language_settings in self.languages.values_mut() { + merge_settings(language_settings, &all_languages.defaults); + } + + // A user's language-specific settings override default language-specific settings. + for (language_name, user_language_settings) in &all_languages.languages.0 { + merge_settings( + self.languages + .entry(LanguageName(language_name.clone())) + .or_insert_with(|| self.defaults.clone()), + user_language_settings, + ); + } + + for (language, patterns) in &all_languages.file_types { + let mut builder = GlobSetBuilder::new(); + + let default_value = self.file_globs.get(&language.clone()); + + // Merge the default value with the user's value. + if let Some(patterns) = default_value { + for pattern in patterns { + if let Some(glob) = Glob::new(pattern).log_err() { + builder.add(glob); + } + } + } + + for pattern in patterns { + if let Some(glob) = Glob::new(pattern).log_err() { + builder.add(glob); + } + } + + self.file_globs + .entry(language.clone()) + .or_default() + .extend(patterns.clone()); + + if let Some(matcher) = builder.build().log_err() { + self.file_types.insert(language.clone(), matcher); + } + } } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - let d = &mut current.defaults; + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { + let d = &mut current.project.all_languages.defaults; if let Some(size) = vscode .read_value("editor.tabSize") .and_then(|v| v.as_u64()) @@ -1511,14 +862,7 @@ impl settings::Settings for AllLanguageSettings { d.wrap_guides = arr; } if let Some(b) = vscode.read_bool("editor.guides.indentation") { - if let Some(guide_settings) = d.indent_guides.as_mut() { - guide_settings.enabled = b; - } else { - d.indent_guides = Some(IndentGuideSettings { - enabled: b, - ..Default::default() - }); - } + d.indent_guides.get_or_insert_default().enabled = Some(b); } if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") { @@ -1568,17 +912,7 @@ impl settings::Settings for AllLanguageSettings { } else { WordsCompletionMode::Disabled }; - if let Some(completion_settings) = d.completions.as_mut() { - completion_settings.words = mode; - } else { - d.completions = Some(CompletionSettings { - words: mode, - words_min_length: 3, - lsp: true, - lsp_fetch_timeout_ms: 0, - lsp_insert_mode: LspInsertMode::ReplaceSuffix, - }); - } + d.completions.get_or_insert_default().words = Some(mode); } // TODO: pull ^ out into helper and reuse for per-language settings @@ -1595,7 +929,11 @@ impl settings::Settings for AllLanguageSettings { } // TODO: do we want to merge imported globs per filetype? for now we'll just replace - current.file_types.extend(associations); + current + .project + .all_languages + .file_types + .extend(associations); // cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs if let Some(disabled_globs) = vscode @@ -1603,6 +941,8 @@ impl settings::Settings for AllLanguageSettings { .and_then(|v| v.as_array()) { current + .project + .all_languages .edit_predictions .get_or_insert_default() .disabled_globs @@ -1618,166 +958,129 @@ impl settings::Settings for AllLanguageSettings { } fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { - fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; - } - } - - merge(&mut settings.tab_size, src.tab_size); + settings.tab_size.merge_from(&src.tab_size); settings.tab_size = settings .tab_size .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap()); - merge(&mut settings.hard_tabs, src.hard_tabs); - merge(&mut settings.soft_wrap, src.soft_wrap); - merge(&mut settings.use_autoclose, src.use_autoclose); - merge(&mut settings.use_auto_surround, src.use_auto_surround); - merge(&mut settings.use_on_type_format, src.use_on_type_format); - merge(&mut settings.auto_indent, src.auto_indent); - merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste); - merge( - &mut settings.always_treat_brackets_as_autoclosed, - src.always_treat_brackets_as_autoclosed, - ); - merge(&mut settings.show_wrap_guides, src.show_wrap_guides); - merge(&mut settings.wrap_guides, src.wrap_guides.clone()); - merge(&mut settings.indent_guides, src.indent_guides); - merge( - &mut settings.code_actions_on_format, - src.code_actions_on_format.clone(), - ); - merge(&mut settings.linked_edits, src.linked_edits); - merge(&mut settings.tasks, src.tasks.clone()); - - merge( - &mut settings.preferred_line_length, - src.preferred_line_length, - ); - merge(&mut settings.formatter, src.formatter.clone()); - merge(&mut settings.prettier, src.prettier.clone()); - merge( - &mut settings.jsx_tag_auto_close, - src.jsx_tag_auto_close.clone(), - ); - merge(&mut settings.format_on_save, src.format_on_save.clone()); - merge( - &mut settings.remove_trailing_whitespace_on_save, - src.remove_trailing_whitespace_on_save, - ); - merge( - &mut settings.ensure_final_newline_on_save, - src.ensure_final_newline_on_save, - ); - merge( - &mut settings.enable_language_server, - src.enable_language_server, - ); - merge(&mut settings.language_servers, src.language_servers.clone()); - merge(&mut settings.allow_rewrap, src.allow_rewrap); - merge( - &mut settings.show_edit_predictions, - src.show_edit_predictions, - ); - merge( - &mut settings.edit_predictions_disabled_in, - src.edit_predictions_disabled_in.clone(), - ); - merge(&mut settings.show_whitespaces, src.show_whitespaces); - merge(&mut settings.whitespace_map, src.whitespace_map.clone()); - merge( - &mut settings.extend_comment_on_newline, - src.extend_comment_on_newline, - ); - merge(&mut settings.inlay_hints, src.inlay_hints); - merge( - &mut settings.show_completions_on_input, - src.show_completions_on_input, - ); - merge( - &mut settings.show_completion_documentation, - src.show_completion_documentation, - ); - merge(&mut settings.completions, src.completions); -} - -/// Allows to enable/disable formatting with Prettier -/// and configure default Prettier, used when no project-level Prettier installation is found. -/// Prettier formatting is disabled by default. -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi)] -pub struct PrettierSettings { - /// Enables or disables formatting with Prettier for a given language. - #[serde(default)] - pub allowed: bool, - - /// Forces Prettier integration to use a specific parser name when formatting files with the language. - #[serde(default)] - pub parser: Option, - - /// Forces Prettier integration to use specific plugins when formatting files with the language. - /// The default Prettier will be installed with these plugins. - #[serde(default)] - #[settings_ui(skip)] - pub plugins: HashSet, - - /// Default Prettier options, in the format as in package.json section for Prettier. - /// If project installs Prettier via its package.json, these options will be ignored. - #[serde(flatten)] - #[settings_ui(skip)] - pub options: HashMap, + settings.hard_tabs.merge_from(&src.hard_tabs); + settings.soft_wrap.merge_from(&src.soft_wrap); + settings.use_autoclose.merge_from(&src.use_autoclose); + settings + .use_auto_surround + .merge_from(&src.use_auto_surround); + settings + .use_on_type_format + .merge_from(&src.use_on_type_format); + settings.auto_indent.merge_from(&src.auto_indent); + settings + .auto_indent_on_paste + .merge_from(&src.auto_indent_on_paste); + settings + .always_treat_brackets_as_autoclosed + .merge_from(&src.always_treat_brackets_as_autoclosed); + settings.show_wrap_guides.merge_from(&src.show_wrap_guides); + settings.wrap_guides.merge_from(&src.wrap_guides); + settings.indent_guides.merge_from(&src.indent_guides); + settings + .code_actions_on_format + .merge_from(&src.code_actions_on_format.clone()); + settings.linked_edits.merge_from(&src.linked_edits); + settings.tasks.merge_from(&src.tasks); + + settings + .preferred_line_length + .merge_from(&src.preferred_line_length); + settings.formatter.merge_from(&src.formatter.clone()); + settings.prettier.merge_from(&src.prettier.clone()); + settings + .jsx_tag_auto_close + .merge_from(&src.jsx_tag_auto_close.as_ref().and_then(|v| v.enabled)); + settings + .format_on_save + .merge_from(&src.format_on_save.clone()); + settings + .remove_trailing_whitespace_on_save + .merge_from(&src.remove_trailing_whitespace_on_save); + settings + .ensure_final_newline_on_save + .merge_from(&src.ensure_final_newline_on_save); + settings + .enable_language_server + .merge_from(&src.enable_language_server); + settings + .language_servers + .merge_from(&src.language_servers.clone()); + settings.allow_rewrap.merge_from(&src.allow_rewrap); + settings + .show_edit_predictions + .merge_from(&src.show_edit_predictions); + settings + .edit_predictions_disabled_in + .merge_from(&src.edit_predictions_disabled_in.clone()); + settings.show_whitespaces.merge_from(&src.show_whitespaces); + settings + .whitespace_map + .merge_from(&src.whitespace_map.clone()); + settings + .extend_comment_on_newline + .merge_from(&src.extend_comment_on_newline); + if let Some(inlay_hints) = &src.inlay_hints { + settings + .inlay_hints + .enabled + .merge_from(&inlay_hints.enabled); + settings + .inlay_hints + .show_value_hints + .merge_from(&inlay_hints.show_value_hints); + settings + .inlay_hints + .show_type_hints + .merge_from(&inlay_hints.show_type_hints); + settings + .inlay_hints + .show_parameter_hints + .merge_from(&inlay_hints.show_parameter_hints); + settings + .inlay_hints + .show_other_hints + .merge_from(&inlay_hints.show_other_hints); + settings + .inlay_hints + .show_background + .merge_from(&inlay_hints.show_background); + settings + .inlay_hints + .edit_debounce_ms + .merge_from(&inlay_hints.edit_debounce_ms); + settings + .inlay_hints + .scroll_debounce_ms + .merge_from(&inlay_hints.scroll_debounce_ms); + if let Some(toggle_on_modifiers_press) = &inlay_hints.toggle_on_modifiers_press { + settings.inlay_hints.toggle_on_modifiers_press = Some(*toggle_on_modifiers_press); + } + } + settings + .show_completions_on_input + .merge_from(&src.show_completions_on_input); + settings + .show_completion_documentation + .merge_from(&src.show_completion_documentation); + settings.completions.merge_from(&src.completions); } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi)] +#[derive(Default, Debug, Clone, PartialEq, Eq, SettingsUi)] pub struct JsxTagAutoCloseSettings { /// Enables or disables auto-closing of JSX tags. - #[serde(default)] pub enabled: bool, } #[cfg(test)] mod tests { - use gpui::TestAppContext; - use super::*; - - #[test] - fn test_formatter_deserialization() { - let raw_auto = "{\"formatter\": \"auto\"}"; - let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap(); - assert_eq!(settings.formatter, Some(SelectedFormatter::Auto)); - let raw = "{\"formatter\": \"language_server\"}"; - let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); - assert_eq!( - settings.formatter, - Some(SelectedFormatter::List(FormatterList::Single( - Formatter::LanguageServer { name: None } - ))) - ); - let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}"; - let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); - assert_eq!( - settings.formatter, - Some(SelectedFormatter::List(FormatterList::Vec(vec![ - Formatter::LanguageServer { name: None } - ]))) - ); - let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}"; - let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); - assert_eq!( - settings.formatter, - Some(SelectedFormatter::List(FormatterList::Vec(vec![ - Formatter::LanguageServer { name: None }, - Formatter::Prettier - ]))) - ); - } - - #[test] - fn test_formatter_deserialization_invalid() { - let raw_auto = "{\"formatter\": {}}"; - let result: Result = serde_json::from_str(raw_auto); - assert!(result.is_err()); - } + use gpui::TestAppContext; #[gpui::test] fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) { diff --git a/crates/language_model/Cargo.toml b/crates/language_model/Cargo.toml index d4513f617b0d9f79e960c6cec6ca1a5dd806cea6..a85283cf121bc10a82e1022071d6a136dd5716f5 100644 --- a/crates/language_model/Cargo.toml +++ b/crates/language_model/Cargo.toml @@ -35,6 +35,7 @@ proto.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true +settings.workspace = true smol.workspace = true telemetry_events.workspace = true thiserror.workspace = true diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index fb35a1cae109490620268b11382c3070a6ef6da2..418a864648e10bf36073f00392a189f45d474534 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -21,6 +21,7 @@ use open_router::OpenRouterError; use parking_lot::Mutex; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; +pub use settings::LanguageModelCacheConfiguration; use std::ops::{Add, Sub}; use std::str::FromStr; use std::sync::Arc; @@ -62,14 +63,6 @@ pub fn init_settings(cx: &mut App) { registry::init(cx); } -/// Configuration for caching language model messages. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct LanguageModelCacheConfiguration { - pub max_cache_anchors: usize, - pub should_speculate: bool, - pub min_total_token: u64, -} - /// A completion event from a language model. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum LanguageModelCompletionEvent { diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index f928dcbcb5c598b6cd3f5ba9def9da29311ebdf6..c9cd56f6328f3070cafe2b7d7ff58f8e5315a3da 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -18,8 +18,6 @@ use language_model::{ LanguageModelToolResultContent, MessageContent, RateLimiter, Role, }; use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr; @@ -30,6 +28,8 @@ use ui::{Icon, IconName, List, Tooltip, prelude::*}; use util::{ResultExt, truncate_and_trailoff}; use zed_env_vars::{EnvVar, env_var}; +pub use settings::AnthropicAvailableModel as AvailableModel; + const PROVIDER_ID: LanguageModelProviderId = language_model::ANTHROPIC_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::ANTHROPIC_PROVIDER_NAME; @@ -40,55 +40,6 @@ pub struct AnthropicSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc - pub name: String, - /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. - pub display_name: Option, - /// The model's context window size. - pub max_tokens: u64, - /// A model `name` to substitute when calling tools, in case the primary model doesn't support tool calling. - pub tool_override: Option, - /// Configuration of Anthropic's caching API. - pub cache_configuration: Option, - pub max_output_tokens: Option, - pub default_temperature: Option, - #[serde(default)] - pub extra_beta_headers: Vec, - /// The model's mode (e.g. thinking) - pub mode: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum ModelMode { - #[default] - Default, - Thinking { - /// The maximum number of tokens to use for reasoning. Must be lower than the model's `max_output_tokens`. - budget_tokens: Option, - }, -} - -impl From for AnthropicModelMode { - fn from(value: ModelMode) -> Self { - match value { - ModelMode::Default => AnthropicModelMode::Default, - ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens }, - } - } -} - -impl From for ModelMode { - fn from(value: AnthropicModelMode) -> Self { - match value { - AnthropicModelMode::Default => ModelMode::Default, - AnthropicModelMode::Thinking { budget_tokens } => ModelMode::Thinking { budget_tokens }, - } - } -} - pub struct AnthropicLanguageModelProvider { http_client: Arc, state: gpui::Entity, @@ -237,7 +188,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider { max_output_tokens: model.max_output_tokens, default_temperature: model.default_temperature, extra_beta_headers: model.extra_beta_headers.clone(), - mode: model.mode.clone().unwrap_or_default().into(), + mode: model.mode.unwrap_or_default().into(), }, ); } diff --git a/crates/language_models/src/provider/bedrock.rs b/crates/language_models/src/provider/bedrock.rs index 49a976d5b18d2c7a2ca3162c632f53706b385cb0..a81e4b30d21f6ecdefc9361017c2d6fd979acced 100644 --- a/crates/language_models/src/provider/bedrock.rs +++ b/crates/language_models/src/provider/bedrock.rs @@ -42,7 +42,7 @@ use language_model::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use settings::{Settings, SettingsStore}; +use settings::{BedrockAvailableModel as AvailableModel, Settings, SettingsStore}; use smol::lock::OnceCell; use strum::{EnumIter, IntoEnumIterator, IntoStaticStr}; use theme::ThemeSettings; @@ -83,15 +83,14 @@ pub enum BedrockAuthMethod { Automatic, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub cache_configuration: Option, - pub max_output_tokens: Option, - pub default_temperature: Option, - pub mode: Option, +impl From for BedrockAuthMethod { + fn from(value: settings::BedrockAuthMethodContent) -> Self { + match value { + settings::BedrockAuthMethodContent::SingleSignOn => BedrockAuthMethod::SingleSignOn, + settings::BedrockAuthMethodContent::Automatic => BedrockAuthMethod::Automatic, + settings::BedrockAuthMethodContent::NamedProfile => BedrockAuthMethod::NamedProfile, + } + } } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 24a4ba6cc260a91de170bb665c86756c8d7a25ca..c62a6989501a71e444b07992bff0cbe1a1bbd6d6 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -32,6 +32,8 @@ use release_channel::AppVersion; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use settings::SettingsStore; +pub use settings::ZedDotDevAvailableModel as AvailableModel; +pub use settings::ZedDotDevAvailableProvider as AvailableProvider; use smol::io::{AsyncReadExt, BufReader}; use std::pin::Pin; use std::str::FromStr as _; @@ -52,42 +54,6 @@ const PROVIDER_NAME: LanguageModelProviderName = language_model::ZED_CLOUD_PROVI pub struct ZedDotDevSettings { pub available_models: Vec, } - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -pub enum AvailableProvider { - Anthropic, - OpenAi, - Google, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - /// The provider of the language model. - pub provider: AvailableProvider, - /// The model's name in the provider's API. e.g. claude-3-5-sonnet-20240620 - pub name: String, - /// The name displayed in the UI, such as in the assistant panel model dropdown menu. - pub display_name: Option, - /// The size of the context window, indicating the maximum number of tokens the model can process. - pub max_tokens: usize, - /// The maximum number of output tokens allowed by the model. - pub max_output_tokens: Option, - /// The maximum number of completion tokens allowed by the model (o1-* only) - pub max_completion_tokens: Option, - /// Override this model with a different Anthropic model for tool calls. - pub tool_override: Option, - /// Indicates whether this custom model supports caching. - pub cache_configuration: Option, - /// The default temperature to use for this model. - pub default_temperature: Option, - /// Any extra beta headers to provide when using the model. - #[serde(default)] - pub extra_beta_headers: Vec, - /// The model's mode (e.g. thinking) - pub mode: Option, -} - #[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ModelMode { diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index 0775f26b0bcaf0cd88bb80934baf8710c5c996ef..a8f08a420664b10e4478df0b566382621ffeb760 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -16,8 +16,7 @@ use language_model::{ LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::DeepseekAvailableModel as AvailableModel; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr; @@ -47,15 +46,6 @@ pub struct DeepSeekSettings { pub api_url: String, pub available_models: Vec, } - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, -} - pub struct DeepSeekLanguageModelProvider { http_client: Arc, state: Entity, diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index 140968b917db497a04c4185857d6a44da0003a65..70a7a27defdfba609765710902845f921a8333ac 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -24,6 +24,7 @@ use language_model::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +pub use settings::GoogleAvailableModel as AvailableModel; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::sync::{ @@ -60,32 +61,6 @@ pub enum ModelMode { }, } -impl From for GoogleModelMode { - fn from(value: ModelMode) -> Self { - match value { - ModelMode::Default => GoogleModelMode::Default, - ModelMode::Thinking { budget_tokens } => GoogleModelMode::Thinking { budget_tokens }, - } - } -} - -impl From for ModelMode { - fn from(value: GoogleModelMode) -> Self { - match value { - GoogleModelMode::Default => ModelMode::Default, - GoogleModelMode::Thinking { budget_tokens } => ModelMode::Thinking { budget_tokens }, - } - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - name: String, - display_name: Option, - max_tokens: u64, - mode: Option, -} - pub struct GoogleLanguageModelProvider { http_client: Arc, state: gpui::Entity, @@ -234,7 +209,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider { name: model.name.clone(), display_name: model.display_name.clone(), max_tokens: model.max_tokens, - mode: model.mode.unwrap_or_default().into(), + mode: model.mode.unwrap_or_default(), }, ); } diff --git a/crates/language_models/src/provider/lmstudio.rs b/crates/language_models/src/provider/lmstudio.rs index e6e1726ff5b1f0a2edd51d5fde32da9734e56f9d..f98906f5be1cc811e124c89cf61050fdf98c967d 100644 --- a/crates/language_models/src/provider/lmstudio.rs +++ b/crates/language_models/src/provider/lmstudio.rs @@ -15,8 +15,7 @@ use language_model::{ LanguageModelRequest, RateLimiter, Role, }; use lmstudio::{ModelType, get_models}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::LmStudioAvailableModel as AvailableModel; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr; @@ -40,15 +39,6 @@ pub struct LmStudioSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub supports_tool_calls: bool, - pub supports_images: bool, -} - pub struct LmStudioLanguageModelProvider { http_client: Arc, state: gpui::Entity, diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index 682d3549b8ae720ca28629293beb720798d9b583..b3375e528c29052f3e5451e20c40abf0fe8a10ad 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -15,8 +15,7 @@ use language_model::{ RateLimiter, Role, StopReason, TokenUsage, }; use mistral::{MISTRAL_API_URL, StreamResponse}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::MistralAvailableModel as AvailableModel; use settings::{Settings, SettingsStore}; use std::collections::HashMap; use std::pin::Pin; @@ -42,18 +41,6 @@ pub struct MistralSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, - pub max_completion_tokens: Option, - pub supports_tools: Option, - pub supports_images: Option, - pub supports_thinking: Option, -} - pub struct MistralLanguageModelProvider { http_client: Arc, state: gpui::Entity, diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index 4932d9507027296003dd8ae095318718e9019334..ff4a8d6c2c3b7b3cb11f6f597e129ca769459bc0 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -13,12 +13,10 @@ use language_model::{ }; use menu; use ollama::{ - ChatMessage, ChatOptions, ChatRequest, ChatResponseDelta, KeepAlive, OLLAMA_API_URL, - OllamaFunctionCall, OllamaFunctionTool, OllamaToolCall, get_models, show_model, - stream_chat_completion, + ChatMessage, ChatOptions, ChatRequest, ChatResponseDelta, OLLAMA_API_URL, OllamaFunctionCall, + OllamaFunctionTool, OllamaToolCall, get_models, show_model, stream_chat_completion, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::OllamaAvailableModel as AvailableModel; use settings::{Settings, SettingsStore, update_settings_file}; use std::pin::Pin; use std::sync::LazyLock; @@ -48,24 +46,6 @@ pub struct OllamaSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - /// The model name in the Ollama API (e.g. "llama3.2:latest") - pub name: String, - /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. - pub display_name: Option, - /// The Context Length parameter to the model (aka num_ctx or n_ctx) - pub max_tokens: u64, - /// The number of seconds to keep the connection open after the last request - pub keep_alive: Option, - /// Whether the model supports tools - pub supports_tools: Option, - /// Whether the model supports vision - pub supports_images: Option, - /// Whether to enable think mode - pub supports_thinking: Option, -} - pub struct OllamaLanguageModelProvider { http_client: Arc, state: gpui::Entity, @@ -703,15 +683,13 @@ impl ConfigurationView { let current_url = OllamaLanguageModelProvider::api_url(cx); if !api_url.is_empty() && &api_url != ¤t_url { let fs = ::global(cx); - update_settings_file::(fs, cx, move |settings, _| { - if let Some(settings) = settings.ollama.as_mut() { - settings.api_url = Some(api_url); - } else { - settings.ollama = Some(crate::settings::OllamaSettingsContent { - api_url: Some(api_url), - available_models: None, - }); - } + update_settings_file(fs, cx, move |settings, _| { + settings + .language_models + .get_or_insert_default() + .ollama + .get_or_insert_default() + .api_url = Some(api_url); }); } } @@ -720,8 +698,12 @@ impl ConfigurationView { self.api_url_editor .update(cx, |input, cx| input.set_text("", window, cx)); let fs = ::global(cx); - update_settings_file::(fs, cx, |settings, _cx| { - if let Some(settings) = settings.ollama.as_mut() { + update_settings_file(fs, cx, |settings, _cx| { + if let Some(settings) = settings + .language_models + .as_mut() + .and_then(|models| models.ollama.as_mut()) + { settings.api_url = Some(OLLAMA_API_URL.into()); } }); diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index ceb8d1144cdb73d4dde3e1cffdfdb780e6ff888e..ade2e47ca39ebfdc48806380b541ed7d97d4d3da 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -15,9 +15,7 @@ use menu; use open_ai::{ ImageUrl, Model, OPEN_AI_API_URL, ReasoningEffort, ResponseStreamEvent, stream_completion, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::{OpenAiAvailableModel as AvailableModel, Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr as _; use std::sync::{Arc, LazyLock}; @@ -41,16 +39,6 @@ pub struct OpenAiSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, - pub max_completion_tokens: Option, - pub reasoning_effort: Option, -} - pub struct OpenAiLanguageModelProvider { http_client: Arc, state: gpui::Entity, diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs index 9a64a6d340b500d651dc88d3138e75f7c5d98528..788a412a8232d43e92ec9195132efe21cf73bf00 100644 --- a/crates/language_models/src/provider/open_ai_compatible.rs +++ b/crates/language_models/src/provider/open_ai_compatible.rs @@ -11,8 +11,6 @@ use language_model::{ }; use menu; use open_ai::{ResponseStreamEvent, stream_completion}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use ui::{ElevationIndex, Tooltip, prelude::*}; @@ -22,6 +20,8 @@ use zed_env_vars::EnvVar; use crate::api_key::ApiKeyState; use crate::provider::open_ai::{OpenAiEventMapper, into_open_ai}; +pub use settings::OpenAiCompatibleAvailableModel as AvailableModel; +pub use settings::OpenAiCompatibleModelCapabilities as ModelCapabilities; #[derive(Default, Clone, Debug, PartialEq)] pub struct OpenAiCompatibleSettings { @@ -29,36 +29,6 @@ pub struct OpenAiCompatibleSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, - pub max_completion_tokens: Option, - #[serde(default)] - pub capabilities: ModelCapabilities, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct ModelCapabilities { - pub tools: bool, - pub images: bool, - pub parallel_tool_calls: bool, - pub prompt_cache_key: bool, -} - -impl Default for ModelCapabilities { - fn default() -> Self { - Self { - tools: true, - images: false, - parallel_tool_calls: false, - prompt_cache_key: false, - } - } -} - pub struct OpenAiCompatibleLanguageModelProvider { id: LanguageModelProviderId, name: LanguageModelProviderName, diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index 170beab5dae84423501127835a7f560d142fe00a..bbc5c44cdc7835b4402543371d4677520bdcf645 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -14,12 +14,9 @@ use language_model::{ LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, }; use open_router::{ - Model, ModelMode as OpenRouterModelMode, OPEN_ROUTER_API_URL, Provider, ResponseStreamEvent, - list_models, + Model, ModelMode as OpenRouterModelMode, OPEN_ROUTER_API_URL, ResponseStreamEvent, list_models, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::{OpenRouterAvailableModel as AvailableModel, Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr as _; use std::sync::{Arc, LazyLock}; @@ -42,51 +39,6 @@ pub struct OpenRouterSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, - pub max_completion_tokens: Option, - pub supports_tools: Option, - pub supports_images: Option, - pub mode: Option, - pub provider: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum ModelMode { - #[default] - Default, - Thinking { - budget_tokens: Option, - }, -} - -impl From for OpenRouterModelMode { - fn from(value: ModelMode) -> Self { - match value { - ModelMode::Default => OpenRouterModelMode::Default, - ModelMode::Thinking { budget_tokens } => { - OpenRouterModelMode::Thinking { budget_tokens } - } - } - } -} - -impl From for ModelMode { - fn from(value: OpenRouterModelMode) -> Self { - match value { - OpenRouterModelMode::Default => ModelMode::Default, - OpenRouterModelMode::Thinking { budget_tokens } => { - ModelMode::Thinking { budget_tokens } - } - } - } -} - pub struct OpenRouterLanguageModelProvider { http_client: Arc, state: gpui::Entity, @@ -259,7 +211,7 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider { max_tokens: model.max_tokens, supports_tools: model.supports_tools, supports_images: model.supports_images, - mode: model.mode.clone().unwrap_or_default().into(), + mode: model.mode.unwrap_or_default(), provider: model.provider.clone(), }); } diff --git a/crates/language_models/src/provider/vercel.rs b/crates/language_models/src/provider/vercel.rs index 6fce79fd85462ed8a8e45d342b43ee83270adc25..4b2ebb8a165e4edd6f10908e85a04538b5f3a92c 100644 --- a/crates/language_models/src/provider/vercel.rs +++ b/crates/language_models/src/provider/vercel.rs @@ -10,8 +10,7 @@ use language_model::{ LanguageModelToolChoice, RateLimiter, Role, }; use open_ai::ResponseStreamEvent; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::VercelAvailableModel as AvailableModel; use settings::{Settings, SettingsStore}; use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; @@ -29,21 +28,12 @@ const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new( const API_KEY_ENV_VAR_NAME: &str = "VERCEL_API_KEY"; static API_KEY_ENV_VAR: LazyLock = env_var!(API_KEY_ENV_VAR_NAME); -#[derive(Default, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct VercelSettings { pub api_url: String, pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, - pub max_completion_tokens: Option, -} - pub struct VercelLanguageModelProvider { http_client: Arc, state: gpui::Entity, diff --git a/crates/language_models/src/provider/x_ai.rs b/crates/language_models/src/provider/x_ai.rs index 51b59a04b3eea21e45926a3383893cdde09a7b8f..d75c8ce78c6c9ee86bf838739047470999a85bfe 100644 --- a/crates/language_models/src/provider/x_ai.rs +++ b/crates/language_models/src/provider/x_ai.rs @@ -10,8 +10,7 @@ use language_model::{ LanguageModelToolChoice, LanguageModelToolSchemaFormat, RateLimiter, Role, }; use open_ai::ResponseStreamEvent; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::XaiAvailableModel as AvailableModel; use settings::{Settings, SettingsStore}; use std::sync::{Arc, LazyLock}; use strum::IntoEnumIterator; @@ -35,15 +34,6 @@ pub struct XAiSettings { pub available_models: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct AvailableModel { - pub name: String, - pub display_name: Option, - pub max_tokens: u64, - pub max_output_tokens: Option, - pub max_completion_tokens: Option, -} - pub struct XAiLanguageModelProvider { http_client: Arc, state: gpui::Entity, diff --git a/crates/language_models/src/settings.rs b/crates/language_models/src/settings.rs index cfe66c91a36d4da562cba84363f79bd1d5b4e1ce..30b208a170794a0d4018e8ebce39f1020b4c9b29 100644 --- a/crates/language_models/src/settings.rs +++ b/crates/language_models/src/settings.rs @@ -1,27 +1,16 @@ use std::sync::Arc; -use anyhow::Result; use collections::HashMap; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; +use util::MergeFrom; use crate::provider::{ - self, - anthropic::AnthropicSettings, - bedrock::AmazonBedrockSettings, - cloud::{self, ZedDotDevSettings}, - deepseek::DeepSeekSettings, - google::GoogleSettings, - lmstudio::LmStudioSettings, - mistral::MistralSettings, - ollama::OllamaSettings, - open_ai::OpenAiSettings, - open_ai_compatible::OpenAiCompatibleSettings, - open_router::OpenRouterSettings, - vercel::VercelSettings, - x_ai::XAiSettings, + anthropic::AnthropicSettings, bedrock::AmazonBedrockSettings, cloud::ZedDotDevSettings, + deepseek::DeepSeekSettings, google::GoogleSettings, lmstudio::LmStudioSettings, + mistral::MistralSettings, ollama::OllamaSettings, open_ai::OpenAiSettings, + open_ai_compatible::OpenAiCompatibleSettings, open_router::OpenRouterSettings, + vercel::VercelSettings, x_ai::XAiSettings, }; /// Initializes the language model settings. @@ -29,7 +18,6 @@ pub fn init_settings(cx: &mut App) { AllLanguageModelSettings::register(cx); } -#[derive(Default)] pub struct AllLanguageModelSettings { pub anthropic: AnthropicSettings, pub bedrock: AmazonBedrockSettings, @@ -46,281 +34,197 @@ pub struct AllLanguageModelSettings { pub zed_dot_dev: ZedDotDevSettings, } -#[derive( - Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, SettingsUi, SettingsKey, -)] -#[settings_key(key = "language_models")] -pub struct AllLanguageModelSettingsContent { - pub anthropic: Option, - pub bedrock: Option, - pub deepseek: Option, - pub google: Option, - pub lmstudio: Option, - pub mistral: Option, - pub ollama: Option, - pub open_router: Option, - pub openai: Option, - pub openai_compatible: Option, OpenAiCompatibleSettingsContent>>, - pub vercel: Option, - pub x_ai: Option, - #[serde(rename = "zed.dev")] - pub zed_dot_dev: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct AnthropicSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct AmazonBedrockSettingsContent { - available_models: Option>, - endpoint_url: Option, - region: Option, - profile: Option, - authentication_method: Option, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct OllamaSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct LmStudioSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct DeepseekSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct MistralSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct OpenAiSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct OpenAiCompatibleSettingsContent { - pub api_url: String, - pub available_models: Vec, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct VercelSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct GoogleSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct XAiSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct ZedDotDevSettingsContent { - available_models: Option>, -} - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct OpenRouterSettingsContent { - pub api_url: Option, - pub available_models: Option>, -} - impl settings::Settings for AllLanguageModelSettings { const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]); - type FileContent = AllLanguageModelSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; - } + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let language_models = content.language_models.clone().unwrap(); + let anthropic = language_models.anthropic.unwrap(); + let bedrock = language_models.bedrock.unwrap(); + let deepseek = language_models.deepseek.unwrap(); + let google = language_models.google.unwrap(); + let lmstudio = language_models.lmstudio.unwrap(); + let mistral = language_models.mistral.unwrap(); + let ollama = language_models.ollama.unwrap(); + let open_router = language_models.open_router.unwrap(); + let openai = language_models.openai.unwrap(); + let openai_compatible = language_models.openai_compatible.unwrap(); + let vercel = language_models.vercel.unwrap(); + let x_ai = language_models.x_ai.unwrap(); + let zed_dot_dev = language_models.zed_dot_dev.unwrap(); + Self { + anthropic: AnthropicSettings { + api_url: anthropic.api_url.unwrap(), + available_models: anthropic.available_models.unwrap_or_default(), + }, + bedrock: AmazonBedrockSettings { + available_models: bedrock.available_models.unwrap_or_default(), + region: bedrock.region, + endpoint: bedrock.endpoint_url, // todo(should be api_url) + profile_name: bedrock.profile, + role_arn: None, // todo(was never a setting for this...) + authentication_method: bedrock.authentication_method.map(Into::into), + }, + deepseek: DeepSeekSettings { + api_url: deepseek.api_url.unwrap(), + available_models: deepseek.available_models.unwrap_or_default(), + }, + google: GoogleSettings { + api_url: google.api_url.unwrap(), + available_models: google.available_models.unwrap_or_default(), + }, + lmstudio: LmStudioSettings { + api_url: lmstudio.api_url.unwrap(), + available_models: lmstudio.available_models.unwrap_or_default(), + }, + mistral: MistralSettings { + api_url: mistral.api_url.unwrap(), + available_models: mistral.available_models.unwrap_or_default(), + }, + ollama: OllamaSettings { + api_url: ollama.api_url.unwrap(), + available_models: ollama.available_models.unwrap_or_default(), + }, + open_router: OpenRouterSettings { + api_url: open_router.api_url.unwrap(), + available_models: open_router.available_models.unwrap_or_default(), + }, + openai: OpenAiSettings { + api_url: openai.api_url.unwrap(), + available_models: openai.available_models.unwrap_or_default(), + }, + openai_compatible: openai_compatible + .into_iter() + .map(|(key, value)| { + ( + key, + OpenAiCompatibleSettings { + api_url: value.api_url, + available_models: value.available_models, + }, + ) + }) + .collect(), + vercel: VercelSettings { + api_url: vercel.api_url.unwrap(), + available_models: vercel.available_models.unwrap_or_default(), + }, + x_ai: XAiSettings { + api_url: x_ai.api_url.unwrap(), + available_models: x_ai.available_models.unwrap_or_default(), + }, + zed_dot_dev: ZedDotDevSettings { + available_models: zed_dot_dev.available_models.unwrap_or_default(), + }, } + } - let mut settings = AllLanguageModelSettings::default(); - - for value in sources.defaults_and_customizations() { - // Anthropic - let anthropic = value.anthropic.clone(); - merge( - &mut settings.anthropic.api_url, - anthropic.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.anthropic.available_models, - anthropic.as_ref().and_then(|s| s.available_models.clone()), - ); - - // Bedrock - let bedrock = value.bedrock.clone(); - merge( - &mut settings.bedrock.profile_name, - bedrock.as_ref().map(|s| s.profile.clone()), - ); - merge( - &mut settings.bedrock.authentication_method, - bedrock.as_ref().map(|s| s.authentication_method.clone()), - ); - merge( - &mut settings.bedrock.region, - bedrock.as_ref().map(|s| s.region.clone()), - ); - merge( - &mut settings.bedrock.endpoint, - bedrock.as_ref().map(|s| s.endpoint_url.clone()), - ); - - // Ollama - let ollama = value.ollama.clone(); - - merge( - &mut settings.ollama.api_url, - value.ollama.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.ollama.available_models, - ollama.as_ref().and_then(|s| s.available_models.clone()), - ); - - // LM Studio - let lmstudio = value.lmstudio.clone(); - - merge( - &mut settings.lmstudio.api_url, - value.lmstudio.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.lmstudio.available_models, - lmstudio.as_ref().and_then(|s| s.available_models.clone()), - ); + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(models) = content.language_models.as_ref() else { + return; + }; - // DeepSeek - let deepseek = value.deepseek.clone(); + if let Some(anthropic) = models.anthropic.as_ref() { + self.anthropic + .available_models + .merge_from(&anthropic.available_models); + self.anthropic.api_url.merge_from(&anthropic.api_url); + } - merge( - &mut settings.deepseek.api_url, - value.deepseek.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.deepseek.available_models, - deepseek.as_ref().and_then(|s| s.available_models.clone()), - ); + if let Some(bedrock) = models.bedrock.clone() { + self.bedrock + .available_models + .merge_from(&bedrock.available_models); - // OpenAI - let openai = value.openai.clone(); - merge( - &mut settings.openai.api_url, - openai.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.openai.available_models, - openai.as_ref().and_then(|s| s.available_models.clone()), - ); + if let Some(endpoint_url) = bedrock.endpoint_url { + self.bedrock.endpoint = Some(endpoint_url) + } - // OpenAI Compatible - if let Some(openai_compatible) = value.openai_compatible.clone() { - for (id, openai_compatible_settings) in openai_compatible { - settings.openai_compatible.insert( - id, - OpenAiCompatibleSettings { - api_url: openai_compatible_settings.api_url, - available_models: openai_compatible_settings.available_models, - }, - ); - } + if let Some(region) = bedrock.region { + self.bedrock.region = Some(region) } - // Vercel - let vercel = value.vercel.clone(); - merge( - &mut settings.vercel.api_url, - vercel.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.vercel.available_models, - vercel.as_ref().and_then(|s| s.available_models.clone()), - ); + if let Some(profile_name) = bedrock.profile { + self.bedrock.profile_name = Some(profile_name); + } - // XAI - let x_ai = value.x_ai.clone(); - merge( - &mut settings.x_ai.api_url, - x_ai.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.x_ai.available_models, - x_ai.as_ref().and_then(|s| s.available_models.clone()), - ); + if let Some(auth_method) = bedrock.authentication_method { + self.bedrock.authentication_method = Some(auth_method.into()); + } + } - // ZedDotDev - merge( - &mut settings.zed_dot_dev.available_models, - value - .zed_dot_dev - .as_ref() - .and_then(|s| s.available_models.clone()), - ); - merge( - &mut settings.google.api_url, - value.google.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.google.available_models, - value - .google - .as_ref() - .and_then(|s| s.available_models.clone()), - ); + if let Some(deepseek) = models.deepseek.as_ref() { + self.deepseek + .available_models + .merge_from(&deepseek.available_models); + self.deepseek.api_url.merge_from(&deepseek.api_url); + } - // Mistral - let mistral = value.mistral.clone(); - merge( - &mut settings.mistral.api_url, - mistral.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.mistral.available_models, - mistral.as_ref().and_then(|s| s.available_models.clone()), - ); + if let Some(google) = models.google.as_ref() { + self.google + .available_models + .merge_from(&google.available_models); + self.google.api_url.merge_from(&google.api_url); + } - // OpenRouter - let open_router = value.open_router.clone(); - merge( - &mut settings.open_router.api_url, - open_router.as_ref().and_then(|s| s.api_url.clone()), - ); - merge( - &mut settings.open_router.available_models, - open_router - .as_ref() - .and_then(|s| s.available_models.clone()), - ); + if let Some(lmstudio) = models.lmstudio.as_ref() { + self.lmstudio + .available_models + .merge_from(&lmstudio.available_models); + self.lmstudio.api_url.merge_from(&lmstudio.api_url); } - Ok(settings) + if let Some(mistral) = models.mistral.as_ref() { + self.mistral + .available_models + .merge_from(&mistral.available_models); + self.mistral.api_url.merge_from(&mistral.api_url); + } + if let Some(ollama) = models.ollama.as_ref() { + self.ollama + .available_models + .merge_from(&ollama.available_models); + self.ollama.api_url.merge_from(&ollama.api_url); + } + if let Some(open_router) = models.open_router.as_ref() { + self.open_router + .available_models + .merge_from(&open_router.available_models); + self.open_router.api_url.merge_from(&open_router.api_url); + } + if let Some(openai) = models.openai.as_ref() { + self.openai + .available_models + .merge_from(&openai.available_models); + self.openai.api_url.merge_from(&openai.api_url); + } + if let Some(openai_compatible) = models.openai_compatible.clone() { + for (name, value) in openai_compatible { + self.openai_compatible.insert( + name, + OpenAiCompatibleSettings { + api_url: value.api_url, + available_models: value.available_models, + }, + ); + } + } + if let Some(vercel) = models.vercel.as_ref() { + self.vercel + .available_models + .merge_from(&vercel.available_models); + self.vercel.api_url.merge_from(&vercel.api_url); + } + if let Some(x_ai) = models.x_ai.as_ref() { + self.x_ai + .available_models + .merge_from(&x_ai.available_models); + self.x_ai.api_url.merge_from(&x_ai.api_url); + } + if let Some(zed_dot_dev) = models.zed_dot_dev.as_ref() { + self.zed_dot_dev + .available_models + .merge_from(&zed_dot_dev.available_models); + } } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} } diff --git a/crates/languages/src/bash.rs b/crates/languages/src/bash.rs index 0c6ab8cc14eca2ba87fa0b876946bee0b21d479b..8fabd4cf43aa4a79fa868854064942252deb4117 100644 --- a/crates/languages/src/bash.rs +++ b/crates/languages/src/bash.rs @@ -19,7 +19,7 @@ pub(super) fn bash_task_context() -> ContextProviderWithTasks { #[cfg(test)] mod tests { use gpui::{AppContext as _, BorrowAppContext, Context, TestAppContext}; - use language::{AutoindentMode, Buffer, language_settings::AllLanguageSettings}; + use language::{AutoindentMode, Buffer}; use settings::SettingsStore; use std::num::NonZeroU32; use unindent::Unindent; @@ -34,8 +34,8 @@ mod tests { cx.set_global(test_settings); language::init(cx); cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |s| { - s.defaults.tab_size = NonZeroU32::new(2) + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(2) }); }); }); diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index 1e3a1f805885e850884c2e5f5ec18ca71801301f..0af467bcaa8f9b4e6b1094d937358a55db402c60 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -394,7 +394,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option(|store, cx| { - store.update_user_settings::(cx, |s| { - s.defaults.tab_size = NonZeroU32::new(2); + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(2); }); }); }); diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 93a072b2ac5a9f4a6d21729eaad9a852fe95b929..f5a4a8c6f6480de7589f0a418157fafbf5fbe2ed 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -312,7 +312,12 @@ pub fn init(languages: Arc, fs: Arc, node: NodeRuntime cx.update(|cx| { SettingsStore::update_global(cx, |settings, cx| { settings - .set_extension_settings(language_settings.clone(), cx) + .set_extension_settings( + settings::ExtensionsSettingsContent { + all_languages: language_settings.clone(), + }, + cx, + ) .log_err(); }); })?; diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index e0273250c91af8f437bff435d4a0f2f0c6467951..4905cd22cd64488a0f0da9ede6834602fc5834bc 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -2169,7 +2169,7 @@ impl LspInstaller for RuffLspAdapter { #[cfg(test)] mod tests { use gpui::{AppContext as _, BorrowAppContext, Context, TestAppContext}; - use language::{AutoindentMode, Buffer, language_settings::AllLanguageSettings}; + use language::{AutoindentMode, Buffer}; use settings::SettingsStore; use std::num::NonZeroU32; @@ -2182,8 +2182,8 @@ mod tests { cx.set_global(test_settings); language::init(cx); cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |s| { - s.defaults.tab_size = NonZeroU32::new(2); + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(2); }); }); }); diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index e35d870610669f0e09cb28e15841d1105d129cca..f5401f448c324f11c2de3a6379c704df5441b3db 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -1051,7 +1051,6 @@ mod tests { use super::*; use crate::language; use gpui::{BorrowAppContext, Hsla, TestAppContext}; - use language::language_settings::AllLanguageSettings; use lsp::CompletionItemLabelDetails; use settings::SettingsStore; use theme::SyntaxTheme; @@ -1381,8 +1380,8 @@ mod tests { cx.set_global(test_settings); language::init(cx); cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |s| { - s.defaults.tab_size = NonZeroU32::new(2); + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(2); }); }); }); diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index c019bad1d081e59077bedb05cb36673a48f294bc..b4cb2a2503dcb6e097c34e4c8aad718f89e30272 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -1,6 +1,6 @@ use assets::Assets; use gpui::{Application, Entity, KeyBinding, StyleRefinement, WindowOptions, prelude::*, rgb}; -use language::{LanguageRegistry, language_settings::AllLanguageSettings}; +use language::LanguageRegistry; use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use node_runtime::NodeRuntime; use settings::SettingsStore; @@ -39,9 +39,6 @@ pub fn main() { let store = SettingsStore::test(cx); cx.set_global(store); language::init(cx); - SettingsStore::update(cx, |store, cx| { - store.update_user_settings::(cx, |_| {}); - }); cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]); let node_runtime = NodeRuntime::unavailable(); diff --git a/crates/markdown/examples/markdown_as_child.rs b/crates/markdown/examples/markdown_as_child.rs index 6c0960c88b3021b6012b769aef8ffb22050b1abd..784047dc3dbd4130f0464b3e13be940b6634fedc 100644 --- a/crates/markdown/examples/markdown_as_child.rs +++ b/crates/markdown/examples/markdown_as_child.rs @@ -1,6 +1,6 @@ use assets::Assets; use gpui::{Application, Entity, KeyBinding, Length, StyleRefinement, WindowOptions, rgb}; -use language::{LanguageRegistry, language_settings::AllLanguageSettings}; +use language::LanguageRegistry; use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use node_runtime::NodeRuntime; use settings::SettingsStore; @@ -23,9 +23,6 @@ pub fn main() { let store = SettingsStore::test(cx); cx.set_global(store); language::init(cx); - SettingsStore::update(cx, |store, cx| { - store.update_user_settings::(cx, |_| {}); - }); cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]); let node_runtime = NodeRuntime::unavailable(); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 1d4c26cf126089261e3534af1da9083d2c126093..2ceeffc89061aa429727142a1659a392d6374b09 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -17,11 +17,11 @@ use gpui::{App, AppContext as _, Context, Entity, EntityId, EventEmitter, Task}; use itertools::Itertools; use language::{ AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier, - CharKind, Chunk, CursorShape, DiagnosticEntry, DiskState, File, IndentSize, Language, - LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, - TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, TreeSitterOptions, - Unclipped, - language_settings::{IndentGuideSettings, LanguageSettings, language_settings}, + CharKind, Chunk, CursorShape, DiagnosticEntry, DiskState, File, IndentGuideSettings, + IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, + PointUtf16, Selection, TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, + TreeSitterOptions, Unclipped, + language_settings::{LanguageSettings, language_settings}, }; use rope::DimensionPair; @@ -5913,7 +5913,7 @@ impl MultiBufferSnapshot { end_row: last_row, depth: next_depth, tab_size, - settings: settings.indent_guides, + settings: settings.indent_guides.clone(), }); } } diff --git a/crates/ollama/Cargo.toml b/crates/ollama/Cargo.toml index 2765f234009c99ba4c9feb6e4a9fac804da7e5a1..0cf1a5505d8035808b8c3f2a0407557535b15008 100644 --- a/crates/ollama/Cargo.toml +++ b/crates/ollama/Cargo.toml @@ -22,4 +22,5 @@ http_client.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true +settings.workspace = true workspace-hack.workspace = true diff --git a/crates/ollama/src/ollama.rs b/crates/ollama/src/ollama.rs index dfafac02d8b25176f1aff5191487a12be914aab1..dced37e0fc1e19e61bba5e14010812f08fe3a1e5 100644 --- a/crates/ollama/src/ollama.rs +++ b/crates/ollama/src/ollama.rs @@ -3,33 +3,11 @@ use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::B use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, http}; use serde::{Deserialize, Serialize}; use serde_json::Value; +pub use settings::KeepAlive; use std::time::Duration; pub const OLLAMA_API_URL: &str = "http://localhost:11434"; -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] -#[serde(untagged)] -pub enum KeepAlive { - /// Keep model alive for N seconds - Seconds(isize), - /// Keep model alive for a fixed duration. Accepts durations like "5m", "10m", "1h", "1d", etc. - Duration(String), -} - -impl KeepAlive { - /// Keep model alive until a new model is loaded or until Ollama shuts down - fn indefinite() -> Self { - Self::Seconds(-1) - } -} - -impl Default for KeepAlive { - fn default() -> Self { - Self::indefinite() - } -} - #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub struct Model { diff --git a/crates/onboarding/Cargo.toml b/crates/onboarding/Cargo.toml index 4157be31723fe66eb8f12ddf581c292e8320be78..3354b985449e0e389f6c02f748ddf26630ff193c 100644 --- a/crates/onboarding/Cargo.toml +++ b/crates/onboarding/Cargo.toml @@ -45,3 +45,6 @@ workspace-hack.workspace = true workspace.workspace = true zed_actions.workspace = true zlog.workspace = true + +[dev-dependencies] +db = {workspace = true, features = ["test-support"]} diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index 3631ad00dfb8662d5d4142a4cbd11186c1b1b137..6ed5e5e240201752938ea7e589c9f98b674b919c 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -264,8 +264,8 @@ pub(crate) fn render_ai_setup_page( ); let fs = ::global(cx); - update_settings_file::(fs, cx, move |ai_settings, _| { - ai_settings.disable_ai = Some(enabled); + update_settings_file(fs, cx, move |settings, _| { + settings.disable_ai = Some(enabled); }); }, ) diff --git a/crates/onboarding/src/base_keymap_picker.rs b/crates/onboarding/src/base_keymap_picker.rs index 950ed1d3133549a566808bd7cee002437708a2ad..63a2894a93504bd658dbf8199534efddf4746f1d 100644 --- a/crates/onboarding/src/base_keymap_picker.rs +++ b/crates/onboarding/src/base_keymap_picker.rs @@ -186,8 +186,8 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { value = base_keymap.to_string() ); - update_settings_file::(self.fs.clone(), cx, move |setting, _| { - setting.base_keymap = Some(base_keymap) + update_settings_file(self.fs.clone(), cx, move |setting, _| { + setting.base_keymap = Some(base_keymap.into()) }); } diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs index aef9dcca86ce49a70f1a508c0a43614737a653c7..b3b2dfc9af85cd1e3d8b8b2892ad4245fc047ca4 100644 --- a/crates/onboarding/src/basics_page.rs +++ b/crates/onboarding/src/basics_page.rs @@ -194,27 +194,27 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement fn write_mode_change(mode: ThemeMode, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |settings, _cx| { - settings.set_mode(mode); + update_settings_file(fs, cx, move |settings, _cx| { + theme::set_mode(settings, mode); }); } fn write_theme_change(theme: impl Into>, theme_mode: ThemeMode, cx: &mut App) { let fs = ::global(cx); let theme = theme.into(); - update_settings_file::(fs, cx, move |settings, cx| { + update_settings_file(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 { + settings.theme.theme = Some(settings::ThemeSelection::Dynamic { mode: ThemeMode::System, light: ThemeName(light_theme.into()), dark: ThemeName(dark_theme.into()), }); } else { let appearance = *SystemAppearance::global(cx); - settings.set_theme(theme, appearance); + theme::set_theme(settings, theme, appearance); } }); } @@ -247,10 +247,13 @@ fn render_telemetry_section(tab_index: &mut isize, cx: &App) -> impl IntoElement ToggleState::Indeterminate => { return; }, }; - update_settings_file::( + update_settings_file( fs.clone(), cx, - move |setting, _| setting.metrics = Some(enabled), + move |setting, _| { + setting.telemetry.get_or_insert_default().metrics = Some(enabled); + } + , ); // This telemetry event shouldn't fire when it's off. If it does we'll be alerted @@ -286,10 +289,13 @@ fn render_telemetry_section(tab_index: &mut isize, cx: &App) -> impl IntoElement ToggleState::Indeterminate => { return; }, }; - update_settings_file::( + update_settings_file( fs.clone(), cx, - move |setting, _| setting.diagnostics = Some(enabled), + move |setting, _| { + setting.telemetry.get_or_insert_default().diagnostics = Some(enabled); + }, + ); // This telemetry event shouldn't fire when it's off. If it does we'll be alerted @@ -358,8 +364,8 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE fn write_keymap_base(keymap_base: BaseKeymap, cx: &App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |setting, _| { - setting.base_keymap = Some(keymap_base); + update_settings_file(fs, cx, move |setting, _| { + setting.base_keymap = Some(keymap_base.into()); }); telemetry::event!("Welcome Keymap Changed", keymap = keymap_base); @@ -387,7 +393,7 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme return; } }; - update_settings_file::(fs.clone(), cx, move |setting, _| { + update_settings_file(fs.clone(), cx, move |setting, _| { setting.vim_mode = Some(vim_mode); }); diff --git a/crates/onboarding/src/editing_page.rs b/crates/onboarding/src/editing_page.rs index 297016abd4a1499feb6f637d028056ca0b412d31..71b697ebb00ba44449621ee51bd4ac04b9e5c10d 100644 --- a/crates/onboarding/src/editing_page.rs +++ b/crates/onboarding/src/editing_page.rs @@ -34,13 +34,13 @@ fn write_show_mini_map(show: ShowMinimap, cx: &mut App) { curr_settings.minimap.show = show; EditorSettings::override_global(curr_settings, cx); - update_settings_file::(fs, cx, move |editor_settings, _| { + update_settings_file(fs, cx, move |settings, _| { telemetry::event!( "Welcome Minimap Clicked", - from = editor_settings.minimap.unwrap_or_default(), + from = settings.editor.minimap.clone().unwrap_or_default(), to = show ); - editor_settings.minimap.get_or_insert_default().show = Some(show); + settings.editor.minimap.get_or_insert_default().show = Some(show); }); } @@ -58,86 +58,80 @@ fn write_inlay_hints(enabled: bool, cx: &mut App) { curr_settings.defaults.inlay_hints.enabled = enabled; AllLanguageSettings::override_global(curr_settings, cx); - update_settings_file::(fs, cx, move |all_language_settings, cx| { - all_language_settings + update_settings_file(fs, cx, move |settings, _cx| { + settings + .project + .all_languages .defaults .inlay_hints - .get_or_insert_with(|| { - AllLanguageSettings::get_global(cx) - .clone() - .defaults - .inlay_hints - }) - .enabled = enabled; + .get_or_insert_default() + .enabled = Some(enabled); }); } fn read_git_blame(cx: &App) -> bool { - ProjectSettings::get_global(cx).git.inline_blame_enabled() + ProjectSettings::get_global(cx).git.inline_blame.enabled } fn write_git_blame(enabled: bool, cx: &mut App) { let fs = ::global(cx); let mut curr_settings = ProjectSettings::get_global(cx).clone(); - curr_settings - .git - .inline_blame - .get_or_insert_default() - .enabled = enabled; + curr_settings.git.inline_blame.enabled = enabled; ProjectSettings::override_global(curr_settings, cx); - update_settings_file::(fs, cx, move |project_settings, _| { - project_settings + update_settings_file(fs, cx, move |settings, _| { + settings .git + .get_or_insert_default() .inline_blame .get_or_insert_default() - .enabled = enabled; + .enabled = Some(enabled); }); } fn write_ui_font_family(font: SharedString, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |theme_settings, _| { + update_settings_file(fs, cx, move |settings, _| { telemetry::event!( "Welcome Font Changed", type = "ui font", - old = theme_settings.ui_font_family, + old = settings.theme.ui_font_family, new = font ); - theme_settings.ui_font_family = Some(FontFamilyName(font.into())); + settings.theme.ui_font_family = Some(FontFamilyName(font.into())); }); } fn write_ui_font_size(size: Pixels, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |theme_settings, _| { - theme_settings.ui_font_size = Some(size.into()); + update_settings_file(fs, cx, move |settings, _| { + settings.theme.ui_font_size = Some(size.into()); }); } fn write_buffer_font_size(size: Pixels, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |theme_settings, _| { - theme_settings.buffer_font_size = Some(size.into()); + update_settings_file(fs, cx, move |settings, _| { + settings.theme.buffer_font_size = Some(size.into()); }); } fn write_buffer_font_family(font_family: SharedString, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |theme_settings, _| { + update_settings_file(fs, cx, move |settings, _| { telemetry::event!( "Welcome Font Changed", type = "editor font", - old = theme_settings.buffer_font_family, + old = settings.theme.buffer_font_family, new = font_family ); - theme_settings.buffer_font_family = Some(FontFamilyName(font_family.into())); + settings.theme.buffer_font_family = Some(FontFamilyName(font_family.into())); }); } @@ -153,8 +147,9 @@ fn write_font_ligatures(enabled: bool, cx: &mut App) { let fs = ::global(cx); let bit = if enabled { 1 } else { 0 }; - update_settings_file::(fs, cx, move |theme_settings, _| { - let mut features = theme_settings + update_settings_file(fs, cx, move |settings, _| { + let mut features = settings + .theme .buffer_font_features .as_mut() .map(|features| features.tag_value_list().to_vec()) @@ -166,7 +161,7 @@ fn write_font_ligatures(enabled: bool, cx: &mut App) { features.push(("calt".into(), bit)); } - theme_settings.buffer_font_features = Some(FontFeatures(Arc::new(features))); + settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features))); }); } @@ -180,8 +175,8 @@ fn read_format_on_save(cx: &App) -> bool { fn write_format_on_save(format_on_save: bool, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |language_settings, _| { - language_settings.defaults.format_on_save = Some(match format_on_save { + update_settings_file(fs, cx, move |settings, _| { + settings.project.all_languages.defaults.format_on_save = Some(match format_on_save { true => FormatOnSave::On, false => FormatOnSave::Off, }); diff --git a/crates/open_ai/Cargo.toml b/crates/open_ai/Cargo.toml index bae00f0a8e888390cc8be4909e752567b94d1a27..776e308c490bf464c641800399ffaf8a1f301702 100644 --- a/crates/open_ai/Cargo.toml +++ b/crates/open_ai/Cargo.toml @@ -23,5 +23,6 @@ schemars = { workspace = true, optional = true } log.workspace = true serde.workspace = true serde_json.workspace = true +settings.workspace = true strum.workspace = true workspace-hack.workspace = true diff --git a/crates/open_ai/src/open_ai.rs b/crates/open_ai/src/open_ai.rs index fda0544be1748f3bf958cd159bc55edccdbb5c14..1cada03a60c54668d2675c2e076345a9507fcb43 100644 --- a/crates/open_ai/src/open_ai.rs +++ b/crates/open_ai/src/open_ai.rs @@ -3,6 +3,7 @@ use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::B use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; use serde::{Deserialize, Serialize}; use serde_json::Value; +pub use settings::OpenAiReasoningEffort as ReasoningEffort; use std::{convert::TryFrom, future::Future}; use strum::EnumIter; @@ -278,16 +279,6 @@ pub enum ToolChoice { Other(ToolDefinition), } -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -#[serde(rename_all = "lowercase")] -pub enum ReasoningEffort { - Minimal, - Low, - Medium, - High, -} - #[derive(Clone, Deserialize, Serialize, Debug)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ToolDefinition { diff --git a/crates/open_router/Cargo.toml b/crates/open_router/Cargo.toml index 54348ea5a598d5c92a30ddd60ea68d3fa7b6046f..f9728673bd891488b90c062acf7cb507bb9e329f 100644 --- a/crates/open_router/Cargo.toml +++ b/crates/open_router/Cargo.toml @@ -22,7 +22,7 @@ http_client.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true -thiserror.workspace = true +settings.workspace = true strum.workspace = true -util.workspace = true +thiserror.workspace = true workspace-hack.workspace = true diff --git a/crates/open_router/src/open_router.rs b/crates/open_router/src/open_router.rs index 2e5f33b161b5947eceb64c9b2c2c4ae81116b813..0081c877756dab46433481ac58f2180877e7667f 100644 --- a/crates/open_router/src/open_router.rs +++ b/crates/open_router/src/open_router.rs @@ -3,10 +3,13 @@ use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::B use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, http}; use serde::{Deserialize, Serialize}; use serde_json::Value; +pub use settings::DataCollection; +pub use settings::ModelMode; +pub use settings::OpenRouterAvailableModel as AvailableModel; +pub use settings::OpenRouterProvider as Provider; use std::{convert::TryFrom, io, time::Duration}; use strum::EnumString; use thiserror::Error; -use util::serde::default_true; pub const OPEN_ROUTER_API_URL: &str = "https://openrouter.ai/api/v1"; @@ -65,41 +68,6 @@ impl From for String { } } -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum DataCollection { - Allow, - Disallow, -} - -impl Default for DataCollection { - fn default() -> Self { - Self::Allow - } -} - -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Provider { - #[serde(skip_serializing_if = "Option::is_none")] - order: Option>, - #[serde(default = "default_true")] - allow_fallbacks: bool, - #[serde(default)] - require_parameters: bool, - #[serde(default)] - data_collection: DataCollection, - #[serde(skip_serializing_if = "Option::is_none")] - only: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - ignore: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - quantizations: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - sort: Option, -} - #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub struct Model { @@ -113,16 +81,6 @@ pub struct Model { pub provider: Option, } -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub enum ModelMode { - #[default] - Default, - Thinking { - budget_tokens: Option, - }, -} - impl Model { pub fn default_fast() -> Self { Self::new( diff --git a/crates/outline_panel/Cargo.toml b/crates/outline_panel/Cargo.toml index 6950929304fb37e1f0d140adc2b461188cbeaaf1..b851ae672df0ba4a99b25e19c8c2ebaf49676346 100644 --- a/crates/outline_panel/Cargo.toml +++ b/crates/outline_panel/Cargo.toml @@ -26,7 +26,6 @@ log.workspace = true menu.workspace = true outline.workspace = true project.workspace = true -schemars.workspace = true search.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index bde35a44cb06967a71e585203f3e5f6ee72f96d5..5fd3103dff842bfbacc612536218ac7367b27ec4 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -38,7 +38,7 @@ use std::{ u32, }; -use outline_panel_settings::{OutlinePanelDockPosition, OutlinePanelSettings, ShowIndentGuides}; +use outline_panel_settings::{DockSide, OutlinePanelSettings, ShowIndentGuides}; use project::{File, Fs, GitEntry, GitTraversal, Project, ProjectItem}; use search::{BufferSearchBar, ProjectSearchView}; use serde::{Deserialize, Serialize}; @@ -4840,8 +4840,8 @@ impl Panel for OutlinePanel { fn position(&self, _: &Window, cx: &App) -> DockPosition { match OutlinePanelSettings::get_global(cx).dock { - OutlinePanelDockPosition::Left => DockPosition::Left, - OutlinePanelDockPosition::Right => DockPosition::Right, + DockSide::Left => DockPosition::Left, + DockSide::Right => DockPosition::Right, } } @@ -4850,17 +4850,13 @@ impl Panel for OutlinePanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| { - let dock = match position { - DockPosition::Left | DockPosition::Bottom => OutlinePanelDockPosition::Left, - DockPosition::Right => OutlinePanelDockPosition::Right, - }; - settings.dock = Some(dock); - }, - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + let dock = match position { + DockPosition::Left | DockPosition::Bottom => DockSide::Left, + DockPosition::Right => DockSide::Right, + }; + settings.outline_panel.get_or_insert_default().dock = Some(dock); + }); } fn size(&self, _: &Window, cx: &App) -> Pixels { diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index db34a318188fc10b100b88f959b1c7a77b1161cb..7652c633f1e1166f2515e0338cba3323d6017fb3 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -1,29 +1,14 @@ use editor::EditorSettings; use gpui::{App, Pixels}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +pub use settings::{DockSide, Settings, ShowIndentGuides}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; +use util::MergeFrom; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum OutlinePanelDockPosition { - Left, - Right, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ShowIndentGuides { - Always, - Never, -} - -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct OutlinePanelSettings { pub button: bool, pub default_width: Pixels, - pub dock: OutlinePanelDockPosition, + pub dock: DockSide, pub file_icons: bool, pub folder_icons: bool, pub git_status: bool, @@ -35,7 +20,7 @@ pub struct OutlinePanelSettings { pub expand_outlines_with_depth: usize, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ScrollbarSettings { /// When to show the scrollbar in the project panel. /// @@ -43,80 +28,11 @@ pub struct ScrollbarSettings { pub show: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ScrollbarSettingsContent { - /// When to show the scrollbar in the project panel. - /// - /// Default: inherits editor scrollbar settings - pub show: Option>, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct IndentGuidesSettings { pub show: ShowIndentGuides, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct IndentGuidesSettingsContent { - /// When to show the scrollbar in the outline panel. - pub show: Option, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "outline_panel")] -pub struct OutlinePanelSettingsContent { - /// Whether to show the outline panel button in the status bar. - /// - /// Default: true - pub button: Option, - /// Customize default width (in pixels) taken by outline panel - /// - /// Default: 240 - pub default_width: Option, - /// The position of outline panel - /// - /// Default: left - pub dock: Option, - /// Whether to show file icons in the outline panel. - /// - /// Default: true - pub file_icons: Option, - /// Whether to show folder icons or chevrons for directories in the outline panel. - /// - /// Default: true - pub folder_icons: Option, - /// Whether to show the git status in the outline panel. - /// - /// Default: true - pub git_status: Option, - /// Amount of indentation (in pixels) for nested items. - /// - /// Default: 20 - pub indent_size: Option, - /// Whether to reveal it in the outline panel automatically, - /// when a corresponding project entry becomes active. - /// Gitignored entries are never auto revealed. - /// - /// Default: true - pub auto_reveal_entries: Option, - /// Whether to fold directories automatically - /// when directory has only one directory inside. - /// - /// Default: true - pub auto_fold_dirs: Option, - /// Settings related to indent guides in the outline panel. - pub indent_guides: Option, - /// Scrollbar-related settings - pub scrollbar: Option, - /// Default depth to expand outline items in the current file. - /// The default depth to which outline entries are expanded on reveal. - /// - Set to 0 to collapse all items that have children - /// - Set to 1 or higher to collapse items at that depth or deeper - /// - /// Default: 100 - pub expand_outlines_with_depth: Option, -} - impl ScrollbarVisibility for OutlinePanelSettings { fn visibility(&self, cx: &App) -> ShowScrollbar { self.scrollbar @@ -126,21 +42,69 @@ impl ScrollbarVisibility for OutlinePanelSettings { } impl Settings for OutlinePanelSettings { - type FileContent = OutlinePanelSettingsContent; - - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - sources.json_merge() + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let panel = content.outline_panel.as_ref().unwrap(); + Self { + button: panel.button.unwrap(), + default_width: panel.default_width.map(gpui::px).unwrap(), + dock: panel.dock.unwrap(), + file_icons: panel.file_icons.unwrap(), + folder_icons: panel.folder_icons.unwrap(), + git_status: panel.git_status.unwrap(), + indent_size: panel.indent_size.unwrap(), + indent_guides: IndentGuidesSettings { + show: panel.indent_guides.unwrap().show.unwrap(), + }, + auto_reveal_entries: panel.auto_reveal_entries.unwrap(), + auto_fold_dirs: panel.auto_fold_dirs.unwrap(), + scrollbar: ScrollbarSettings { + show: panel.scrollbar.unwrap().show.flatten().map(Into::into), + }, + expand_outlines_with_depth: panel.expand_outlines_with_depth.unwrap(), + } } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(panel) = content.outline_panel.as_ref() else { + return; + }; + + self.button.merge_from(&panel.button); + self.default_width + .merge_from(&panel.default_width.map(Pixels::from)); + self.dock.merge_from(&panel.dock); + self.file_icons.merge_from(&panel.file_icons); + self.folder_icons.merge_from(&panel.folder_icons); + self.git_status.merge_from(&panel.git_status); + self.indent_size.merge_from(&panel.indent_size); + + if let Some(indent_guides) = panel.indent_guides.as_ref() { + self.indent_guides.show.merge_from(&indent_guides.show); + } + + self.auto_reveal_entries + .merge_from(&panel.auto_reveal_entries); + self.auto_fold_dirs.merge_from(&panel.auto_fold_dirs); + + if let Some(scrollbar) = panel.scrollbar.as_ref() + && let Some(show) = scrollbar.show.flatten() + { + self.scrollbar.show = Some(show.into()) + } + } + fn import_from_vscode( + vscode: &settings::VsCodeSettings, + current: &mut settings::SettingsContent, + ) { if let Some(b) = vscode.read_bool("outline.icons") { - current.file_icons = Some(b); - current.folder_icons = Some(b); + let outline_panel = current.outline_panel.get_or_insert_default(); + outline_panel.file_icons = Some(b); + outline_panel.folder_icons = Some(b); } - vscode.bool_setting("git.decorations.enabled", &mut current.git_status); + if let Some(b) = vscode.read_bool("git.decorations.enabled") { + let outline_panel = current.outline_panel.get_or_insert_default(); + outline_panel.git_status = Some(b); + } } } diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index abe27710d8969d6e365d9a2539855fd625645134..b7d1a75521480353433cf4953960b505201ab3c1 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -22,7 +22,7 @@ use rpc::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{SettingsKey, SettingsSources, SettingsStore, SettingsUi}; +use settings::{SettingsContent, SettingsKey, SettingsStore, SettingsUi}; use util::{ResultExt as _, debug_panic}; use crate::ProjectEnvironment; @@ -989,47 +989,19 @@ impl ExternalAgentServer for LocalCustomAgent { pub const GEMINI_NAME: &'static str = "gemini"; pub const CLAUDE_CODE_NAME: &'static str = "claude"; -#[derive( - Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi, SettingsKey, PartialEq, -)] +#[derive(Default, Clone, JsonSchema, Debug, SettingsUi, SettingsKey, PartialEq)] #[settings_key(key = "agent_servers")] pub struct AllAgentServersSettings { pub gemini: Option, pub claude: Option, - - /// Custom agent servers configured by the user - #[serde(flatten)] pub custom: HashMap, } - -#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +#[derive(Default, Clone, JsonSchema, Debug, PartialEq)] pub struct BuiltinAgentServerSettings { - /// Absolute path to a binary to be used when launching this agent. - /// - /// This can be used to run a specific binary without automatic downloads or searching `$PATH`. - #[serde(rename = "command")] pub path: Option, - /// If a binary is specified in `command`, it will be passed these arguments. pub args: Option>, - /// If a binary is specified in `command`, it will be passed these environment variables. pub env: Option>, - /// Whether to skip searching `$PATH` for an agent server binary when - /// launching this agent. - /// - /// This has no effect if a `command` is specified. Otherwise, when this is - /// `false`, Zed will search `$PATH` for an agent server binary and, if one - /// is found, use it for threads with this agent. If no agent binary is - /// found on `$PATH`, Zed will automatically install and use its own binary. - /// When this is `true`, Zed will not search `$PATH`, and will always use - /// its own binary. - /// - /// Default: true pub ignore_system_version: Option, - /// The default mode to use for this agent. - /// - /// Note: Not only all agents support modes. - /// - /// Default: None pub default_mode: Option, } @@ -1043,6 +1015,18 @@ impl BuiltinAgentServerSettings { } } +impl From for BuiltinAgentServerSettings { + fn from(value: settings::BuiltinAgentServerSettings) -> Self { + BuiltinAgentServerSettings { + path: value.path, + args: value.args, + env: value.env, + ignore_system_version: value.ignore_system_version, + default_mode: value.default_mode, + } + } +} + impl From for BuiltinAgentServerSettings { fn from(value: AgentServerCommand) -> Self { BuiltinAgentServerSettings { @@ -1054,9 +1038,8 @@ impl From for BuiltinAgentServerSettings { } } -#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +#[derive(Clone, JsonSchema, Debug, PartialEq)] pub struct CustomAgentServerSettings { - #[serde(flatten)] pub command: AgentServerCommand, /// The default mode to use for this agent. /// @@ -1066,36 +1049,47 @@ pub struct CustomAgentServerSettings { pub default_mode: Option, } -impl settings::Settings for AllAgentServersSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - let mut settings = AllAgentServersSettings::default(); - - for AllAgentServersSettings { - gemini, - claude, - custom, - } in sources.defaults_and_customizations() - { - if gemini.is_some() { - settings.gemini = gemini.clone(); - } - if claude.is_some() { - settings.claude = claude.clone(); - } +impl From for CustomAgentServerSettings { + fn from(value: settings::CustomAgentServerSettings) -> Self { + CustomAgentServerSettings { + command: AgentServerCommand { + path: value.path, + args: value.args, + env: value.env, + }, + default_mode: value.default_mode, + } + } +} - // Merge custom agents - for (name, config) in custom { - // Skip built-in agent names to avoid conflicts - if name != GEMINI_NAME && name != CLAUDE_CODE_NAME { - settings.custom.insert(name.clone(), config.clone()); - } - } +impl settings::Settings for AllAgentServersSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let agent_settings = content.agent_servers.clone().unwrap(); + Self { + gemini: agent_settings.gemini.map(Into::into), + claude: agent_settings.claude.map(Into::into), + custom: agent_settings + .custom + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), } + } - Ok(settings) + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(content) = &content.agent_servers else { + return; + }; + if let Some(gemini) = content.gemini.clone() { + self.gemini = Some(gemini.into()) + }; + if let Some(claude) = content.claude.clone() { + self.claude = Some(claude.into()); + } + for (name, config) in content.custom.clone() { + self.custom.insert(name, config.into()); + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 7abd9d85fa6395d104a87e6f585a6c8934a84684..70f2bb53d42ed843178dad75eb8d503924fb87f5 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -924,7 +924,7 @@ mod tests { set_context_server_configuration( vec![( server_1_id.0.clone(), - ContextServerSettings::Extension { + settings::ContextServerSettingsContent::Extension { enabled: true, settings: json!({ "somevalue": false @@ -943,7 +943,7 @@ mod tests { set_context_server_configuration( vec![( server_1_id.0.clone(), - ContextServerSettings::Extension { + settings::ContextServerSettingsContent::Extension { enabled: true, settings: json!({ "somevalue": false @@ -970,7 +970,7 @@ mod tests { vec![ ( server_1_id.0.clone(), - ContextServerSettings::Extension { + settings::ContextServerSettingsContent::Extension { enabled: true, settings: json!({ "somevalue": false @@ -979,7 +979,7 @@ mod tests { ), ( server_2_id.0.clone(), - ContextServerSettings::Custom { + settings::ContextServerSettingsContent::Custom { enabled: true, command: ContextServerCommand { path: "somebinary".into(), @@ -1011,7 +1011,7 @@ mod tests { vec![ ( server_1_id.0.clone(), - ContextServerSettings::Extension { + settings::ContextServerSettingsContent::Extension { enabled: true, settings: json!({ "somevalue": false @@ -1020,7 +1020,7 @@ mod tests { ), ( server_2_id.0.clone(), - ContextServerSettings::Custom { + settings::ContextServerSettingsContent::Custom { enabled: true, command: ContextServerCommand { path: "somebinary".into(), @@ -1047,7 +1047,7 @@ mod tests { set_context_server_configuration( vec![( server_1_id.0.clone(), - ContextServerSettings::Extension { + settings::ContextServerSettingsContent::Extension { enabled: true, settings: json!({ "somevalue": false @@ -1070,7 +1070,7 @@ mod tests { set_context_server_configuration( vec![( server_1_id.0.clone(), - ContextServerSettings::Extension { + settings::ContextServerSettingsContent::Extension { enabled: true, settings: json!({ "somevalue": false @@ -1156,7 +1156,7 @@ mod tests { set_context_server_configuration( vec![( server_1_id.0.clone(), - ContextServerSettings::Custom { + settings::ContextServerSettingsContent::Custom { enabled: false, command: ContextServerCommand { path: "somebinary".into(), @@ -1185,7 +1185,7 @@ mod tests { set_context_server_configuration( vec![( server_1_id.0.clone(), - ContextServerSettings::Custom { + settings::ContextServerSettingsContent::Custom { enabled: true, command: ContextServerCommand { path: "somebinary".into(), @@ -1203,18 +1203,17 @@ mod tests { } fn set_context_server_configuration( - context_servers: Vec<(Arc, ContextServerSettings)>, + context_servers: Vec<(Arc, settings::ContextServerSettingsContent)>, cx: &mut TestAppContext, ) { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - let mut settings = ProjectSettings::default(); - for (id, config) in context_servers { - settings.context_servers.insert(id, config); - } - store - .set_user_settings(&serde_json::to_string(&settings).unwrap(), cx) - .unwrap(); + store.update_user_settings(cx, |content| { + content.project.context_servers.clear(); + for (id, config) in context_servers { + content.project.context_servers.insert(id, config); + } + }); }) }); } diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 6c1449b728d3ee5b8c8b019d5e527e9adfb3bf25..d96e5c220fcc98413a42dc46e0889004300d1e2f 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -5,8 +5,10 @@ use super::{ session::{self, Session, SessionStateEvent}, }; use crate::{ - InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState, debugger::session::SessionQuirks, - project_settings::ProjectSettings, worktree_store::WorktreeStore, + InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState, + debugger::session::SessionQuirks, + project_settings::{DapBinary, ProjectSettings}, + worktree_store::WorktreeStore, }; use anyhow::{Context as _, Result, anyhow}; use async_trait::async_trait; @@ -28,8 +30,9 @@ use futures::{ }; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task}; use http_client::HttpClient; -use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind}; +use language::{Buffer, LanguageToolchainStore}; use node_runtime::NodeRuntime; +use settings::InlayHintKind; use remote::RemoteClient; use rpc::{ @@ -208,8 +211,10 @@ impl DapStore { let dap_settings = ProjectSettings::get(Some(settings_location), cx) .dap .get(&adapter.name()); - let user_installed_path = - dap_settings.and_then(|s| s.binary.as_ref().map(PathBuf::from)); + let user_installed_path = dap_settings.and_then(|s| match &s.binary { + DapBinary::Default => None, + DapBinary::Custom(binary) => Some(PathBuf::from(binary)), + }); let user_args = dap_settings.map(|s| s.args.clone()); let delegate = self.delegate(worktree, console, cx); diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 37a97a35ab8300248d6e1b68b628ed6439c5e8e0..14a3f1921c04a6572fb5f4e4535ba4895c556d94 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -5816,7 +5816,8 @@ impl LspStore { buffer.read(cx).file(), cx, ) - .completions; + .completions + .clone(); if !completion_settings.lsp { return Task::ready(Ok(Vec::new())); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index db19aed6135f9de13e83fe07cbbf3c7186c0c603..571a7b5db48eaf3660aadf268d61bf1864140066 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -29,7 +29,6 @@ use context_server_store::ContextServerStore; pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent}; use git::repository::get_git_committer; use git_store::{Repository, RepositoryId}; -use schemars::JsonSchema; pub mod search_history; mod yarn; @@ -101,10 +100,7 @@ use rpc::{ }; use search::{SearchInputKind, SearchQuery, SearchResult}; use search_history::SearchHistory; -use settings::{ - InvalidSettingsError, Settings, SettingsKey, SettingsLocation, SettingsSources, SettingsStore, - SettingsUi, -}; +use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsStore}; use smol::channel::Receiver; use snippet::Snippet; use snippet_provider::SnippetProvider; @@ -980,46 +976,23 @@ pub struct DisableAiSettings { pub disable_ai: bool, } -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Default, - serde::Serialize, - serde::Deserialize, - SettingsUi, - SettingsKey, - JsonSchema, -)] -#[settings_key(None)] -pub struct DisableAiSettingContent { - pub disable_ai: Option, -} - impl settings::Settings for DisableAiSettings { - type FileContent = DisableAiSettingContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - // For security reasons, settings can only make AI restrictions MORE strict, not less. - // (For example, if someone is working on a project that contractually - // requires no AI use, that should override the user's setting which - // permits AI use.) - // This also prevents an attacker from using project or server settings to enable AI when it should be disabled. - let disable_ai = sources - .project - .iter() - .chain(sources.user.iter()) - .chain(sources.server.iter()) - .chain(sources.release_channel.iter()) - .chain(sources.profile.iter()) - .any(|disabled| disabled.disable_ai == Some(true)); + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + Self { + disable_ai: content.disable_ai.unwrap(), + } + } - Ok(Self { disable_ai }) + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + // If disable_ai is true *in any file*, it is disabled. + self.disable_ai = self.disable_ai || content.disable_ai.unwrap_or(false); } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { + } } impl Project { @@ -3271,7 +3244,6 @@ impl Project { ) { self.buffers_needing_diff.insert(buffer.downgrade()); let first_insertion = self.buffers_needing_diff.len() == 1; - let settings = ProjectSettings::get_global(cx); let delay = if let Some(delay) = settings.git.gutter_debounce { delay @@ -5668,146 +5640,50 @@ fn provide_inline_values( mod disable_ai_settings_tests { use super::*; use gpui::TestAppContext; - use settings::{Settings, SettingsSources}; + use settings::Settings; #[gpui::test] async fn test_disable_ai_settings_security(cx: &mut TestAppContext) { - fn disable_setting(value: Option) -> DisableAiSettingContent { - DisableAiSettingContent { disable_ai: value } - } cx.update(|cx| { + settings::init(cx); + Project::init_settings(cx); + // Test 1: Default is false (AI enabled) - let sources = SettingsSources { - default: &DisableAiSettingContent { - disable_ai: Some(false), - }, - global: None, - extensions: None, - user: None, - release_channel: None, - operating_system: None, - profile: None, - server: None, - project: &[], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); - assert!(!settings.disable_ai, "Default should allow AI"); - - // Test 2: Global true, local false -> still disabled (local cannot re-enable) - let global_true = disable_setting(Some(true)); - let local_false = disable_setting(Some(false)); - let sources = SettingsSources { - default: &disable_setting(Some(false)), - global: None, - extensions: None, - user: Some(&global_true), - release_channel: None, - operating_system: None, - profile: None, - server: None, - project: &[&local_false], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); assert!( - settings.disable_ai, - "Local false cannot override global true" + !DisableAiSettings::get_global(cx).disable_ai, + "Default should allow AI" ); + }); - // Test 3: Global false, local true -> disabled (local can make more restrictive) - let global_false = disable_setting(Some(false)); - let local_true = disable_setting(Some(true)); - let sources = SettingsSources { - default: &disable_setting(Some(false)), - global: None, - extensions: None, - user: Some(&global_false), - release_channel: None, - operating_system: None, - profile: None, - server: None, - project: &[&local_true], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); - assert!(settings.disable_ai, "Local true can override global false"); - - // Test 4: Server can only make more restrictive (set to true) - let user_false = disable_setting(Some(false)); - let server_true = disable_setting(Some(true)); - let sources = SettingsSources { - default: &disable_setting(Some(false)), - global: None, - extensions: None, - user: Some(&user_false), - release_channel: None, - operating_system: None, - profile: None, - server: Some(&server_true), - project: &[], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); - assert!( - settings.disable_ai, - "Server can set to true even if user is false" - ); + let disable_true = serde_json::json!({ + "disable_ai": true + }) + .to_string(); + let disable_false = serde_json::json!({ + "disable_ai": false + }) + .to_string(); - // Test 5: Server false cannot override user true - let user_true = disable_setting(Some(true)); - let server_false = disable_setting(Some(false)); - let sources = SettingsSources { - default: &disable_setting(Some(false)), - global: None, - extensions: None, - user: Some(&user_true), - release_channel: None, - operating_system: None, - profile: None, - server: Some(&server_false), - project: &[], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); + cx.update_global::(|store, cx| { + store.set_user_settings(&disable_false, cx).unwrap(); + store.set_global_settings(&disable_true, cx).unwrap(); + }); + cx.update(|cx| { assert!( - settings.disable_ai, - "Server false cannot override user true" + DisableAiSettings::get_global(cx).disable_ai, + "Local false cannot override global true" ); + }); - // Test 6: Multiple local settings, any true disables AI - let global_false = disable_setting(Some(false)); - let local_false3 = disable_setting(Some(false)); - let local_true2 = disable_setting(Some(true)); - let local_false4 = disable_setting(Some(false)); - let sources = SettingsSources { - default: &disable_setting(Some(false)), - global: None, - extensions: None, - user: Some(&global_false), - release_channel: None, - operating_system: None, - profile: None, - server: None, - project: &[&local_false3, &local_true2, &local_false4], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); - assert!(settings.disable_ai, "Any local true should disable AI"); - - // Test 7: All three sources can independently disable AI - let user_false2 = disable_setting(Some(false)); - let server_false2 = disable_setting(Some(false)); - let local_true3 = disable_setting(Some(true)); - let sources = SettingsSources { - default: &disable_setting(Some(false)), - global: None, - extensions: None, - user: Some(&user_false2), - release_channel: None, - operating_system: None, - profile: None, - server: Some(&server_false2), - project: &[&local_true3], - }; - let settings = DisableAiSettings::load(sources, cx).unwrap(); + cx.update_global::(|store, cx| { + store.set_global_settings(&disable_false, cx).unwrap(); + store.set_user_settings(&disable_true, cx).unwrap(); + }); + + cx.update(|cx| { assert!( - settings.disable_ai, - "Local can disable even if user and server are false" + DisableAiSettings::get_global(cx).disable_ai, + "Local false cannot override global true" ); }); } diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 0d3cda3b8fd5beb89ac8aa8ffc119c6e5a5400da..097224e02cd9c1cbbb51ccc6d3d373d3b5a71f85 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -17,18 +17,19 @@ use rpc::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +pub use settings::DirenvSettings; +pub use settings::LspSettings; use settings::{ - InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, - SettingsSources, SettingsStore, SettingsUi, parse_json_with_comments, watch_config_file, + DapSettingsContent, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, + SettingsStore, SettingsUi, parse_json_with_comments, watch_config_file, }; use std::{ - collections::BTreeMap, path::{Path, PathBuf}, sync::Arc, time::Duration, }; use task::{DebugTaskFile, TaskTemplates, VsCodeDebugTaskFile, VsCodeTaskFile}; -use util::{ResultExt, serde::default_true}; +use util::{MergeFrom as _, ResultExt, serde::default_true}; use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId}; use crate::{ @@ -36,8 +37,7 @@ use crate::{ worktree_store::{WorktreeStore, WorktreeStoreEvent}, }; -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] +#[derive(Debug, Clone)] pub struct ProjectSettings { /// Configuration for language servers. /// @@ -47,48 +47,75 @@ pub struct ProjectSettings { /// To override settings for a language, add an entry for that language server's /// name to the lsp value. /// Default: null - #[serde(default)] - pub lsp: HashMap, + // todo(settings-follow-up) + // We should change to use a non content type (settings::LspSettings is a content type) + // Note: Will either require merging with defaults, which also requires deciding where the defaults come from, + // or case by case deciding which fields are optional and which are actually required. + pub lsp: HashMap, /// Common language server settings. - #[serde(default)] pub global_lsp_settings: GlobalLspSettings, /// Configuration for Debugger-related features - #[serde(default)] pub dap: HashMap, /// Settings for context servers used for AI-related features. - #[serde(default)] pub context_servers: HashMap, ContextServerSettings>, /// Configuration for Diagnostics-related features. - #[serde(default)] pub diagnostics: DiagnosticsSettings, /// Configuration for Git-related features - #[serde(default)] pub git: GitSettings, /// Configuration for Node-related features - #[serde(default)] pub node: NodeBinarySettings, /// Configuration for how direnv configuration should be loaded - #[serde(default)] pub load_direnv: DirenvSettings, /// Configuration for session-related features - #[serde(default)] pub session: SessionSettings, } +#[derive(Copy, Clone, Debug)] +pub struct SessionSettings { + /// Whether or not to restore unsaved buffers on restart. + /// + /// If this is true, user won't be prompted whether to save/discard + /// dirty files when closing the application. + /// + /// Default: true + pub restore_unsaved_buffers: bool, +} + #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct DapSettings { - pub binary: Option, - #[serde(default)] - pub args: Vec, +pub struct NodeBinarySettings { + /// The path to the Node binary. + pub path: Option, + /// The path to the npm binary Zed should use (defaults to `.path/../npm`). + pub npm_path: Option, + /// If enabled, Zed will download its own copy of Node. + pub ignore_system_version: bool, +} + +impl From for NodeBinarySettings { + fn from(settings: settings::NodeBinarySettings) -> Self { + Self { + path: settings.path, + npm_path: settings.npm_path, + ignore_system_version: settings.ignore_system_version.unwrap_or(false), + } + } +} + +/// Common language server settings. +#[derive(Debug, Clone, PartialEq)] +pub struct GlobalLspSettings { + /// Whether to show the LSP servers button in the status bar. + /// + /// Default: `true` + pub button: bool, } #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)] @@ -114,14 +141,29 @@ pub enum ContextServerSettings { }, } -/// Common language server settings. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct GlobalLspSettings { - /// Whether to show the LSP servers button in the status bar. - /// - /// Default: `true` - #[serde(default = "default_true")] - pub button: bool, +impl From for ContextServerSettings { + fn from(value: settings::ContextServerSettingsContent) -> Self { + match value { + settings::ContextServerSettingsContent::Custom { enabled, command } => { + ContextServerSettings::Custom { enabled, command } + } + settings::ContextServerSettingsContent::Extension { enabled, settings } => { + ContextServerSettings::Extension { enabled, settings } + } + } + } +} +impl Into for ContextServerSettings { + fn into(self) -> settings::ContextServerSettingsContent { + match self { + ContextServerSettings::Custom { enabled, command } => { + settings::ContextServerSettingsContent::Custom { enabled, command } + } + ContextServerSettings::Extension { enabled, settings } => { + settings::ContextServerSettingsContent::Extension { enabled, settings } + } + } + } } impl ContextServerSettings { @@ -147,140 +189,6 @@ impl ContextServerSettings { } } -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct NodeBinarySettings { - /// The path to the Node binary. - pub path: Option, - /// The path to the npm binary Zed should use (defaults to `.path/../npm`). - pub npm_path: Option, - /// If enabled, Zed will download its own copy of Node. - #[serde(default)] - pub ignore_system_version: bool, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DirenvSettings { - /// Load direnv configuration through a shell hook - ShellHook, - /// Load direnv configuration directly using `direnv export json` - #[default] - Direct, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(default)] -pub struct DiagnosticsSettings { - /// Whether to show the project diagnostics button in the status bar. - pub button: bool, - - /// Whether or not to include warning diagnostics. - pub include_warnings: bool, - - /// Settings for using LSP pull diagnostics mechanism in Zed. - pub lsp_pull_diagnostics: LspPullDiagnosticsSettings, - - /// Settings for showing inline diagnostics. - pub inline: InlineDiagnosticsSettings, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(default)] -pub struct LspPullDiagnosticsSettings { - /// Whether to pull for diagnostics or not. - /// - /// Default: true - #[serde(default = "default_true")] - pub enabled: bool, - /// Minimum time to wait before pulling diagnostics from the language server(s). - /// 0 turns the debounce off. - /// - /// Default: 50 - #[serde(default = "default_lsp_diagnostics_pull_debounce_ms")] - pub debounce_ms: u64, -} - -fn default_lsp_diagnostics_pull_debounce_ms() -> u64 { - 50 -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(default)] -pub struct InlineDiagnosticsSettings { - /// Whether or not to show inline diagnostics - /// - /// Default: false - pub enabled: bool, - /// Whether to only show the inline diagnostics after a delay after the - /// last editor event. - /// - /// Default: 150 - #[serde(default = "default_inline_diagnostics_update_debounce_ms")] - pub update_debounce_ms: u64, - /// The amount of padding between the end of the source line and the start - /// of the inline diagnostic in units of columns. - /// - /// Default: 4 - #[serde(default = "default_inline_diagnostics_padding")] - pub padding: u32, - /// The minimum column to display inline diagnostics. This setting can be - /// used to horizontally align inline diagnostics at some position. Lines - /// longer than this value will still push diagnostics further to the right. - /// - /// Default: 0 - pub min_column: u32, - - pub max_severity: Option, -} - -fn default_inline_diagnostics_update_debounce_ms() -> u64 { - 150 -} - -fn default_inline_diagnostics_padding() -> u32 { - 4 -} - -impl Default for DiagnosticsSettings { - fn default() -> Self { - Self { - button: true, - include_warnings: true, - lsp_pull_diagnostics: LspPullDiagnosticsSettings::default(), - inline: InlineDiagnosticsSettings::default(), - } - } -} - -impl Default for LspPullDiagnosticsSettings { - fn default() -> Self { - Self { - enabled: true, - debounce_ms: default_lsp_diagnostics_pull_debounce_ms(), - } - } -} - -impl Default for InlineDiagnosticsSettings { - fn default() -> Self { - Self { - enabled: false, - update_debounce_ms: default_inline_diagnostics_update_debounce_ms(), - padding: default_inline_diagnostics_padding(), - min_column: 0, - max_severity: None, - } - } -} - -impl Default for GlobalLspSettings { - fn default() -> Self { - Self { - button: default_true(), - } - } -} - #[derive( Clone, Copy, @@ -301,7 +209,6 @@ pub enum DiagnosticSeverity { Error, Warning, Info, - #[serde(alias = "all")] Hint, } @@ -317,6 +224,18 @@ impl DiagnosticSeverity { } } +impl From for DiagnosticSeverity { + fn from(severity: settings::DiagnosticSeverityContent) -> Self { + match severity { + settings::DiagnosticSeverityContent::Off => DiagnosticSeverity::Off, + settings::DiagnosticSeverityContent::Error => DiagnosticSeverity::Error, + settings::DiagnosticSeverityContent::Warning => DiagnosticSeverity::Warning, + settings::DiagnosticSeverityContent::Info => DiagnosticSeverity::Info, + settings::DiagnosticSeverityContent::Hint => DiagnosticSeverity::Hint, + } + } +} + /// Determines the severity of the diagnostic that should be moved to. #[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Eq, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -390,12 +309,12 @@ impl GoToDiagnosticSeverityFilter { } } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug)] pub struct GitSettings { /// Whether or not to show the git gutter. /// /// Default: tracked_files - pub git_gutter: Option, + pub git_gutter: settings::GitGutterSetting, /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter. /// /// Default: null @@ -404,111 +323,50 @@ pub struct GitSettings { /// the currently focused line. /// /// Default: on - pub inline_blame: Option, + pub inline_blame: InlineBlameSettings, /// Which information to show in the branch picker. /// /// Default: on - pub branch_picker: Option, + pub branch_picker: BranchPickerSettings, /// How hunks are displayed visually in the editor. /// /// Default: staged_hollow - pub hunk_style: Option, + pub hunk_style: settings::GitHunkStyleSetting, } -impl GitSettings { - pub fn inline_blame_enabled(&self) -> bool { - #[allow(unknown_lints, clippy::manual_unwrap_or_default)] - match self.inline_blame { - Some(InlineBlameSettings { enabled, .. }) => enabled, - _ => false, - } - } - - pub fn inline_blame_delay(&self) -> Option { - match self.inline_blame { - Some(InlineBlameSettings { delay_ms, .. }) if delay_ms > 0 => { - Some(Duration::from_millis(delay_ms)) - } - _ => None, - } - } - - pub fn show_inline_commit_summary(&self) -> bool { - match self.inline_blame { - Some(InlineBlameSettings { - show_commit_summary, - .. - }) => show_commit_summary, - _ => false, - } - } -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitHunkStyleSetting { - /// Show unstaged hunks with a filled background and staged hunks hollow. - #[default] - StagedHollow, - /// Show unstaged hunks hollow and staged hunks with a filled background. - UnstagedHollow, -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitGutterSetting { - /// Show git gutter in tracked files. - #[default] - TrackedFiles, - /// Hide git gutter - Hide, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Debug)] pub struct InlineBlameSettings { /// Whether or not to show git blame data inline in /// the currently focused line. /// /// Default: true - #[serde(default = "default_true")] pub enabled: bool, /// Whether to only show the inline blame information /// after a delay once the cursor stops moving. /// /// Default: 0 - #[serde(default)] - pub delay_ms: u64, + pub delay_ms: std::time::Duration, /// The amount of padding between the end of the source line and the start /// of the inline blame in units of columns. /// /// Default: 7 - #[serde(default = "default_inline_blame_padding")] pub padding: u32, /// The minimum column number to show the inline blame information at /// /// Default: 0 - #[serde(default)] pub min_column: u32, /// Whether to show commit summary as part of the inline blame. /// /// Default: false - #[serde(default)] pub show_commit_summary: bool, } -fn default_inline_blame_padding() -> u32 { - 7 -} - -impl Default for InlineBlameSettings { - fn default() -> Self { - Self { - enabled: true, - delay_ms: 0, - padding: default_inline_blame_padding(), - min_column: 0, - show_commit_summary: false, +impl GitSettings { + pub fn inline_blame_delay(&self) -> Option { + if self.inline_blame.delay_ms.as_millis() > 0 { + Some(self.inline_blame.delay_ms) + } else { + None } } } @@ -531,93 +389,273 @@ impl Default for BranchPickerSettings { } } -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] -pub struct BinarySettings { - pub path: Option, - pub arguments: Option>, - pub env: Option>, - pub ignore_system_version: Option, -} +#[derive(Clone, Debug)] +pub struct DiagnosticsSettings { + /// Whether to show the project diagnostics button in the status bar. + pub button: bool, -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] -pub struct FetchSettings { - // Whether to consider pre-releases for fetching - pub pre_release: Option, -} + /// Whether or not to include warning diagnostics. + pub include_warnings: bool, -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] -#[serde(rename_all = "snake_case")] -pub struct LspSettings { - pub binary: Option, - pub initialization_options: Option, - pub settings: Option, - /// If the server supports sending tasks over LSP extensions, - /// this setting can be used to enable or disable them in Zed. - /// Default: true - #[serde(default = "default_true")] - pub enable_lsp_tasks: bool, - pub fetch: Option, -} + /// Settings for using LSP pull diagnostics mechanism in Zed. + pub lsp_pull_diagnostics: LspPullDiagnosticsSettings, -impl Default for LspSettings { - fn default() -> Self { - Self { - binary: None, - initialization_options: None, - settings: None, - enable_lsp_tasks: true, - fetch: None, - } - } + /// Settings for showing inline diagnostics. + pub inline: InlineDiagnosticsSettings, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct SessionSettings { - /// Whether or not to restore unsaved buffers on restart. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InlineDiagnosticsSettings { + /// Whether or not to show inline diagnostics /// - /// If this is true, user won't be prompted whether to save/discard - /// dirty files when closing the application. + /// Default: false + pub enabled: bool, + /// Whether to only show the inline diagnostics after a delay after the + /// last editor event. + /// + /// Default: 150 + pub update_debounce_ms: u64, + /// The amount of padding between the end of the source line and the start + /// of the inline diagnostic in units of columns. + /// + /// Default: 4 + pub padding: u32, + /// The minimum column to display inline diagnostics. This setting can be + /// used to horizontally align inline diagnostics at some position. Lines + /// longer than this value will still push diagnostics further to the right. + /// + /// Default: 0 + pub min_column: u32, + + pub max_severity: Option, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct LspPullDiagnosticsSettings { + /// Whether to pull for diagnostics or not. /// /// Default: true - pub restore_unsaved_buffers: bool, + pub enabled: bool, + /// Minimum time to wait before pulling diagnostics from the language server(s). + /// 0 turns the debounce off. + /// + /// Default: 50 + pub debounce_ms: u64, } -impl Default for SessionSettings { - fn default() -> Self { +impl Settings for ProjectSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let project = &content.project.clone(); + let diagnostics = content.diagnostics.as_ref().unwrap(); + let lsp_pull_diagnostics = diagnostics.lsp_pull_diagnostics.as_ref().unwrap(); + let inline_diagnostics = diagnostics.inline.as_ref().unwrap(); + + let git = content.git.as_ref().unwrap(); + let git_settings = GitSettings { + git_gutter: git.git_gutter.unwrap(), + gutter_debounce: git.gutter_debounce, + inline_blame: { + let inline = git.inline_blame.unwrap(); + InlineBlameSettings { + enabled: inline.enabled.unwrap(), + delay_ms: std::time::Duration::from_millis(inline.delay_ms.unwrap()), + padding: inline.padding.unwrap(), + min_column: inline.min_column.unwrap(), + show_commit_summary: inline.show_commit_summary.unwrap(), + } + }, + branch_picker: { + let branch_picker = git.branch_picker.unwrap(); + BranchPickerSettings { + show_author_name: branch_picker.show_author_name.unwrap(), + } + }, + hunk_style: git.hunk_style.unwrap(), + }; Self { - restore_unsaved_buffers: true, + context_servers: project + .context_servers + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect(), + lsp: project + .lsp + .clone() + .into_iter() + .map(|(key, value)| (LanguageServerName(key.into()), value)) + .collect(), + global_lsp_settings: GlobalLspSettings { + button: content + .global_lsp_settings + .as_ref() + .unwrap() + .button + .unwrap(), + }, + dap: project + .dap + .clone() + .into_iter() + .map(|(key, value)| (DebugAdapterName(key.into()), DapSettings::from(value))) + .collect(), + diagnostics: DiagnosticsSettings { + button: diagnostics.button.unwrap(), + include_warnings: diagnostics.include_warnings.unwrap(), + lsp_pull_diagnostics: LspPullDiagnosticsSettings { + enabled: lsp_pull_diagnostics.enabled.unwrap(), + debounce_ms: lsp_pull_diagnostics.debounce_ms.unwrap(), + }, + inline: InlineDiagnosticsSettings { + enabled: inline_diagnostics.enabled.unwrap(), + update_debounce_ms: inline_diagnostics.update_debounce_ms.unwrap(), + padding: inline_diagnostics.padding.unwrap(), + min_column: inline_diagnostics.min_column.unwrap(), + max_severity: inline_diagnostics.max_severity.map(Into::into), + }, + }, + git: git_settings, + node: content.node.clone().unwrap().into(), + load_direnv: project.load_direnv.clone().unwrap(), + session: SessionSettings { + restore_unsaved_buffers: content.session.unwrap().restore_unsaved_buffers.unwrap(), + }, } } -} -impl Settings for ProjectSettings { - type FileContent = Self; + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let project = &content.project; + self.context_servers.extend( + project + .context_servers + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())), + ); + self.dap.extend( + project + .dap + .clone() + .into_iter() + .map(|(key, value)| (DebugAdapterName(key.into()), DapSettings::from(value))), + ); + if let Some(diagnostics) = content.diagnostics.as_ref() { + if let Some(inline) = &diagnostics.inline { + self.diagnostics.inline.enabled.merge_from(&inline.enabled); + self.diagnostics + .inline + .update_debounce_ms + .merge_from(&inline.update_debounce_ms); + self.diagnostics.inline.padding.merge_from(&inline.padding); + self.diagnostics + .inline + .min_column + .merge_from(&inline.min_column); + if let Some(max_severity) = inline.max_severity { + self.diagnostics.inline.max_severity = Some(max_severity.into()) + } + } + + self.diagnostics.button.merge_from(&diagnostics.button); + self.diagnostics + .include_warnings + .merge_from(&diagnostics.include_warnings); + if let Some(pull_diagnostics) = &diagnostics.lsp_pull_diagnostics { + self.diagnostics + .lsp_pull_diagnostics + .enabled + .merge_from(&pull_diagnostics.enabled); + self.diagnostics + .lsp_pull_diagnostics + .debounce_ms + .merge_from(&pull_diagnostics.debounce_ms); + } + } + if let Some(git) = content.git.as_ref() { + if let Some(branch_picker) = git.branch_picker.as_ref() { + self.git + .branch_picker + .show_author_name + .merge_from(&branch_picker.show_author_name); + } + if let Some(inline_blame) = git.inline_blame.as_ref() { + self.git + .inline_blame + .enabled + .merge_from(&inline_blame.enabled); + self.git + .inline_blame + .delay_ms + .merge_from(&inline_blame.delay_ms.map(std::time::Duration::from_millis)); + self.git + .inline_blame + .padding + .merge_from(&inline_blame.padding); + self.git + .inline_blame + .min_column + .merge_from(&inline_blame.min_column); + self.git + .inline_blame + .show_commit_summary + .merge_from(&inline_blame.show_commit_summary); + } + self.git.git_gutter.merge_from(&git.git_gutter); + self.git.hunk_style.merge_from(&git.hunk_style); + if let Some(debounce) = git.gutter_debounce { + self.git.gutter_debounce = Some(debounce); + } + } + self.global_lsp_settings.button.merge_from( + &content + .global_lsp_settings + .as_ref() + .and_then(|settings| settings.button), + ); + self.load_direnv + .merge_from(&content.project.load_direnv.clone()); - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - sources.json_merge() + for (key, value) in content.project.lsp.clone() { + self.lsp.insert(LanguageServerName(key.into()), value); + } + + if let Some(node) = content.node.as_ref() { + self.node + .ignore_system_version + .merge_from(&node.ignore_system_version); + if let Some(path) = node.path.clone() { + self.node.path = Some(path); + } + if let Some(npm_path) = node.npm_path.clone() { + self.node.npm_path = Some(npm_path); + } + } + self.session + .restore_unsaved_buffers + .merge_from(&content.session.and_then(|s| s.restore_unsaved_buffers)); } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode( + vscode: &settings::VsCodeSettings, + current: &mut settings::SettingsContent, + ) { // this just sets the binary name instead of a full path so it relies on path lookup // resolving to the one you want - vscode.enum_setting( - "npm.packageManager", - &mut current.node.npm_path, - |s| match s { - v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()), - _ => None, - }, - ); + let npm_path = vscode.read_enum("npm.packageManager", |s| match s { + v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()), + _ => None, + }); + if npm_path.is_some() { + current.node.get_or_insert_default().npm_path = npm_path; + } if let Some(b) = vscode.read_bool("git.blame.editorDecoration.enabled") { - if let Some(blame) = current.git.inline_blame.as_mut() { - blame.enabled = b - } else { - current.git.inline_blame = Some(InlineBlameSettings { - enabled: b, - ..Default::default() - }) - } + current + .git + .get_or_insert_default() + .inline_blame + .get_or_insert_default() + .enabled = Some(b); } #[derive(Deserialize)] @@ -627,29 +665,27 @@ impl Settings for ProjectSettings { env: Option>, // note: we don't support envFile and type } - impl From for ContextServerCommand { - fn from(cmd: VsCodeContextServerCommand) -> Self { - Self { - path: cmd.command, - args: cmd.args.unwrap_or_default(), - env: cmd.env, - timeout: None, - } - } - } if let Some(mcp) = vscode.read_value("mcp").and_then(|v| v.as_object()) { current + .project .context_servers .extend(mcp.iter().filter_map(|(k, v)| { Some(( k.clone().into(), - ContextServerSettings::Custom { + settings::ContextServerSettingsContent::Custom { enabled: true, command: serde_json::from_value::( v.clone(), ) - .ok()? - .into(), + .ok() + .map(|cmd| { + settings::ContextServerCommand { + path: cmd.command, + args: cmd.args.unwrap_or_default(), + env: cmd.env, + timeout: None, + } + })?, }, )) })); @@ -736,17 +772,19 @@ impl SettingsObserver { if let Some(upstream_client) = upstream_client { let mut user_settings = None; user_settings_watcher = Some(cx.observe_global::(move |_, cx| { - let new_settings = cx.global::().raw_user_settings(); - if Some(new_settings) != user_settings.as_ref() { - if let Some(new_settings_string) = serde_json::to_string(new_settings).ok() - { - user_settings = Some(new_settings.clone()); - upstream_client - .send(proto::UpdateUserSettings { - project_id: REMOTE_SERVER_PROJECT_ID, - contents: new_settings_string, - }) - .log_err(); + if let Some(new_settings) = cx.global::().raw_user_settings() { + if Some(new_settings) != user_settings.as_ref() { + if let Some(new_settings_string) = + serde_json::to_string(new_settings).ok() + { + user_settings = Some(new_settings.clone()); + upstream_client + .send(proto::UpdateUserSettings { + project_id: REMOTE_SERVER_PROJECT_ID, + contents: new_settings_string, + }) + .log_err(); + } } } })); @@ -786,6 +824,7 @@ impl SettingsObserver { for worktree in self.worktree_store.read(cx).worktrees() { let worktree_id = worktree.read(cx).id().to_proto(); for (path, content) in store.local_settings(worktree.read(cx).id()) { + let content = serde_json::to_string(&content).unwrap(); downstream_client .send(proto::UpdateWorktreeSettings { project_id, @@ -856,10 +895,9 @@ impl SettingsObserver { envelope: TypedEnvelope, cx: AsyncApp, ) -> anyhow::Result<()> { - let new_settings = serde_json::from_str::(&envelope.payload.contents) - .with_context(|| { - format!("deserializing {} user settings", envelope.payload.contents) - })?; + let new_settings = serde_json::from_str(&envelope.payload.contents).with_context(|| { + format!("deserializing {} user settings", envelope.payload.contents) + })?; cx.update_global(|settings_store: &mut SettingsStore, cx| { settings_store .set_raw_user_settings(new_settings, cx) @@ -1292,3 +1330,26 @@ pub fn local_settings_kind_to_proto(kind: LocalSettingsKind) -> proto::LocalSett LocalSettingsKind::Debug => proto::LocalSettingsKind::Debug, } } + +#[derive(Debug, Clone)] +pub struct DapSettings { + pub binary: DapBinary, + pub args: Vec, +} + +impl From for DapSettings { + fn from(content: DapSettingsContent) -> Self { + DapSettings { + binary: content + .binary + .map_or_else(|| DapBinary::Default, |binary| DapBinary::Custom(binary)), + args: content.args.unwrap_or_default(), + } + } +} + +#[derive(Debug, Clone)] +pub enum DapBinary { + Default, + Custom(String), +} diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 3745b1fdd0a784bc90fcd787c1f5454672b4b429..915ae07230bdae01bc16f2cd4318658b42dcdfb7 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -23,7 +23,7 @@ use language::{ Diagnostic, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, DiskState, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageName, LineEnding, ManifestName, ManifestProvider, ManifestQuery, OffsetRangeExt, Point, ToPoint, ToolchainList, ToolchainLister, - language_settings::{AllLanguageSettings, LanguageSettingsContent, language_settings}, + language_settings::{LanguageSettingsContent, language_settings}, tree_sitter_rust, tree_sitter_typescript, }; use lsp::{ @@ -2246,8 +2246,8 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { // Disable Rust language server, ensuring only that server gets stopped. cx.update(|cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.languages.0.insert( + settings.update_user_settings(cx, |settings| { + settings.languages_mut().insert( "Rust".into(), LanguageSettingsContent { enable_language_server: Some(false), @@ -2265,16 +2265,16 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { // former gets started again and that the latter stops. cx.update(|cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.languages.0.insert( - LanguageName::new("Rust"), + settings.update_user_settings(cx, |settings| { + settings.languages_mut().insert( + "Rust".into(), LanguageSettingsContent { enable_language_server: Some(true), ..Default::default() }, ); - settings.languages.0.insert( - LanguageName::new("JavaScript"), + settings.languages_mut().insert( + "JavaScript".into(), LanguageSettingsContent { enable_language_server: Some(false), ..Default::default() @@ -8769,8 +8769,8 @@ async fn test_rescan_with_gitignore(cx: &mut gpui::TestAppContext) { init_test(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(Vec::new()); + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(Vec::new()); }); }); }); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ec63c5ca60ee2bdca9ba699c2af300116e5cdb3a..debe3fc32a002b7a78806e3d6979f028cdc665b0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -36,12 +36,13 @@ use project::{ project_settings::GoToDiagnosticSeverityFilter, relativize_path, }; -use project_panel_settings::{ - ProjectPanelDockPosition, ProjectPanelSettings, ShowDiagnostics, ShowIndentGuides, -}; +use project_panel_settings::ProjectPanelSettings; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore, update_settings_file}; +use settings::{ + DockSide, ProjectPanelEntrySpacing, Settings, SettingsStore, ShowDiagnostics, ShowIndentGuides, + update_settings_file, +}; use smallvec::SmallVec; use std::{any::TypeId, time::Instant}; use std::{ @@ -343,8 +344,14 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &ToggleHideGitIgnore, _, cx| { let fs = workspace.app_state().fs.clone(); - update_settings_file::(fs, cx, move |setting, _| { - setting.hide_gitignore = Some(!setting.hide_gitignore.unwrap_or(false)); + update_settings_file(fs, cx, move |setting, _| { + setting.project_panel.get_or_insert_default().hide_gitignore = Some( + !setting + .project_panel + .get_or_insert_default() + .hide_gitignore + .unwrap_or(false), + ); }) }); @@ -4481,8 +4488,8 @@ impl ProjectPanel { .indent_level(depth) .indent_step_size(px(settings.indent_size)) .spacing(match settings.entry_spacing { - project_panel_settings::EntrySpacing::Comfortable => ListItemSpacing::Dense, - project_panel_settings::EntrySpacing::Standard => { + ProjectPanelEntrySpacing::Comfortable => ListItemSpacing::Dense, + ProjectPanelEntrySpacing::Standard => { ListItemSpacing::ExtraDense } }) @@ -5760,8 +5767,8 @@ impl EventEmitter for ProjectPanel {} impl Panel for ProjectPanel { fn position(&self, _: &Window, cx: &App) -> DockPosition { match ProjectPanelSettings::get_global(cx).dock { - ProjectPanelDockPosition::Left => DockPosition::Left, - ProjectPanelDockPosition::Right => DockPosition::Right, + DockSide::Left => DockPosition::Left, + DockSide::Right => DockPosition::Right, } } @@ -5770,17 +5777,13 @@ impl Panel for ProjectPanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| { - let dock = match position { - DockPosition::Left | DockPosition::Bottom => ProjectPanelDockPosition::Left, - DockPosition::Right => ProjectPanelDockPosition::Right, - }; - settings.dock = Some(dock); - }, - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + let dock = match position { + DockPosition::Left | DockPosition::Bottom => DockSide::Left, + DockPosition::Right => DockSide::Right, + }; + settings.project_panel.get_or_insert_default().dock = Some(dock); + }); } fn size(&self, _: &Window, cx: &App) -> Pixels { diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 2a01d10a02449d47cad8a1c89bb9269f25db2725..b2646477697118e14abbbe6647a284d5a19a8866 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -2,40 +2,23 @@ use editor::EditorSettings; use gpui::Pixels; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; -use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ProjectPanelDockPosition { - Left, - Right, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ShowIndentGuides { - Always, - Never, -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum EntrySpacing { - /// Comfortable spacing of entries. - #[default] - Comfortable, - /// The standard spacing of entries. - Standard, -} +use settings::{ + DockSide, ProjectPanelEntrySpacing, Settings, SettingsContent, ShowDiagnostics, + ShowIndentGuides, +}; +use ui::{ + px, + scrollbars::{ScrollbarVisibility, ShowScrollbar}, +}; +use util::MergeFrom; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct ProjectPanelSettings { pub button: bool, pub hide_gitignore: bool, pub default_width: Pixels, - pub dock: ProjectPanelDockPosition, - pub entry_spacing: EntrySpacing, + pub dock: DockSide, + pub entry_spacing: ProjectPanelEntrySpacing, pub file_icons: bool, pub folder_icons: bool, pub git_status: bool, @@ -56,12 +39,6 @@ pub struct IndentGuidesSettings { pub show: ShowIndentGuides, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct IndentGuidesSettingsContent { - /// When to show the scrollbar in the project panel. - pub show: Option, -} - #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarSettings { /// When to show the scrollbar in the project panel. @@ -70,105 +47,6 @@ pub struct ScrollbarSettings { pub show: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ScrollbarSettingsContent { - /// When to show the scrollbar in the project panel. - /// - /// Default: inherits editor scrollbar settings - pub show: Option>, -} - -/// Whether to indicate diagnostic errors and/or warnings in project panel items. -/// -/// Default: all -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ShowDiagnostics { - /// Never mark the diagnostic errors/warnings in the project panel. - Off, - /// Mark files containing only diagnostic errors in the project panel. - Errors, - #[default] - /// Mark files containing diagnostic errors or warnings in the project panel. - All, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "project_panel")] -pub struct ProjectPanelSettingsContent { - /// Whether to show the project panel button in the status bar. - /// - /// Default: true - pub button: Option, - /// Whether to hide gitignore files in the project panel. - /// - /// Default: false - pub hide_gitignore: Option, - /// Customize default width (in pixels) taken by project panel - /// - /// Default: 240 - pub default_width: Option, - /// The position of project panel - /// - /// Default: left - pub dock: Option, - /// Spacing between worktree entries in the project panel. - /// - /// Default: comfortable - pub entry_spacing: Option, - /// Whether to show file icons in the project panel. - /// - /// Default: true - pub file_icons: Option, - /// Whether to show folder icons or chevrons for directories in the project panel. - /// - /// Default: true - pub folder_icons: Option, - /// Whether to show the git status in the project panel. - /// - /// Default: true - pub git_status: Option, - /// Amount of indentation (in pixels) for nested items. - /// - /// Default: 20 - pub indent_size: Option, - /// Whether to reveal it in the project panel automatically, - /// when a corresponding project entry becomes active. - /// Gitignored entries are never auto revealed. - /// - /// Default: true - pub auto_reveal_entries: Option, - /// Whether to fold directories automatically - /// when directory has only one directory inside. - /// - /// Default: true - pub auto_fold_dirs: Option, - /// Whether the project panel should open on startup. - /// - /// Default: true - pub starts_open: Option, - /// Scrollbar-related settings - pub scrollbar: Option, - /// Which files containing diagnostic errors/warnings to mark in the project panel. - /// - /// Default: all - pub show_diagnostics: Option, - /// Settings related to indent guides in the project panel. - pub indent_guides: Option, - /// Whether to hide the root entry when only one folder is open in the window. - /// - /// Default: false - pub hide_root: Option, - /// Whether to stick parent directories at top of the project panel. - /// - /// Default: true - pub sticky_scroll: Option, - /// Whether to enable drag-and-drop operations in the project panel. - /// - /// Default: true - pub drag_and_drop: Option, -} - impl ScrollbarVisibility for ProjectPanelSettings { fn visibility(&self, cx: &ui::App) -> ShowScrollbar { self.scrollbar @@ -178,32 +56,112 @@ impl ScrollbarVisibility for ProjectPanelSettings { } impl Settings for ProjectPanelSettings { - type FileContent = ProjectPanelSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + let project_panel = content.project_panel.clone().unwrap(); + Self { + button: project_panel.button.unwrap(), + hide_gitignore: project_panel.hide_gitignore.unwrap(), + default_width: px(project_panel.default_width.unwrap()), + dock: project_panel.dock.unwrap(), + entry_spacing: project_panel.entry_spacing.unwrap(), + file_icons: project_panel.file_icons.unwrap(), + folder_icons: project_panel.folder_icons.unwrap(), + git_status: project_panel.git_status.unwrap(), + indent_size: project_panel.indent_size.unwrap(), + indent_guides: IndentGuidesSettings { + show: project_panel.indent_guides.unwrap().show.unwrap(), + }, + sticky_scroll: project_panel.sticky_scroll.unwrap(), + auto_reveal_entries: project_panel.auto_reveal_entries.unwrap(), + auto_fold_dirs: project_panel.auto_fold_dirs.unwrap(), + starts_open: project_panel.starts_open.unwrap(), + scrollbar: ScrollbarSettings { + show: project_panel + .scrollbar + .unwrap() + .show + .flatten() + .map(Into::into), + }, + show_diagnostics: project_panel.show_diagnostics.unwrap(), + hide_root: project_panel.hide_root.unwrap(), + drag_and_drop: project_panel.drag_and_drop.unwrap(), + } + } - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - sources.json_merge() + fn refine(&mut self, content: &SettingsContent, _cx: &mut ui::App) { + let Some(project_panel) = content.project_panel.as_ref() else { + return; + }; + self.button.merge_from(&project_panel.button); + self.hide_gitignore + .merge_from(&project_panel.hide_gitignore); + self.default_width + .merge_from(&project_panel.default_width.map(px)); + self.dock.merge_from(&project_panel.dock); + self.entry_spacing.merge_from(&project_panel.entry_spacing); + self.file_icons.merge_from(&project_panel.file_icons); + self.folder_icons.merge_from(&project_panel.folder_icons); + self.git_status.merge_from(&project_panel.git_status); + self.indent_size.merge_from(&project_panel.indent_size); + self.sticky_scroll.merge_from(&project_panel.sticky_scroll); + self.auto_reveal_entries + .merge_from(&project_panel.auto_reveal_entries); + self.auto_fold_dirs + .merge_from(&project_panel.auto_fold_dirs); + self.starts_open.merge_from(&project_panel.starts_open); + self.show_diagnostics + .merge_from(&project_panel.show_diagnostics); + self.hide_root.merge_from(&project_panel.hide_root); + self.drag_and_drop.merge_from(&project_panel.drag_and_drop); + if let Some(show) = project_panel + .indent_guides + .as_ref() + .and_then(|indent| indent.show) + { + self.indent_guides.show = show; + } + if let Some(show) = project_panel + .scrollbar + .as_ref() + .and_then(|scrollbar| scrollbar.show) + { + self.scrollbar.show = show.map(Into::into) + } } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.bool_setting("explorer.excludeGitIgnore", &mut current.hide_gitignore); - vscode.bool_setting("explorer.autoReveal", &mut current.auto_reveal_entries); - vscode.bool_setting("explorer.compactFolders", &mut current.auto_fold_dirs); + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { + if let Some(hide_gitignore) = vscode.read_bool("explorer.excludeGitIgnore") { + current.project_panel.get_or_insert_default().hide_gitignore = Some(hide_gitignore); + } + if let Some(auto_reveal) = vscode.read_bool("explorer.autoReveal") { + current + .project_panel + .get_or_insert_default() + .auto_reveal_entries = Some(auto_reveal); + } + if let Some(compact_folders) = vscode.read_bool("explorer.compactFolders") { + current.project_panel.get_or_insert_default().auto_fold_dirs = Some(compact_folders); + } if Some(false) == vscode.read_bool("git.decorations.enabled") { - current.git_status = Some(false); + current.project_panel.get_or_insert_default().git_status = Some(false); } if Some(false) == vscode.read_bool("problems.decorations.enabled") { - current.show_diagnostics = Some(ShowDiagnostics::Off); + current + .project_panel + .get_or_insert_default() + .show_diagnostics = Some(ShowDiagnostics::Off); } if let (Some(false), Some(false)) = ( vscode.read_bool("explorer.decorations.badges"), vscode.read_bool("explorer.decorations.colors"), ) { - current.git_status = Some(false); - current.show_diagnostics = Some(ShowDiagnostics::Off); + current.project_panel.get_or_insert_default().git_status = Some(false); + current + .project_panel + .get_or_insert_default() + .show_diagnostics = Some(ShowDiagnostics::Off); } } } diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 73c33b057807d66687c2dec13962fed5ed0412d3..59061d6188721d6fefb2f539325f2058f54f1ee7 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -2,7 +2,7 @@ use super::*; use collections::HashSet; use gpui::{Empty, Entity, TestAppContext, VisualTestContext, WindowHandle}; use pretty_assertions::assert_eq; -use project::{FakeFs, WorktreeSettings}; +use project::FakeFs; use serde_json::json; use settings::SettingsStore; use std::path::{Path, PathBuf}; @@ -161,8 +161,8 @@ async fn test_exclusions_in_visible_list(cx: &mut gpui::TestAppContext) { init_test(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/4/**".to_string()]); }); }); @@ -3448,11 +3448,12 @@ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) { init_test_with_editor(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); - }); - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(false) + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(Vec::new()); + settings + .project_panel + .get_or_insert_default() + .auto_reveal_entries = Some(false); }); }) }); @@ -3570,8 +3571,11 @@ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) { cx.update(|_, cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(true) + store.update_user_settings(cx, |settings| { + settings + .project_panel + .get_or_insert_default() + .auto_reveal_entries = Some(true) }); }) }); @@ -3684,13 +3688,14 @@ async fn test_gitignored_and_always_included(cx: &mut gpui::TestAppContext) { init_test_with_editor(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); - worktree_settings.file_scan_inclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(Vec::new()); + settings.project.worktree.file_scan_inclusions = Some(vec!["always_included_but_ignored_dir/*".to_string()]); - }); - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(false) + settings + .project_panel + .get_or_insert_default() + .auto_reveal_entries = Some(false) }); }) }); @@ -3759,8 +3764,11 @@ async fn test_gitignored_and_always_included(cx: &mut gpui::TestAppContext) { cx.run_until_parked(); cx.update(|_, cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(true) + store.update_user_settings(cx, |settings| { + settings + .project_panel + .get_or_insert_default() + .auto_reveal_entries = Some(true) }); }) }); @@ -3800,11 +3808,12 @@ async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) { init_test_with_editor(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); - }); - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(false) + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(Vec::new()); + settings + .project_panel + .get_or_insert_default() + .auto_reveal_entries = Some(false) }); }) }); @@ -4001,8 +4010,8 @@ async fn test_creating_excluded_entries(cx: &mut gpui::TestAppContext) { init_test(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]); }); }); @@ -6650,11 +6659,12 @@ fn init_test(cx: &mut TestAppContext) { Project::init_settings(cx); cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_fold_dirs = Some(false); - }); - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); + store.update_user_settings(cx, |settings| { + settings + .project_panel + .get_or_insert_default() + .auto_fold_dirs = Some(false); + settings.project.worktree.file_scan_exclusions = Some(Vec::new()); }); }); }); @@ -6672,11 +6682,12 @@ fn init_test_with_editor(cx: &mut TestAppContext) { Project::init_settings(cx); cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_fold_dirs = Some(false); - }); - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); + store.update_user_settings(cx, |settings| { + settings + .project_panel + .get_or_insert_default() + .auto_fold_dirs = Some(false); + settings.project.worktree.file_scan_exclusions = Some(Vec::new()) }); }); }); diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 2ba6293ad2cf63c7ca664dba43f53d7facc70a57..1445b80fa6118bbc79fa83f113bc23bdf66c7dc4 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -31,7 +31,6 @@ picker.workspace = true project.workspace = true release_channel.workspace = true remote.workspace = true -schemars.workspace = true serde.workspace = true settings.workspace = true smol.workspace = true diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 71a27737bdd1bfc82923a45459b7e100adbbf22e..2b011638218dd58b758f3af2e46836614e1c6780 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -700,7 +700,7 @@ mod tests { use dap::debugger_settings::DebuggerSettings; use editor::Editor; use gpui::{TestAppContext, UpdateGlobal, WindowHandle}; - use project::{Project, project_settings::ProjectSettings}; + use project::Project; use serde_json::json; use settings::SettingsStore; use util::path; @@ -714,8 +714,11 @@ mod tests { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.session.restore_unsaved_buffers = false + store.update_user_settings(cx, |settings| { + settings + .session + .get_or_insert_default() + .restore_unsaved_buffers = Some(false) }); }); }); diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 8b47cbfc0f031f6f7013d9f105dd496223a67cec..d3888c9878840d78f43f77e8437311a237822a81 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeSet; use std::{path::PathBuf, sync::Arc}; use anyhow::{Context as _, Result}; @@ -17,35 +16,32 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use release_channel::ReleaseChannel; use remote::{ ConnectionIdentifier, RemoteClient, RemoteConnectionOptions, RemotePlatform, - SshConnectionOptions, SshPortForwardOption, WslConnectionOptions, + SshConnectionOptions, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +pub use settings::SshConnection; +use settings::{Settings, WslConnection}; use theme::ThemeSettings; use ui::{ ActiveTheme, Color, CommonAnimationExt, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, Styled, Window, prelude::*, }; -use util::serde::default_true; +use util::MergeFrom; use workspace::{AppState, ModalView, Workspace}; -#[derive(Deserialize)] pub struct SshSettings { - pub ssh_connections: Option>, - pub wsl_connections: Option>, + pub ssh_connections: Vec, + pub wsl_connections: Vec, /// Whether to read ~/.ssh/config for ssh connection sources. - #[serde(default = "default_true")] pub read_ssh_config: bool, } impl SshSettings { pub fn ssh_connections(&self) -> impl Iterator + use<> { - self.ssh_connections.clone().into_iter().flatten() + self.ssh_connections.clone().into_iter() } pub fn wsl_connections(&self) -> impl Iterator + use<> { - self.wsl_connections.clone().into_iter().flatten() + self.wsl_connections.clone().into_iter() } pub fn fill_connection_options_from_settings(&self, options: &mut SshConnectionOptions) { @@ -80,66 +76,7 @@ impl SshSettings { } } -#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct SshConnection { - pub host: SharedString, - #[serde(skip_serializing_if = "Option::is_none")] - pub username: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub port: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub args: Vec, - #[serde(default)] - pub projects: BTreeSet, - /// Name to use for this server in UI. - #[serde(skip_serializing_if = "Option::is_none")] - pub nickname: Option, - // By default Zed will download the binary to the host directly. - // If this is set to true, Zed will download the binary to your local machine, - // and then upload it over the SSH connection. Useful if your SSH server has - // limited outbound internet access. - #[serde(skip_serializing_if = "Option::is_none")] - pub upload_binary_over_ssh: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub port_forwards: Option>, -} - -impl From for SshConnectionOptions { - fn from(val: SshConnection) -> Self { - SshConnectionOptions { - host: val.host.into(), - username: val.username, - port: val.port, - password: None, - args: Some(val.args), - nickname: val.nickname, - upload_binary_over_ssh: val.upload_binary_over_ssh.unwrap_or_default(), - port_forwards: val.port_forwards, - } - } -} - -#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct WslConnection { - pub distro_name: SharedString, - #[serde(default)] - pub user: Option, - #[serde(default)] - pub projects: BTreeSet, -} - -impl From for WslConnectionOptions { - fn from(val: WslConnection) -> Self { - WslConnectionOptions { - distro_name: val.distro_name.into(), - user: val.user, - } - } -} - -#[derive(Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, PartialEq)] pub enum Connection { Ssh(SshConnection), Wsl(WslConnection), @@ -166,27 +103,26 @@ impl From for Connection { } } -#[derive(Clone, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema)] -pub struct SshProject { - pub paths: Vec, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct RemoteSettingsContent { - pub ssh_connections: Option>, - pub wsl_connections: Option>, - pub read_ssh_config: Option, -} - impl Settings for SshSettings { - type FileContent = RemoteSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let remote = &content.remote; + Self { + ssh_connections: remote.ssh_connections.clone().unwrap_or_default(), + wsl_connections: remote.wsl_connections.clone().unwrap_or_default(), + read_ssh_config: remote.read_ssh_config.unwrap(), + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + if let Some(ssh_connections) = content.remote.ssh_connections.clone() { + self.ssh_connections.extend(ssh_connections) + } + if let Some(wsl_connections) = content.remote.wsl_connections.clone() { + self.wsl_connections.extend(wsl_connections) + } + self.read_ssh_config + .merge_from(&content.remote.read_ssh_config); + } } pub struct RemoteConnectionPrompt { diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index d8dd37485e140d603e219ff6bdf8c9780da51528..7b19c887b0c36490a83ef7da35f7ecbd533fcb16 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1,8 +1,7 @@ use crate::{ remote_connections::{ - Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent, - SshConnection, SshConnectionHeader, SshProject, SshSettings, connect, connect_over_ssh, - open_remote_project, + Connection, RemoteConnectionModal, RemoteConnectionPrompt, SshConnection, + SshConnectionHeader, SshSettings, connect, connect_over_ssh, open_remote_project, }, ssh_config::parse_ssh_config_hosts, }; @@ -22,7 +21,10 @@ use remote::{ RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions, remote_client::ConnectionIdentifier, }; -use settings::{Settings, SettingsStore, update_settings_file, watch_config_file}; +use settings::{ + RemoteSettingsContent, Settings as _, SettingsStore, SshProject, update_settings_file, + watch_config_file, +}; use smol::stream::StreamExt as _; use std::{ borrow::Cow, @@ -233,14 +235,15 @@ impl ProjectPicker { cx.update(|_, cx| { let fs = app_state.fs.clone(); - update_settings_file::(fs, cx, { + update_settings_file(fs, cx, { let paths = paths .iter() .map(|path| path.to_string_lossy().to_string()) .collect(); - move |setting, _| match index { + move |settings, _| match index { ServerIndex::Ssh(index) => { - if let Some(server) = setting + if let Some(server) = settings + .remote .ssh_connections .as_mut() .and_then(|connections| connections.get_mut(index.0)) @@ -249,7 +252,8 @@ impl ProjectPicker { }; } ServerIndex::Wsl(index) => { - if let Some(server) = setting + if let Some(server) = settings + .remote .wsl_connections .as_mut() .and_then(|connections| connections.get_mut(index.0)) @@ -1317,7 +1321,7 @@ impl RemoteServerProjects { else { return; }; - update_settings_file::(fs, cx, move |setting, cx| f(setting, cx)); + update_settings_file(fs, cx, move |setting, cx| f(&mut setting.remote, cx)); } fn delete_ssh_server(&mut self, server: SshServerIndex, cx: &mut Context) { @@ -1356,7 +1360,7 @@ impl RemoteServerProjects { setting .wsl_connections .get_or_insert(Default::default()) - .push(crate::remote_connections::WslConnection { + .push(settings::WslConnection { distro_name: SharedString::from(connection_options.distro_name), user: connection_options.user, projects: BTreeSet::new(), @@ -1881,41 +1885,27 @@ impl RemoteServerProjects { let ssh_settings = SshSettings::get_global(cx); let mut should_rebuild = false; - let ssh_connections_changed = - ssh_settings - .ssh_connections - .as_ref() - .is_some_and(|connections| { - state - .servers - .iter() - .filter_map(|server| match server { - RemoteEntry::Project { - connection: Connection::Ssh(connection), - .. - } => Some(connection), - _ => None, - }) - .ne(connections.iter()) - }); + let ssh_connections_changed = ssh_settings.ssh_connections.iter().ne(state + .servers + .iter() + .filter_map(|server| match server { + RemoteEntry::Project { + connection: Connection::Ssh(connection), + .. + } => Some(connection), + _ => None, + })); - let wsl_connections_changed = - ssh_settings - .wsl_connections - .as_ref() - .is_some_and(|connections| { - state - .servers - .iter() - .filter_map(|server| match server { - RemoteEntry::Project { - connection: Connection::Wsl(connection), - .. - } => Some(connection), - _ => None, - }) - .ne(connections.iter()) - }); + let wsl_connections_changed = ssh_settings.wsl_connections.iter().ne(state + .servers + .iter() + .filter_map(|server| match server { + RemoteEntry::Project { + connection: Connection::Wsl(connection), + .. + } => Some(connection), + _ => None, + })); if ssh_connections_changed || wsl_connections_changed { should_rebuild = true; diff --git a/crates/remote/Cargo.toml b/crates/remote/Cargo.toml index 5985bcae827c42f4ae535b1dd859e436167e3fe5..59d80206b0585d59df6c73e2fee44b9ba1fb70c1 100644 --- a/crates/remote/Cargo.toml +++ b/crates/remote/Cargo.toml @@ -32,9 +32,9 @@ paths.workspace = true prost.workspace = true release_channel.workspace = true rpc = { workspace = true, features = ["gpui"] } -schemars.workspace = true serde.workspace = true serde_json.workspace = true +settings.workspace = true shlex.workspace = true smol.workspace = true tempfile.workspace = true diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 2a589ea836fc2576c4afd0fcff1e7b35aa6fde73..85ebf4659ccfbda35933f8b8aa4bec25d44a489c 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -15,8 +15,7 @@ use itertools::Itertools; use parking_lot::Mutex; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use rpc::proto::Envelope; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::SshPortForwardOption; use smol::{ fs, process::{self, Child, Stdio}, @@ -54,14 +53,19 @@ pub struct SshConnectionOptions { pub upload_binary_over_ssh: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)] -pub struct SshPortForwardOption { - #[serde(skip_serializing_if = "Option::is_none")] - pub local_host: Option, - pub local_port: u16, - #[serde(skip_serializing_if = "Option::is_none")] - pub remote_host: Option, - pub remote_port: u16, +impl From for SshConnectionOptions { + fn from(val: settings::SshConnection) -> Self { + SshConnectionOptions { + host: val.host.into(), + username: val.username, + port: val.port, + password: None, + args: Some(val.args), + nickname: val.nickname, + upload_binary_over_ssh: val.upload_binary_over_ssh.unwrap_or_default(), + port_forwards: val.port_forwards, + } + } } #[derive(Clone)] diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index 160a953129ebd82c357ecbfbb48b3d875f196c3d..69d1d97d82375367578dbe29de7be7294db2eba0 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -25,6 +25,15 @@ pub struct WslConnectionOptions { pub user: Option, } +impl From for WslConnectionOptions { + fn from(val: settings::WslConnection) -> Self { + WslConnectionOptions { + distro_name: val.distro_name.into(), + user: val.user, + } + } +} + pub(crate) struct WslRemoteConnection { remote_binary_path: Option, platform: RemotePlatform, diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index 5821bc6297266cffba19234d82efec3f6190c310..6386dc330af8fd1eb46380cb39c71f4adffea1e6 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -38,7 +38,6 @@ multi_buffer.workspace = true nbformat.workspace = true project.workspace = true runtimelib.workspace = true -schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/repl/src/jupyter_settings.rs b/crates/repl/src/jupyter_settings.rs index c89736a03dc6d77dd89bb33c4990b25149189a41..8a468e9ec65feb8431222bd6b9a33c8bbd9efaf3 100644 --- a/crates/repl/src/jupyter_settings.rs +++ b/crates/repl/src/jupyter_settings.rs @@ -1,10 +1,8 @@ -use std::collections::HashMap; +use collections::HashMap; use editor::EditorSettings; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; #[derive(Debug, Default)] pub struct JupyterSettings { @@ -20,45 +18,20 @@ impl JupyterSettings { } } -#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)] -#[settings_key(key = "jupyter")] -pub struct JupyterSettingsContent { - /// Default kernels to select for each language. - /// - /// Default: `{}` - pub kernel_selections: Option>, -} - -impl Default for JupyterSettingsContent { - fn default() -> Self { - JupyterSettingsContent { - kernel_selections: Some(HashMap::new()), +impl Settings for JupyterSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let jupyter = content.editor.jupyter.clone().unwrap(); + Self { + kernel_selections: jupyter.kernel_selections.unwrap_or_default(), } } -} -impl Settings for JupyterSettings { - type FileContent = JupyterSettingsContent; - - fn load( - sources: SettingsSources, - _cx: &mut gpui::App, - ) -> anyhow::Result - where - Self: Sized, - { - let mut settings = JupyterSettings::default(); - - for value in sources.defaults_and_customizations() { - if let Some(source) = &value.kernel_selections { - for (k, v) in source { - settings.kernel_selections.insert(k.clone(), v.clone()); - } - } + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(jupyter) = content.editor.jupyter.as_ref() else { + return; + }; + if let Some(kernel_selections) = jupyter.kernel_selections.clone() { + self.kernel_selections.extend(kernel_selections) } - - Ok(settings) } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} } diff --git a/crates/repl/src/repl_settings.rs b/crates/repl/src/repl_settings.rs index 2fbf4b63731009a30691f04067bf96fbb8250880..1c9ceeecfc19bec40243193e9d4ea21a2e266e45 100644 --- a/crates/repl/src/repl_settings.rs +++ b/crates/repl/src/repl_settings.rs @@ -1,55 +1,38 @@ use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; +use util::MergeFrom; /// Settings for configuring REPL display and behavior. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "repl")] +#[derive(Clone, Debug)] pub struct ReplSettings { /// Maximum number of lines to keep in REPL's scrollback buffer. /// Clamped with [4, 256] range. /// /// Default: 32 - #[serde(default = "default_max_lines")] pub max_lines: usize, /// Maximum number of columns to keep in REPL's scrollback buffer. /// Clamped with [20, 512] range. /// /// Default: 128 - #[serde(default = "default_max_columns")] pub max_columns: usize, } impl Settings for ReplSettings { - type FileContent = Self; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let repl = content.repl.as_ref().unwrap(); - fn load(sources: SettingsSources, _cx: &mut App) -> anyhow::Result { - let mut settings: ReplSettings = sources.json_merge()?; - settings.max_columns = settings.max_columns.clamp(20, 512); - settings.max_lines = settings.max_lines.clamp(4, 256); - Ok(settings) + Self { + max_lines: repl.max_lines.unwrap(), + max_columns: repl.max_columns.unwrap(), + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} -} - -const DEFAULT_NUM_LINES: usize = 32; -const DEFAULT_NUM_COLUMNS: usize = 128; - -fn default_max_lines() -> usize { - DEFAULT_NUM_LINES -} - -fn default_max_columns() -> usize { - DEFAULT_NUM_COLUMNS -} + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(repl) = content.repl.as_ref() else { + return; + }; -impl Default for ReplSettings { - fn default() -> Self { - ReplSettings { - max_lines: DEFAULT_NUM_LINES, - max_columns: DEFAULT_NUM_COLUMNS, - } + self.max_columns.merge_from(&repl.max_columns); + self.max_lines.merge_from(&repl.max_lines); } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 86e5ab67c47cae32db3bc22a06c6fd360c4b0141..dd096cde851b21983be80c5ce64b78338f54d78e 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1480,7 +1480,7 @@ mod tests { use gpui::{Hsla, TestAppContext, UpdateGlobal, VisualTestContext}; use language::{Buffer, Point}; use project::Project; - use settings::SettingsStore; + use settings::{SearchSettingsContent, SettingsStore}; use smol::stream::StreamExt as _; use unindent::Unindent as _; @@ -2866,8 +2866,14 @@ mod tests { fn update_search_settings(search_settings: SearchSettings, cx: &mut TestAppContext) { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.search = Some(search_settings); + store.update_user_settings(cx, |settings| { + settings.editor.search = Some(SearchSettingsContent { + button: Some(search_settings.button), + whole_word: Some(search_settings.whole_word), + case_sensitive: Some(search_settings.case_sensitive), + include_ignored: Some(search_settings.include_ignored), + regex: Some(search_settings.regex), + }); }); }); }); diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 4748644604ace11febddd5dcf34b43954dc361c6..062af3d23926b5bfdc0e1f7239859b6bbfd7efdd 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -32,7 +32,9 @@ serde.workspace = true serde_json.workspace = true settings_ui_macros.workspace = true serde_json_lenient.workspace = true +serde_repr.workspace = true serde_path_to_error.workspace = true +serde_with.workspace = true smallvec.workspace = true tree-sitter-json.workspace = true tree-sitter.workspace = true diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index a6bfeecbc3c01eb5309221443d1b9905b99dcd5b..858a3e758ff7d0aa6ec045197316f027913b49d6 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -1,9 +1,14 @@ use std::fmt::{Display, Formatter}; -use crate::{self as settings}; +use crate::{ + self as settings, + settings_content::{self, BaseKeymapContent, SettingsContent}, +}; +use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources, VsCodeSettings}; +use serde_with::skip_serializing_none; +use settings::{Settings, VsCodeSettings}; use settings_ui_macros::{SettingsKey, SettingsUi}; /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. @@ -24,6 +29,35 @@ pub enum BaseKeymap { None, } +impl From for BaseKeymap { + fn from(value: BaseKeymapContent) -> Self { + match value { + BaseKeymapContent::VSCode => Self::VSCode, + BaseKeymapContent::JetBrains => Self::JetBrains, + BaseKeymapContent::SublimeText => Self::SublimeText, + BaseKeymapContent::Atom => Self::Atom, + BaseKeymapContent::TextMate => Self::TextMate, + BaseKeymapContent::Emacs => Self::Emacs, + BaseKeymapContent::Cursor => Self::Cursor, + BaseKeymapContent::None => Self::None, + } + } +} +impl Into for BaseKeymap { + fn into(self) -> BaseKeymapContent { + match self { + BaseKeymap::VSCode => BaseKeymapContent::VSCode, + BaseKeymap::JetBrains => BaseKeymapContent::JetBrains, + BaseKeymap::SublimeText => BaseKeymapContent::SublimeText, + BaseKeymap::Atom => BaseKeymapContent::Atom, + BaseKeymap::TextMate => BaseKeymapContent::TextMate, + BaseKeymap::Emacs => BaseKeymapContent::Emacs, + BaseKeymap::Cursor => BaseKeymapContent::Cursor, + BaseKeymap::None => BaseKeymapContent::None, + } + } +} + impl Display for BaseKeymap { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -115,30 +149,23 @@ impl BaseKeymap { )] // extracted so that it can be an option, and still work with derive(SettingsUi) #[settings_key(None)] +#[skip_serializing_none] pub struct BaseKeymapSetting { pub base_keymap: Option, } impl Settings for BaseKeymap { - type FileContent = BaseKeymapSetting; + fn from_defaults(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Self { + s.base_keymap.unwrap().into() + } - fn load( - sources: SettingsSources, - _: &mut gpui::App, - ) -> anyhow::Result { - if let Some(Some(user_value)) = sources.user.map(|setting| setting.base_keymap) { - return Ok(user_value); - } - if let Some(Some(server_value)) = sources.server.map(|setting| setting.base_keymap) { - return Ok(server_value); - } - sources - .default - .base_keymap - .ok_or_else(Self::missing_default) + fn refine(&mut self, s: &settings_content::SettingsContent, _cx: &mut App) { + if let Some(base_keymap) = s.base_keymap { + *self = base_keymap.into(); + }; } - fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut Self::FileContent) { - current.base_keymap = Some(BaseKeymap::VSCode); + fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut SettingsContent) { + current.base_keymap = Some(BaseKeymapContent::VSCode); } } diff --git a/crates/settings/src/editable_setting_control.rs b/crates/settings/src/editable_setting_control.rs index 0a5b3e9be0f93e9e2c0a20386d68b644af883be9..fd9d9869620f57b9cee9c522312bb2e59222e4c0 100644 --- a/crates/settings/src/editable_setting_control.rs +++ b/crates/settings/src/editable_setting_control.rs @@ -1,16 +1,13 @@ use fs::Fs; use gpui::{App, RenderOnce, SharedString}; -use crate::{Settings, update_settings_file}; +use crate::{settings_content::SettingsContent, update_settings_file}; /// A UI control that can be used to edit a setting. pub trait EditableSettingControl: RenderOnce { /// The type of the setting value. type Value: Send; - /// The settings type to which this setting belongs. - type Settings: Settings; - /// Returns the name of this setting. fn name(&self) -> SharedString; @@ -20,17 +17,13 @@ pub trait EditableSettingControl: RenderOnce { /// Applies the given setting file to the settings file contents. /// /// This will be called when writing the setting value back to the settings file. - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - cx: &App, - ); + fn apply(settings: &mut SettingsContent, value: Self::Value, cx: &App); /// Writes the given setting value to the settings files. fn write(value: Self::Value, cx: &App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |settings, cx| { + update_settings_file(fs, cx, move |settings, cx| { Self::apply(settings, value, cx); }); } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 8a50b1afe5d0c68365efe0652421937f6dad2783..bb242aaac28e2910d72982eeb7e92c0ea8f6c896 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,12 +1,15 @@ mod base_keymap_setting; mod editable_setting_control; mod keymap_file; +mod settings_content; mod settings_file; mod settings_json; mod settings_store; mod settings_ui_core; mod vscode_import; +pub use settings_content::*; + use gpui::{App, Global}; use rust_embed::RustEmbed; use std::{borrow::Cow, fmt, str}; @@ -21,8 +24,7 @@ pub use keymap_file::{ pub use settings_file::*; pub use settings_json::*; pub use settings_store::{ - InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, - SettingsSources, SettingsStore, + InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, SettingsStore, }; pub use settings_ui_core::*; // Re-export the derive macro @@ -75,10 +77,7 @@ impl fmt::Display for WorktreeId { pub struct SettingsAssets; pub fn init(cx: &mut App) { - let mut settings = SettingsStore::new(cx); - settings - .set_default_settings(&default_settings(), cx) - .unwrap(); + let settings = SettingsStore::new(cx, &default_settings()); cx.set_global(settings); BaseKeymap::register(cx); SettingsStore::observe_active_settings_profile_name(cx).detach(); diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs new file mode 100644 index 0000000000000000000000000000000000000000..542cc6c57b08389f85c71ab0ef8dfcdd1f78c189 --- /dev/null +++ b/crates/settings/src/settings_content.rs @@ -0,0 +1,818 @@ +mod agent; +mod editor; +mod language; +mod language_model; +mod project; +mod terminal; +mod theme; +mod workspace; + +pub use agent::*; +pub use editor::*; +pub use language::*; +pub use language_model::*; +pub use project::*; +pub use terminal::*; +pub use theme::*; +pub use workspace::*; + +use collections::HashMap; +use gpui::{App, SharedString}; +use release_channel::ReleaseChannel; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use std::collections::BTreeSet; +use std::env; +use std::sync::Arc; +pub use util::serde::default_true; + +use crate::ActiveSettingsProfileName; + +#[skip_serializing_none] +#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SettingsContent { + #[serde(flatten)] + pub project: ProjectSettingsContent, + + #[serde(flatten)] + pub theme: Box, + + #[serde(flatten)] + pub extension: ExtensionSettingsContent, + + #[serde(flatten)] + pub workspace: WorkspaceSettingsContent, + + #[serde(flatten)] + pub editor: EditorSettingsContent, + + #[serde(flatten)] + pub remote: RemoteSettingsContent, + + /// Settings related to the file finder. + pub file_finder: Option, + + pub git_panel: Option, + + pub tabs: Option, + pub tab_bar: Option, + + pub preview_tabs: Option, + + pub agent: Option, + pub agent_servers: Option, + + /// Configuration of audio in Zed. + pub audio: Option, + + /// Whether or not to automatically check for updates. + /// + /// Default: true + pub auto_update: Option, + + /// This base keymap settings adjusts the default keybindings in Zed to be similar + /// to other common code editors. By default, Zed's keymap closely follows VSCode's + /// keymap, with minor adjustments, this corresponds to the "VSCode" setting. + /// + /// Default: VSCode + pub base_keymap: Option, + + /// Configuration for the collab panel visual settings. + pub collaboration_panel: Option, + + pub debugger: Option, + + /// Configuration for Diagnostics-related features. + pub diagnostics: Option, + + /// Configuration for Git-related features + pub git: Option, + + /// Common language server settings. + pub global_lsp_settings: Option, + + /// The settings for the image viewer. + pub image_viewer: Option, + + pub repl: Option, + + /// Whether or not to enable Helix mode. + /// + /// Default: false + pub helix_mode: Option, + + pub journal: Option, + + /// A map of log scopes to the desired log level. + /// Useful for filtering out noisy logs or enabling more verbose logging. + /// + /// Example: {"log": {"client": "warn"}} + pub log: Option>, + + pub line_indicator_format: Option, + + pub language_models: Option, + + pub outline_panel: Option, + + pub project_panel: Option, + + /// Configuration for the Message Editor + pub message_editor: Option, + + /// Configuration for Node-related features + pub node: Option, + + /// Configuration for the Notification Panel + pub notification_panel: Option, + + pub proxy: Option, + + /// The URL of the Zed server to connect to. + pub server_url: Option, + + /// Configuration for session-related features + pub session: Option, + /// Control what info is collected by Zed. + pub telemetry: Option, + + /// Configuration of the terminal in Zed. + pub terminal: Option, + + pub title_bar: Option, + + /// Whether or not to enable Vim mode. + /// + /// Default: false + pub vim_mode: Option, + + // Settings related to calls in Zed + pub calls: Option, + + /// Whether to disable all AI features in Zed. + /// + /// Default: false + pub disable_ai: Option, + + /// Settings related to Vim mode in Zed. + pub vim: Option, +} + +impl SettingsContent { + pub fn languages_mut(&mut self) -> &mut HashMap { + &mut self.project.all_languages.languages.0 + } +} + +#[skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ServerSettingsContent { + #[serde(flatten)] + pub project: ProjectSettingsContent, +} + +#[skip_serializing_none] +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +pub struct UserSettingsContent { + #[serde(flatten)] + pub content: Box, + + pub dev: Option>, + pub nightly: Option>, + pub preview: Option>, + pub stable: Option>, + + pub macos: Option>, + pub windows: Option>, + pub linux: Option>, + + #[serde(default)] + pub profiles: HashMap, +} + +pub struct ExtensionsSettingsContent { + pub all_languages: AllLanguageSettingsContent, +} + +impl UserSettingsContent { + pub fn for_release_channel(&self) -> Option<&SettingsContent> { + match *release_channel::RELEASE_CHANNEL { + ReleaseChannel::Dev => self.dev.as_deref(), + ReleaseChannel::Nightly => self.nightly.as_deref(), + ReleaseChannel::Preview => self.preview.as_deref(), + ReleaseChannel::Stable => self.stable.as_deref(), + } + } + + pub fn for_os(&self) -> Option<&SettingsContent> { + match env::consts::OS { + "macos" => self.macos.as_deref(), + "linux" => self.linux.as_deref(), + "windows" => self.windows.as_deref(), + _ => None, + } + } + + pub fn for_profile(&self, cx: &App) -> Option<&SettingsContent> { + let Some(active_profile) = cx.try_global::() else { + return None; + }; + self.profiles.get(&active_profile.0) + } +} + +/// Base key bindings scheme. Base keymaps can be overridden with user keymaps. +/// +/// Default: VSCode +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +pub enum BaseKeymapContent { + #[default] + VSCode, + JetBrains, + SublimeText, + Atom, + TextMate, + Emacs, + Cursor, + None, +} + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct TitleBarSettingsContent { + /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen". + /// + /// Default: "always" + pub show: Option, + /// Whether to show the branch icon beside branch switcher in the title bar. + /// + /// Default: false + pub show_branch_icon: Option, + /// Whether to show onboarding banners in the title bar. + /// + /// Default: true + pub show_onboarding_banner: Option, + /// Whether to show user avatar in the title bar. + /// + /// Default: true + pub show_user_picture: Option, + /// Whether to show the branch name button in the titlebar. + /// + /// Default: true + pub show_branch_name: Option, + /// Whether to show the project host and name in the titlebar. + /// + /// Default: true + pub show_project_items: Option, + /// Whether to show the sign in button in the title bar. + /// + /// Default: true + pub show_sign_in: Option, + /// Whether to show the menus in the title bar. + /// + /// Default: false + pub show_menus: Option, +} + +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum TitleBarVisibility { + Always, + Never, + HideInFullScreen, +} + +/// Configuration of audio in Zed. +#[skip_serializing_none] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct AudioSettingsContent { + /// Opt into the new audio system. + #[serde(rename = "experimental.rodio_audio", default)] + pub rodio_audio: Option, + /// Requires 'rodio_audio: true' + /// + /// Use the new audio systems automatic gain control for your microphone. + /// This affects how loud you sound to others. + #[serde(rename = "experimental.control_input_volume", default)] + pub control_input_volume: Option, + /// Requires 'rodio_audio: true' + /// + /// Use the new audio systems automatic gain control on everyone in the + /// call. This makes call members who are too quite louder and those who are + /// too loud quieter. This only affects how things sound for you. + #[serde(rename = "experimental.control_output_volume", default)] + pub control_output_volume: Option, +} + +/// Control what info is collected by Zed. +#[skip_serializing_none] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug)] +pub struct TelemetrySettingsContent { + /// Send debug info like crash reports. + /// + /// Default: true + pub diagnostics: Option, + /// Send anonymized usage data like what languages you're using Zed with. + /// + /// Default: true + pub metrics: Option, +} + +#[skip_serializing_none] +#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone)] +pub struct DebuggerSettingsContent { + /// Determines the stepping granularity. + /// + /// Default: line + pub stepping_granularity: Option, + /// Whether the breakpoints should be reused across Zed sessions. + /// + /// Default: true + pub save_breakpoints: Option, + /// Whether to show the debug button in the status bar. + /// + /// Default: true + pub button: Option, + /// Time in milliseconds until timeout error when connecting to a TCP debug adapter + /// + /// Default: 2000ms + pub timeout: Option, + /// Whether to log messages between active debug adapters and Zed + /// + /// Default: true + pub log_dap_communications: Option, + /// Whether to format dap messages in when adding them to debug adapter logger + /// + /// Default: true + pub format_dap_log_messages: Option, + /// The dock position of the debug panel + /// + /// Default: Bottom + pub dock: Option, +} + +/// The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`. +#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SteppingGranularity { + /// The step should allow the program to run until the current statement has finished executing. + /// The meaning of a statement is determined by the adapter and it may be considered equivalent to a line. + /// For example 'for(int i = 0; i < 10; i++)' could be considered to have 3 statements 'int i = 0', 'i < 10', and 'i++'. + Statement, + /// The step should allow the program to run until the current source line has executed. + Line, + /// The step should allow one instruction to execute (e.g. one x86 instruction). + Instruction, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum DockPosition { + Left, + Bottom, + Right, +} + +/// Settings for slash commands. +#[skip_serializing_none] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, PartialEq, Eq)] +pub struct SlashCommandSettings { + /// Settings for the `/cargo-workspace` slash command. + pub cargo_workspace: Option, +} + +/// Settings for the `/cargo-workspace` slash command. +#[skip_serializing_none] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, PartialEq, Eq)] +pub struct CargoWorkspaceCommandSettings { + /// Whether `/cargo-workspace` is enabled. + pub enabled: Option, +} + +/// Configuration of voice calls in Zed. +#[skip_serializing_none] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct CallSettingsContent { + /// Whether the microphone should be muted when joining a channel or a call. + /// + /// Default: false + pub mute_on_join: Option, + + /// Whether your current project should be shared when joining an empty channel. + /// + /// Default: false + pub share_on_join: Option, +} + +#[skip_serializing_none] +#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone, JsonSchema)] +pub struct ExtensionSettingsContent { + /// The extensions that should be automatically installed by Zed. + /// + /// This is used to make functionality provided by extensions (e.g., language support) + /// available out-of-the-box. + /// + /// Default: { "html": true } + #[serde(default)] + pub auto_install_extensions: HashMap, bool>, + #[serde(default)] + pub auto_update_extensions: HashMap, bool>, +} + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct GitPanelSettingsContent { + /// Whether to show the panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Where to dock the panel. + /// + /// Default: left + pub dock: Option, + /// Default width of the panel in pixels. + /// + /// Default: 360 + pub default_width: Option, + /// How entry statuses are displayed. + /// + /// Default: icon + pub status_style: Option, + /// How and when the scrollbar should be displayed. + /// + /// Default: inherits editor scrollbar settings + pub scrollbar: Option, + + /// What the default branch name should be when + /// `init.defaultBranch` is not set in git + /// + /// Default: main + pub fallback_branch_name: Option, + + /// Whether to sort entries in the panel by path + /// or by status (the default). + /// + /// Default: false + pub sort_by_path: Option, + + /// Whether to collapse untracked files in the diff panel. + /// + /// Default: false + pub collapse_untracked_diff: Option, +} + +#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum StatusStyle { + #[default] + Icon, + LabelColor, +} + +#[skip_serializing_none] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ScrollbarSettings { + pub show: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +pub struct NotificationPanelSettingsContent { + /// Whether to show the panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Where to dock the panel. + /// + /// Default: right + pub dock: Option, + /// Default width of the panel in pixels. + /// + /// Default: 300 + pub default_width: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +pub struct PanelSettingsContent { + /// Whether to show the panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Where to dock the panel. + /// + /// Default: left + pub dock: Option, + /// Default width of the panel in pixels. + /// + /// Default: 240 + pub default_width: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +pub struct MessageEditorSettings { + /// Whether to automatically replace emoji shortcodes with emoji characters. + /// For example: typing `:wave:` gets replaced with `👋`. + /// + /// Default: false + pub auto_replace_emoji_shortcode: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +pub struct FileFinderSettingsContent { + /// Whether to show file icons in the file finder. + /// + /// Default: true + pub file_icons: Option, + /// Determines how much space the file finder can take up in relation to the available window width. + /// + /// Default: small + pub modal_max_width: Option, + /// Determines whether the file finder should skip focus for the active file in search results. + /// + /// Default: true + pub skip_focus_for_active_in_search: Option, + /// Determines whether to show the git status in the file finder + /// + /// Default: true + pub git_status: Option, + /// Whether to use gitignored files when searching. + /// Only the file Zed had indexed will be used, not necessary all the gitignored files. + /// + /// Can accept 3 values: + /// * `Some(true)`: Use all gitignored files + /// * `Some(false)`: Use only the files Zed had indexed + /// * `None`: Be smart and search for ignored when called from a gitignored worktree + /// + /// Default: None + /// todo() -> Change this type to an enum + pub include_ignored: Option>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum FileFinderWidthContent { + #[default] + Small, + Medium, + Large, + XLarge, + Full, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema)] +pub struct VimSettingsContent { + pub default_mode: Option, + pub toggle_relative_line_numbers: Option, + pub use_system_clipboard: Option, + pub use_smartcase_find: Option, + pub custom_digraphs: Option>>, + pub highlight_on_yank_duration: Option, + pub cursor_shape: Option, +} + +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ModeContent { + #[default] + Normal, + Insert, + HelixNormal, +} + +/// Controls when to use system clipboard. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum UseSystemClipboard { + /// Don't use system clipboard. + Never, + /// Use system clipboard. + Always, + /// Use system clipboard for yank operations. + OnYank, +} + +/// The settings for cursor shape. +#[skip_serializing_none] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +pub struct CursorShapeSettings { + /// Cursor shape for the normal mode. + /// + /// Default: block + pub normal: Option, + /// Cursor shape for the replace mode. + /// + /// Default: underline + pub replace: Option, + /// Cursor shape for the visual mode. + /// + /// Default: block + pub visual: Option, + /// Cursor shape for the insert mode. + /// + /// The default value follows the primary cursor_shape. + pub insert: Option, +} + +/// Settings specific to journaling +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct JournalSettingsContent { + /// The path of the directory where journal entries are stored. + /// + /// Default: `~` + pub path: Option, + /// What format to display the hours in. + /// + /// Default: hour12 + pub hour_format: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum HourFormat { + #[default] + Hour12, + Hour24, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +pub struct OutlinePanelSettingsContent { + /// Whether to show the outline panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Customize default width (in pixels) taken by outline panel + /// + /// Default: 240 + pub default_width: Option, + /// The position of outline panel + /// + /// Default: left + pub dock: Option, + /// Whether to show file icons in the outline panel. + /// + /// Default: true + pub file_icons: Option, + /// Whether to show folder icons or chevrons for directories in the outline panel. + /// + /// Default: true + pub folder_icons: Option, + /// Whether to show the git status in the outline panel. + /// + /// Default: true + pub git_status: Option, + /// Amount of indentation (in pixels) for nested items. + /// + /// Default: 20 + pub indent_size: Option, + /// Whether to reveal it in the outline panel automatically, + /// when a corresponding project entry becomes active. + /// Gitignored entries are never auto revealed. + /// + /// Default: true + pub auto_reveal_entries: Option, + /// Whether to fold directories automatically + /// when directory has only one directory inside. + /// + /// Default: true + pub auto_fold_dirs: Option, + /// Settings related to indent guides in the outline panel. + pub indent_guides: Option, + /// Scrollbar-related settings + pub scrollbar: Option, + /// Default depth to expand outline items in the current file. + /// The default depth to which outline entries are expanded on reveal. + /// - Set to 0 to collapse all items that have children + /// - Set to 1 or higher to collapse items at that depth or deeper + /// + /// Default: 100 + pub expand_outlines_with_depth: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum DockSide { + Left, + Right, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ShowIndentGuides { + Always, + Never, +} + +#[skip_serializing_none] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct IndentGuidesSettingsContent { + /// When to show the scrollbar in the outline panel. + pub show: Option, +} + +#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum LineIndicatorFormat { + Short, + #[default] + Long, +} + +/// The settings for the image viewer. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, PartialEq)] +pub struct ImageViewerSettingsContent { + /// The unit to use for displaying image file sizes. + /// + /// Default: "binary" + pub unit: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ImageFileSizeUnit { + /// Displays file size in binary units (e.g., KiB, MiB). + #[default] + Binary, + /// Displays file size in decimal units (e.g., KB, MB). + Decimal, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct RemoteSettingsContent { + pub ssh_connections: Option>, + pub wsl_connections: Option>, + pub read_ssh_config: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct SshConnection { + pub host: SharedString, + pub username: Option, + pub port: Option, + #[serde(default)] + pub args: Vec, + #[serde(default)] + pub projects: collections::BTreeSet, + /// Name to use for this server in UI. + pub nickname: Option, + // By default Zed will download the binary to the host directly. + // If this is set to true, Zed will download the binary to your local machine, + // and then upload it over the SSH connection. Useful if your SSH server has + // limited outbound internet access. + pub upload_binary_over_ssh: Option, + + pub port_forwards: Option>, +} + +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, Debug)] +pub struct WslConnection { + pub distro_name: SharedString, + pub user: Option, + #[serde(default)] + pub projects: BTreeSet, +} + +#[skip_serializing_none] +#[derive( + Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, +)] +pub struct SshProject { + pub paths: Vec, +} + +#[skip_serializing_none] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)] +pub struct SshPortForwardOption { + #[serde(skip_serializing_if = "Option::is_none")] + pub local_host: Option, + pub local_port: u16, + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_host: Option, + pub remote_port: u16, +} + +/// Settings for configuring REPL display and behavior. +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct ReplSettingsContent { + /// Maximum number of lines to keep in REPL's scrollback buffer. + /// Clamped with [4, 256] range. + /// + /// Default: 32 + pub max_lines: Option, + /// Maximum number of columns to keep in REPL's scrollback buffer. + /// Clamped with [20, 512] range. + /// + /// Default: 128 + pub max_columns: Option, +} diff --git a/crates/settings/src/settings_content/agent.rs b/crates/settings/src/settings_content/agent.rs new file mode 100644 index 0000000000000000000000000000000000000000..0dd9a78343ec6737a7b98a8ef9c755783c1e6f33 --- /dev/null +++ b/crates/settings/src/settings_content/agent.rs @@ -0,0 +1,336 @@ +use collections::{HashMap, IndexMap}; +use gpui::SharedString; +use schemars::{JsonSchema, json_schema}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use std::{borrow::Cow, path::PathBuf, sync::Arc}; + +use crate::DockPosition; + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug, Default)] +pub struct AgentSettingsContent { + /// Whether the Agent is enabled. + /// + /// Default: true + pub enabled: Option, + /// Whether to show the agent panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Where to dock the agent panel. + /// + /// Default: right + pub dock: Option, + /// Default width in pixels when the agent panel is docked to the left or right. + /// + /// Default: 640 + pub default_width: Option, + /// Default height in pixels when the agent panel is docked to the bottom. + /// + /// Default: 320 + pub default_height: Option, + /// The default model to use when creating new chats and for other features when a specific model is not specified. + pub default_model: Option, + /// Model to use for the inline assistant. Defaults to default_model when not specified. + pub inline_assistant_model: Option, + /// Model to use for generating git commit messages. Defaults to default_model when not specified. + pub commit_message_model: Option, + /// Model to use for generating thread summaries. Defaults to default_model when not specified. + pub thread_summary_model: Option, + /// Additional models with which to generate alternatives when performing inline assists. + pub inline_alternatives: Option>, + /// The default profile to use in the Agent. + /// + /// Default: write + pub default_profile: Option>, + /// Which view type to show by default in the agent panel. + /// + /// Default: "thread" + pub default_view: Option, + /// The available agent profiles. + pub profiles: Option, AgentProfileContent>>, + /// Whenever a tool action would normally wait for your confirmation + /// that you allow it, always choose to allow it. + /// + /// This setting has no effect on external agents that support permission modes, such as Claude Code. + /// + /// Set `agent_servers.claude.default_mode` to `bypassPermissions`, to disable all permission requests when using Claude Code. + /// + /// Default: false + pub always_allow_tool_actions: Option, + /// Where to show a popup notification when the agent is waiting for user input. + /// + /// Default: "primary_screen" + pub notify_when_agent_waiting: Option, + /// Whether to play a sound when the agent has either completed its response, or needs user input. + /// + /// Default: false + pub play_sound_when_agent_done: Option, + /// Whether to stream edits from the agent as they are received. + /// + /// Default: false + pub stream_edits: Option, + /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane. + /// + /// Default: true + pub single_file_review: Option, + /// Additional parameters for language model requests. When making a request + /// to a model, parameters will be taken from the last entry in this list + /// that matches the model's provider and name. In each entry, both provider + /// and model are optional, so that you can specify parameters for either + /// one. + /// + /// Default: [] + #[serde(default)] + pub model_parameters: Vec, + /// What completion mode to enable for new threads + /// + /// Default: normal + pub preferred_completion_mode: Option, + /// Whether to show thumb buttons for feedback in the agent panel. + /// + /// Default: true + pub enable_feedback: Option, + /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff. + /// + /// Default: true + pub expand_edit_card: Option, + /// Whether to have terminal cards in the agent panel expanded, showing the whole command output. + /// + /// Default: true + pub expand_terminal_card: Option, + /// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel. + /// + /// Default: false + pub use_modifier_to_send: Option, + /// Minimum number of lines of height the agent message editor should have. + /// + /// Default: 4 + pub message_editor_min_lines: Option, +} + +impl AgentSettingsContent { + pub fn set_dock(&mut self, dock: DockPosition) { + self.dock = Some(dock); + } + + pub fn set_model(&mut self, language_model: LanguageModelSelection) { + // let model = language_model.id().0.to_string(); + // let provider = language_model.provider_id().0.to_string(); + // self.default_model = Some(LanguageModelSelection { + // provider: provider.into(), + // model, + // }); + self.default_model = Some(language_model) + } + + pub fn set_inline_assistant_model(&mut self, provider: String, model: String) { + self.inline_assistant_model = Some(LanguageModelSelection { + provider: provider.into(), + model, + }); + } + + pub fn set_commit_message_model(&mut self, provider: String, model: String) { + self.commit_message_model = Some(LanguageModelSelection { + provider: provider.into(), + model, + }); + } + + pub fn set_thread_summary_model(&mut self, provider: String, model: String) { + self.thread_summary_model = Some(LanguageModelSelection { + provider: provider.into(), + model, + }); + } + + pub fn set_always_allow_tool_actions(&mut self, allow: bool) { + self.always_allow_tool_actions = Some(allow); + } + + pub fn set_play_sound_when_agent_done(&mut self, allow: bool) { + self.play_sound_when_agent_done = Some(allow); + } + + pub fn set_single_file_review(&mut self, allow: bool) { + self.single_file_review = Some(allow); + } + + pub fn set_use_modifier_to_send(&mut self, always_use: bool) { + self.use_modifier_to_send = Some(always_use); + } + + pub fn set_profile(&mut self, profile_id: Arc) { + self.default_profile = Some(profile_id); + } +} + +#[skip_serializing_none] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +pub struct AgentProfileContent { + pub name: Arc, + #[serde(default)] + pub tools: IndexMap, bool>, + /// Whether all context servers are enabled by default. + pub enable_all_context_servers: Option, + #[serde(default)] + pub context_servers: IndexMap, ContextServerPresetContent>, +} + +#[skip_serializing_none] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct ContextServerPresetContent { + pub tools: IndexMap, bool>, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DefaultAgentView { + #[default] + Thread, + TextThread, +} + +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum NotifyWhenAgentWaiting { + #[default] + PrimaryScreen, + AllScreens, + Never, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct LanguageModelSelection { + pub provider: LanguageModelProviderSetting, + pub model: String, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[serde(rename_all = "snake_case")] +pub enum CompletionMode { + #[default] + Normal, + #[serde(alias = "max")] + Burn, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct LanguageModelParameters { + pub provider: Option, + pub model: Option, + pub temperature: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct LanguageModelProviderSetting(pub String); + +impl JsonSchema for LanguageModelProviderSetting { + fn schema_name() -> Cow<'static, str> { + "LanguageModelProviderSetting".into() + } + + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + // list the builtin providers as a subset so that we still auto complete them in the settings + json_schema!({ + "anyOf": [ + { + "type": "string", + "enum": [ + "amazon-bedrock", + "anthropic", + "copilot_chat", + "deepseek", + "google", + "lmstudio", + "mistral", + "ollama", + "openai", + "openrouter", + "vercel", + "x_ai", + "zed.dev" + ] + }, + { + "type": "string", + } + ] + }) + } +} + +impl From for LanguageModelProviderSetting { + fn from(provider: String) -> Self { + Self(provider) + } +} + +impl From<&str> for LanguageModelProviderSetting { + fn from(provider: &str) -> Self { + Self(provider.to_string()) + } +} + +#[skip_serializing_none] +#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, Debug)] +pub struct AllAgentServersSettings { + pub gemini: Option, + pub claude: Option, + + /// Custom agent servers configured by the user + #[serde(flatten)] + pub custom: HashMap, +} + +#[skip_serializing_none] +#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +pub struct BuiltinAgentServerSettings { + /// Absolute path to a binary to be used when launching this agent. + /// + /// This can be used to run a specific binary without automatic downloads or searching `$PATH`. + #[serde(rename = "command")] + pub path: Option, + /// If a binary is specified in `command`, it will be passed these arguments. + pub args: Option>, + /// If a binary is specified in `command`, it will be passed these environment variables. + pub env: Option>, + /// Whether to skip searching `$PATH` for an agent server binary when + /// launching this agent. + /// + /// This has no effect if a `command` is specified. Otherwise, when this is + /// `false`, Zed will search `$PATH` for an agent server binary and, if one + /// is found, use it for threads with this agent. If no agent binary is + /// found on `$PATH`, Zed will automatically install and use its own binary. + /// When this is `true`, Zed will not search `$PATH`, and will always use + /// its own binary. + /// + /// Default: true + pub ignore_system_version: Option, + /// The default mode to use for this agent. + /// + /// Note: Not only all agents support modes. + /// + /// Default: None + pub default_mode: Option, +} + +#[skip_serializing_none] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +pub struct CustomAgentServerSettings { + #[serde(rename = "command")] + pub path: PathBuf, + #[serde(default)] + pub args: Vec, + pub env: Option>, + /// The default mode to use for this agent. + /// + /// Note: Not only all agents support modes. + /// + /// Default: None + pub default_mode: Option, +} diff --git a/crates/settings/src/settings_content/editor.rs b/crates/settings/src/settings_content/editor.rs new file mode 100644 index 0000000000000000000000000000000000000000..d5984d4213ea053589e51abc91b5ff9f1e7268f1 --- /dev/null +++ b/crates/settings/src/settings_content/editor.rs @@ -0,0 +1,603 @@ +use std::num; + +use collections::HashMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +use crate::{DiagnosticSeverityContent, ShowScrollbar}; + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct EditorSettingsContent { + /// Whether the cursor blinks in the editor. + /// + /// Default: true + pub cursor_blink: Option, + /// Cursor shape for the default editor. + /// Can be "bar", "block", "underline", or "hollow". + /// + /// Default: bar + pub cursor_shape: Option, + /// Determines when the mouse cursor should be hidden in an editor or input box. + /// + /// Default: on_typing_and_movement + pub hide_mouse: Option, + /// Determines how snippets are sorted relative to other completion items. + /// + /// Default: inline + pub snippet_sort_order: Option, + /// How to highlight the current line in the editor. + /// + /// Default: all + pub current_line_highlight: Option, + /// Whether to highlight all occurrences of the selected text in an editor. + /// + /// Default: true + pub selection_highlight: Option, + /// Whether the text selection should have rounded corners. + /// + /// Default: true + pub rounded_selection: Option, + /// The debounce delay before querying highlights from the language + /// server based on the current cursor location. + /// + /// Default: 75 + pub lsp_highlight_debounce: Option, + /// Whether to show the informational hover box when moving the mouse + /// over symbols in the editor. + /// + /// Default: true + pub hover_popover_enabled: Option, + /// Time to wait in milliseconds before showing the informational hover box. + /// + /// Default: 300 + pub hover_popover_delay: Option, + /// Status bar related settings + pub status_bar: Option, + /// Toolbar related settings + pub toolbar: Option, + /// Scrollbar related settings + pub scrollbar: Option, + /// Minimap related settings + pub minimap: Option, + /// Gutter related settings + pub gutter: Option, + /// Whether the editor will scroll beyond the last line. + /// + /// Default: one_page + pub scroll_beyond_last_line: Option, + /// The number of lines to keep above/below the cursor when auto-scrolling. + /// + /// Default: 3. + pub vertical_scroll_margin: Option, + /// Whether to scroll when clicking near the edge of the visible text area. + /// + /// Default: false + pub autoscroll_on_clicks: Option, + /// The number of characters to keep on either side when scrolling with the mouse. + /// + /// Default: 5. + pub horizontal_scroll_margin: Option, + /// Scroll sensitivity multiplier. This multiplier is applied + /// to both the horizontal and vertical delta values while scrolling. + /// + /// Default: 1.0 + pub scroll_sensitivity: Option, + /// Scroll sensitivity multiplier for fast scrolling. This multiplier is applied + /// to both the horizontal and vertical delta values while scrolling. Fast scrolling + /// happens when a user holds the alt or option key while scrolling. + /// + /// Default: 4.0 + pub fast_scroll_sensitivity: Option, + /// Whether the line numbers on editors gutter are relative or not. + /// + /// Default: false + pub relative_line_numbers: Option, + /// When to populate a new search's query based on the text under the cursor. + /// + /// Default: always + pub seed_search_query_from_cursor: Option, + pub use_smartcase_search: Option, + /// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier. + /// + /// Default: alt + pub multi_cursor_modifier: Option, + /// Hide the values of variables in `private` files, as defined by the + /// private_files setting. This only changes the visual representation, + /// the values are still present in the file and can be selected / copied / pasted + /// + /// Default: false + pub redact_private_values: Option, + + /// How many lines to expand the multibuffer excerpts by default + /// + /// Default: 3 + pub expand_excerpt_lines: Option, + + /// How many lines of context to provide in multibuffer excerpts by default + /// + /// Default: 2 + pub excerpt_context_lines: Option, + + /// Whether to enable middle-click paste on Linux + /// + /// Default: true + pub middle_click_paste: Option, + + /// What to do when multibuffer is double clicked in some of its excerpts + /// (parts of singleton buffers). + /// + /// Default: select + pub double_click_in_multibuffer: Option, + /// Whether the editor search results will loop + /// + /// Default: true + pub search_wrap: Option, + + /// Defaults to use when opening a new buffer and project search items. + /// + /// Default: nothing is enabled + pub search: Option, + + /// Whether to automatically show a signature help pop-up or not. + /// + /// Default: false + pub auto_signature_help: Option, + + /// Whether to show the signature help pop-up after completions or bracket pairs inserted. + /// + /// Default: false + pub show_signature_help_after_edits: Option, + /// The minimum APCA perceptual contrast to maintain when + /// rendering text over highlight backgrounds in the editor. + /// + /// Values range from 0 to 106. Set to 0 to disable adjustments. + /// Default: 45 + pub minimum_contrast_for_highlights: Option, + + /// Whether to follow-up empty go to definition responses from the language server or not. + /// `FindAllReferences` allows to look up references of the same symbol instead. + /// `None` disables the fallback. + /// + /// Default: FindAllReferences + pub go_to_definition_fallback: Option, + + /// Jupyter REPL settings. + pub jupyter: Option, + + /// Which level to use to filter out diagnostics displayed in the editor. + /// + /// Affects the editor rendering only, and does not interrupt + /// the functionality of diagnostics fetching and project diagnostics editor. + /// Which files containing diagnostic errors/warnings to mark in the tabs. + /// Diagnostics are only shown when file icons are also active. + /// + /// Shows all diagnostics if not specified. + /// + /// Default: warning + pub diagnostics_max_severity: Option, + + /// Whether to show code action button at start of buffer line. + /// + /// Default: true + pub inline_code_actions: Option, + + /// Drag and drop related settings + pub drag_and_drop_selection: Option, + + /// How to render LSP `textDocument/documentColor` colors in the editor. + /// + /// Default: [`DocumentColorsRenderMode::Inlay`] + pub lsp_document_colors: Option, +} + +// Status bar related settings +#[skip_serializing_none] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct StatusBarContent { + /// Whether to display the active language button in the status bar. + /// + /// Default: true + pub active_language_button: Option, + /// Whether to show the cursor position button in the status bar. + /// + /// Default: true + pub cursor_position_button: Option, +} + +// Toolbar related settings +#[skip_serializing_none] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ToolbarContent { + /// Whether to display breadcrumbs in the editor toolbar. + /// + /// Default: true + pub breadcrumbs: Option, + /// Whether to display quick action buttons in the editor toolbar. + /// + /// Default: true + pub quick_actions: Option, + /// Whether to show the selections menu in the editor toolbar. + /// + /// Default: true + pub selections_menu: Option, + /// Whether to display Agent review buttons in the editor toolbar. + /// Only applicable while reviewing a file edited by the Agent. + /// + /// Default: true + pub agent_review: Option, + /// Whether to display code action buttons in the editor toolbar. + /// + /// Default: false + pub code_actions: Option, +} + +/// Scrollbar related settings +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +pub struct ScrollbarContent { + /// When to show the scrollbar in the editor. + /// + /// Default: auto + pub show: Option, + /// Whether to show git diff indicators in the scrollbar. + /// + /// Default: true + pub git_diff: Option, + /// Whether to show buffer search result indicators in the scrollbar. + /// + /// Default: true + pub search_results: Option, + /// Whether to show selected text occurrences in the scrollbar. + /// + /// Default: true + pub selected_text: Option, + /// Whether to show selected symbol occurrences in the scrollbar. + /// + /// Default: true + pub selected_symbol: Option, + /// Which diagnostic indicators to show in the scrollbar: + /// + /// Default: all + pub diagnostics: Option, + /// Whether to show cursor positions in the scrollbar. + /// + /// Default: true + pub cursors: Option, + /// Forcefully enable or disable the scrollbar for each axis + pub axes: Option, +} + +/// Minimap related settings +#[skip_serializing_none] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct MinimapContent { + /// When to show the minimap in the editor. + /// + /// Default: never + pub show: Option, + + /// Where to show the minimap in the editor. + /// + /// Default: [`DisplayIn::ActiveEditor`] + pub display_in: Option, + + /// When to show the minimap thumb. + /// + /// Default: always + pub thumb: Option, + + /// Defines the border style for the minimap's scrollbar thumb. + /// + /// Default: left_open + pub thumb_border: Option, + + /// How to highlight the current line in the minimap. + /// + /// Default: inherits editor line highlights setting + pub current_line_highlight: Option>, + + /// Maximum number of columns to display in the minimap. + /// + /// Default: 80 + pub max_width_columns: Option, +} + +/// Forcefully enable or disable the scrollbar for each axis +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +pub struct ScrollbarAxesContent { + /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. + /// + /// Default: true + pub horizontal: Option, + + /// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings. + /// + /// Default: true + pub vertical: Option, +} + +/// Gutter related settings +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct GutterContent { + /// Whether to show line numbers in the gutter. + /// + /// Default: true + pub line_numbers: Option, + /// Minimum number of characters to reserve space for in the gutter. + /// + /// Default: 4 + pub min_line_number_digits: Option, + /// Whether to show runnable buttons in the gutter. + /// + /// Default: true + pub runnables: Option, + /// Whether to show breakpoints in the gutter. + /// + /// Default: true + pub breakpoints: Option, + /// Whether to show fold buttons in the gutter. + /// + /// Default: true + pub folds: Option, +} + +/// How to render LSP `textDocument/documentColor` colors in the editor. +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DocumentColorsRenderMode { + /// Do not query and render document colors. + None, + /// Render document colors as inlay hints near the color text. + #[default] + Inlay, + /// Draw a border around the color text. + Border, + /// Draw a background behind the color text. + Background, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CurrentLineHighlight { + // Don't highlight the current line. + None, + // Highlight the gutter area. + Gutter, + // Highlight the editor area. + Line, + // Highlight the full line. + All, +} + +/// When to populate a new search's query based on the text under the cursor. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SeedQuerySetting { + /// Always populate the search query with the word under the cursor. + Always, + /// Only populate the search query when there is text selected. + Selection, + /// Never populate the search query + Never, +} + +/// What to do when multibuffer is double clicked in some of its excerpts (parts of singleton buffers). +#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DoubleClickInMultibuffer { + /// Behave as a regular buffer and select the whole word. + #[default] + Select, + /// Open the excerpt clicked as a new buffer in the new tab, if no `alt` modifier was pressed during double click. + /// Otherwise, behave as a regular buffer and select the whole word. + Open, +} + +/// When to show the minimap thumb. +/// +/// Default: always +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum MinimapThumb { + /// Show the minimap thumb only when the mouse is hovering over the minimap. + Hover, + /// Always show the minimap thumb. + #[default] + Always, +} + +/// Defines the border style for the minimap's scrollbar thumb. +/// +/// Default: left_open +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum MinimapThumbBorder { + /// Displays a border on all sides of the thumb. + Full, + /// Displays a border on all sides except the left side of the thumb. + #[default] + LeftOpen, + /// Displays a border on all sides except the right side of the thumb. + RightOpen, + /// Displays a border only on the left side of the thumb. + LeftOnly, + /// Displays the thumb without any border. + None, +} + +/// Which diagnostic indicators to show in the scrollbar. +/// +/// Default: all +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ScrollbarDiagnostics { + /// Show all diagnostic levels: hint, information, warnings, error. + All, + /// Show only the following diagnostic levels: information, warning, error. + Information, + /// Show only the following diagnostic levels: warning, error. + Warning, + /// Show only the following diagnostic level: error. + Error, + /// Do not show diagnostics. + None, +} + +/// The key to use for adding multiple cursors +/// +/// Default: alt +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum MultiCursorModifier { + Alt, + #[serde(alias = "cmd", alias = "ctrl")] + CmdOrCtrl, +} + +/// Whether the editor will scroll beyond the last line. +/// +/// Default: one_page +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ScrollBeyondLastLine { + /// The editor will not scroll beyond the last line. + Off, + + /// The editor will scroll beyond the last line by one page. + OnePage, + + /// The editor will scroll beyond the last line by the same number of lines as vertical_scroll_margin. + VerticalScrollMargin, +} + +/// The shape of a selection cursor. +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CursorShape { + /// A vertical bar + #[default] + Bar, + /// A block that surrounds the following character + Block, + /// An underline that runs along the following character + Underline, + /// A box drawn around the following character + Hollow, +} + +/// What to do when go to definition yields no results. +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GoToDefinitionFallback { + /// Disables the fallback. + None, + /// Looks up references of the same symbol instead. + #[default] + FindAllReferences, +} + +/// Determines when the mouse cursor should be hidden in an editor or input box. +/// +/// Default: on_typing_and_movement +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HideMouseMode { + /// Never hide the mouse cursor + Never, + /// Hide only when typing + OnTyping, + /// Hide on both typing and cursor movement + #[default] + OnTypingAndMovement, +} + +/// Determines how snippets are sorted relative to other completion items. +/// +/// Default: inline +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SnippetSortOrder { + /// Place snippets at the top of the completion list + Top, + /// Sort snippets normally using the default comparison logic + #[default] + Inline, + /// Place snippets at the bottom of the completion list + Bottom, + /// Do not show snippets in the completion list + None, +} + +/// Default options for buffer and project search items. +#[skip_serializing_none] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct SearchSettingsContent { + /// Whether to show the project search button in the status bar. + pub button: Option, + pub whole_word: Option, + pub case_sensitive: Option, + pub include_ignored: Option, + pub regex: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct JupyterContent { + /// Whether the Jupyter feature is enabled. + /// + /// Default: true + pub enabled: Option, + + /// Default kernels to select for each language. + /// + /// Default: `{}` + pub kernel_selections: Option>, +} + +/// Whether to allow drag and drop text selection in buffer. +#[skip_serializing_none] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct DragAndDropSelectionContent { + /// When true, enables drag and drop text selection in buffer. + /// + /// Default: true + pub enabled: Option, + + /// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created. + /// + /// Default: 300 + pub delay: Option, +} + +/// When to show the minimap in the editor. +/// +/// Default: never +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ShowMinimap { + /// Follow the visibility of the scrollbar. + Auto, + /// Always show the minimap. + Always, + /// Never show the minimap. + #[default] + Never, +} + +/// Where to show the minimap in the editor. +/// +/// Default: all_editors +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum DisplayIn { + /// Show on all open editors. + AllEditors, + /// Show the minimap on the active editor only. + #[default] + ActiveEditor, +} diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs new file mode 100644 index 0000000000000000000000000000000000000000..de8cf378d1e25f7e84047d788816572ffd97d25c --- /dev/null +++ b/crates/settings/src/settings_content/language.rs @@ -0,0 +1,894 @@ +use std::{borrow::Cow, num::NonZeroU32}; + +use collections::{HashMap, HashSet}; +use gpui::{Modifiers, SharedString}; +use schemars::{JsonSchema, json_schema}; +use serde::{ + Deserialize, Deserializer, Serialize, + de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor}, +}; +use serde_with::skip_serializing_none; +use std::sync::Arc; +use util::schemars::replace_subschema; + +use crate::ParameterizedJsonSchema; + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AllLanguageSettingsContent { + /// The settings for enabling/disabling features. + #[serde(default)] + pub features: Option, + /// The edit prediction settings. + #[serde(default)] + pub edit_predictions: Option, + /// The default language settings. + #[serde(flatten)] + pub defaults: LanguageSettingsContent, + /// The settings for individual languages. + #[serde(default)] + pub languages: LanguageToSettingsMap, + /// Settings for associating file extensions and filenames + /// with languages. + #[serde(default)] + pub file_types: HashMap, Vec>, +} + +/// The settings for enabling/disabling features. +#[skip_serializing_none] +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct FeaturesContent { + /// Determines which edit prediction provider to use. + pub edit_prediction_provider: Option, +} + +/// The provider that supplies edit predictions. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum EditPredictionProvider { + None, + #[default] + Copilot, + Supermaven, + Zed, +} + +/// The contents of the edit prediction settings. +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct EditPredictionSettingsContent { + /// A list of globs representing files that edit predictions should be disabled for. + /// This list adds to a pre-existing, sensible default set of globs. + /// Any additional ones you add are combined with them. + pub disabled_globs: Option>, + /// The mode used to display edit predictions in the buffer. + /// Provider support required. + pub mode: Option, + /// Settings specific to GitHub Copilot. + pub copilot: Option, + /// Whether edit predictions are enabled in the assistant prompt editor. + /// This has no effect if globally disabled. + pub enabled_in_text_threads: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct CopilotSettingsContent { + /// HTTP/HTTPS proxy to use for Copilot. + /// + /// Default: none + pub proxy: Option, + /// Disable certificate verification for the proxy (not recommended). + /// + /// Default: false + pub proxy_no_verify: Option, + /// Enterprise URI for Copilot. + /// + /// Default: none + pub enterprise_uri: Option, +} + +/// The mode in which edit predictions should be displayed. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum EditPredictionsMode { + /// If provider supports it, display inline when holding modifier key (e.g., alt). + /// Otherwise, eager preview is used. + #[serde(alias = "auto")] + Subtle, + /// Display inline when there are no language server completions available. + #[default] + #[serde(alias = "eager_preview")] + Eager, +} + +/// Controls the soft-wrapping behavior in the editor. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SoftWrap { + /// Prefer a single line generally, unless an overly long line is encountered. + None, + /// Deprecated: use None instead. Left to avoid breaking existing users' configs. + /// Prefer a single line generally, unless an overly long line is encountered. + PreferLine, + /// Soft wrap lines that exceed the editor width. + EditorWidth, + /// Soft wrap lines at the preferred line length. + PreferredLineLength, + /// Soft wrap line at the preferred line length or the editor width (whichever is smaller). + Bounded, +} + +/// The settings for a particular language. +#[skip_serializing_none] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct LanguageSettingsContent { + /// How many columns a tab should occupy. + /// + /// Default: 4 + pub tab_size: Option, + /// Whether to indent lines using tab characters, as opposed to multiple + /// spaces. + /// + /// Default: false + pub hard_tabs: Option, + /// How to soft-wrap long lines of text. + /// + /// Default: none + pub soft_wrap: Option, + /// The column at which to soft-wrap lines, for buffers where soft-wrap + /// is enabled. + /// + /// Default: 80 + pub preferred_line_length: Option, + /// Whether to show wrap guides in the editor. Setting this to true will + /// show a guide at the 'preferred_line_length' value if softwrap is set to + /// 'preferred_line_length', and will show any additional guides as specified + /// by the 'wrap_guides' setting. + /// + /// Default: true + pub show_wrap_guides: Option, + /// Character counts at which to show wrap guides in the editor. + /// + /// Default: [] + pub wrap_guides: Option>, + /// Indent guide related settings. + pub indent_guides: Option, + /// Whether or not to perform a buffer format before saving. + /// + /// Default: on + pub format_on_save: Option, + /// Whether or not to remove any trailing whitespace from lines of a buffer + /// before saving it. + /// + /// Default: true + pub remove_trailing_whitespace_on_save: Option, + /// Whether or not to ensure there's a single newline at the end of a buffer + /// when saving it. + /// + /// Default: true + pub ensure_final_newline_on_save: Option, + /// How to perform a buffer format. + /// + /// Default: auto + pub formatter: Option, + /// Zed's Prettier integration settings. + /// Allows to enable/disable formatting with Prettier + /// and configure default Prettier, used when no project-level Prettier installation is found. + /// + /// Default: off + pub prettier: Option, + /// Whether to automatically close JSX tags. + pub jsx_tag_auto_close: Option, + /// Whether to use language servers to provide code intelligence. + /// + /// Default: true + pub enable_language_server: Option, + /// The list of language servers to use (or disable) for this language. + /// + /// This array should consist of language server IDs, as well as the following + /// special tokens: + /// - `"!"` - A language server ID prefixed with a `!` will be disabled. + /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language. + /// + /// Default: ["..."] + pub language_servers: Option>, + /// Controls where the `editor::Rewrap` action is allowed for this language. + /// + /// Note: This setting has no effect in Vim mode, as rewrap is already + /// allowed everywhere. + /// + /// Default: "in_comments" + pub allow_rewrap: Option, + /// Controls whether edit predictions are shown immediately (true) + /// or manually by triggering `editor::ShowEditPrediction` (false). + /// + /// Default: true + pub show_edit_predictions: Option, + /// Controls whether edit predictions are shown in the given language + /// scopes. + /// + /// Example: ["string", "comment"] + /// + /// Default: [] + pub edit_predictions_disabled_in: Option>, + /// Whether to show tabs and spaces in the editor. + pub show_whitespaces: Option, + /// Visible characters used to render whitespace when show_whitespaces is enabled. + /// + /// Default: "•" for spaces, "→" for tabs. + pub whitespace_map: Option, + /// Whether to start a new line with a comment when a previous line is a comment as well. + /// + /// Default: true + pub extend_comment_on_newline: Option, + /// Inlay hint related settings. + pub inlay_hints: Option, + /// Whether to automatically type closing characters for you. For example, + /// when you type (, Zed will automatically add a closing ) at the correct position. + /// + /// Default: true + pub use_autoclose: Option, + /// Whether to automatically surround text with characters for you. For example, + /// when you select text and type (, Zed will automatically surround text with (). + /// + /// Default: true + pub use_auto_surround: Option, + /// Controls how the editor handles the autoclosed characters. + /// When set to `false`(default), skipping over and auto-removing of the closing characters + /// happen only for auto-inserted characters. + /// Otherwise(when `true`), the closing characters are always skipped over and auto-removed + /// no matter how they were inserted. + /// + /// Default: false + pub always_treat_brackets_as_autoclosed: Option, + /// Whether to use additional LSP queries to format (and amend) the code after + /// every "trigger" symbol input, defined by LSP server capabilities. + /// + /// Default: true + pub use_on_type_format: Option, + /// Which code actions to run on save after the formatter. + /// These are not run if formatting is off. + /// + /// Default: {} (or {"source.organizeImports": true} for Go). + pub code_actions_on_format: Option>, + /// Whether to perform linked edits of associated ranges, if the language server supports it. + /// For example, when editing opening tag, the contents of the closing tag will be edited as well. + /// + /// Default: true + pub linked_edits: Option, + /// Whether indentation should be adjusted based on the context whilst typing. + /// + /// Default: true + pub auto_indent: Option, + /// Whether indentation of pasted content should be adjusted based on the context. + /// + /// Default: true + pub auto_indent_on_paste: Option, + /// Task configuration for this language. + /// + /// Default: {} + pub tasks: Option, + /// Whether to pop the completions menu while typing in an editor without + /// explicitly requesting it. + /// + /// Default: true + pub show_completions_on_input: Option, + /// Whether to display inline and alongside documentation for items in the + /// completions menu. + /// + /// Default: true + pub show_completion_documentation: Option, + /// Controls how completions are processed for this language. + pub completions: Option, + /// Preferred debuggers for this language. + /// + /// Default: [] + pub debuggers: Option>, +} + +/// Controls how whitespace should be displayedin the editor. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ShowWhitespaceSetting { + /// Draw whitespace only for the selected text. + Selection, + /// Do not draw any tabs or spaces. + None, + /// Draw all invisible symbols. + All, + /// Draw whitespaces at boundaries only. + /// + /// For a whitespace to be on a boundary, any of the following conditions need to be met: + /// - It is a tab + /// - It is adjacent to an edge (start or end) + /// - It is adjacent to a whitespace (left or right) + Boundary, + /// Draw whitespaces only after non-whitespace characters. + Trailing, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct WhitespaceMap { + pub space: Option, + pub tab: Option, +} + +impl WhitespaceMap { + pub fn space(&self) -> SharedString { + self.space + .as_ref() + .map_or_else(|| SharedString::from("•"), |s| SharedString::from(s)) + } + + pub fn tab(&self) -> SharedString { + self.tab + .as_ref() + .map_or_else(|| SharedString::from("→"), |s| SharedString::from(s)) + } +} + +/// The behavior of `editor::Rewrap`. +#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RewrapBehavior { + /// Only rewrap within comments. + #[default] + InComments, + /// Only rewrap within the current selection(s). + InSelections, + /// Allow rewrapping anywhere. + Anywhere, +} + +#[skip_serializing_none] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct JsxTagAutoCloseSettingsContent { + /// Enables or disables auto-closing of JSX tags. + pub enabled: Option, +} + +/// The settings for inlay hints. +#[skip_serializing_none] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct InlayHintSettingsContent { + /// Global switch to toggle hints on and off. + /// + /// Default: false + pub enabled: Option, + /// Global switch to toggle inline values on and off when debugging. + /// + /// Default: true + pub show_value_hints: Option, + /// Whether type hints should be shown. + /// + /// Default: true + pub show_type_hints: Option, + /// Whether parameter hints should be shown. + /// + /// Default: true + pub show_parameter_hints: Option, + /// Whether other hints should be shown. + /// + /// Default: true + pub show_other_hints: Option, + /// Whether to show a background for inlay hints. + /// + /// If set to `true`, the background will use the `hint.background` color + /// from the current theme. + /// + /// Default: false + pub show_background: Option, + /// Whether or not to debounce inlay hints updates after buffer edits. + /// + /// Set to 0 to disable debouncing. + /// + /// Default: 700 + pub edit_debounce_ms: Option, + /// Whether or not to debounce inlay hints updates after buffer scrolls. + /// + /// Set to 0 to disable debouncing. + /// + /// Default: 50 + pub scroll_debounce_ms: Option, + /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified. + /// If only a subset of the modifiers specified is pressed, hints are not toggled. + /// If no modifiers are specified, this is equivalent to `null`. + /// + /// Default: null + pub toggle_on_modifiers_press: Option, +} + +/// The kind of an inlay hint. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InlayHintKind { + /// An inlay hint for a type. + Type, + /// An inlay hint for a parameter. + Parameter, +} + +impl InlayHintKind { + /// Returns the [`InlayHintKind`]fromthe given name. + /// + /// Returns `None` if `name` does not match any of the expected + /// string representations. + pub fn from_name(name: &str) -> Option { + match name { + "type" => Some(InlayHintKind::Type), + "parameter" => Some(InlayHintKind::Parameter), + _ => None, + } + } + + /// Returns the name of this [`InlayHintKind`]. + pub fn name(&self) -> &'static str { + match self { + InlayHintKind::Type => "type", + InlayHintKind::Parameter => "parameter", + } + } +} + +/// Controls how completions are processed for this language. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub struct CompletionSettingsContent { + /// Controls how words are completed. + /// For large documents, not all words may be fetched for completion. + /// + /// Default: `fallback` + pub words: Option, + /// How many characters has to be in the completions query to automatically show the words-based completions. + /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command. + /// + /// Default: 3 + pub words_min_length: Option, + /// Whether to fetch LSP completions or not. + /// + /// Default: true + pub lsp: Option, + /// When fetching LSP completions, determines how long to wait for a response of a particular server. + /// When set to 0, waits indefinitely. + /// + /// Default: 0 + pub lsp_fetch_timeout_ms: Option, + /// Controls how LSP completions are inserted. + /// + /// Default: "replace_suffix" + pub lsp_insert_mode: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum LspInsertMode { + /// Replaces text before the cursor, using the `insert` range described in the LSP specification. + Insert, + /// Replaces text before and after the cursor, using the `replace` range described in the LSP specification. + Replace, + /// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text, + /// and like `"insert"` otherwise. + ReplaceSubsequence, + /// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like + /// `"insert"` otherwise. + ReplaceSuffix, +} + +/// Controls how document's words are completed. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WordsCompletionMode { + /// Always fetch document's words for completions along with LSP completions. + Enabled, + /// Only if LSP response errors or times out, + /// use document's words to show completions. + Fallback, + /// Never fetch or complete document's words for completions. + /// (Word-based completions can still be queried via a separate action) + Disabled, +} + +/// Allows to enable/disable formatting with Prettier +/// and configure default Prettier, used when no project-level Prettier installation is found. +/// Prettier formatting is disabled by default. +#[skip_serializing_none] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct PrettierSettingsContent { + /// Enables or disables formatting with Prettier for a given language. + pub allowed: Option, + + /// Forces Prettier integration to use a specific parser name when formatting files with the language. + pub parser: Option, + + /// Forces Prettier integration to use specific plugins when formatting files with the language. + /// The default Prettier will be installed with these plugins. + #[serde(default)] + pub plugins: HashSet, + + /// Default Prettier options, in the format as in package.json section for Prettier. + /// If project installs Prettier via its package.json, these options will be ignored. + #[serde(flatten)] + pub options: HashMap, +} + +/// Controls the behavior of formatting files when they are saved. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FormatOnSave { + /// Files should be formatted on save. + On, + /// Files should not be formatted on save. + Off, + List(FormatterList), +} + +impl JsonSchema for FormatOnSave { + fn schema_name() -> Cow<'static, str> { + "OnSaveFormatter".into() + } + + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + let formatter_schema = Formatter::json_schema(generator); + + json_schema!({ + "oneOf": [ + { + "type": "array", + "items": formatter_schema + }, + { + "type": "string", + "enum": ["on", "off", "language_server"] + }, + formatter_schema + ] + }) + } +} + +impl Serialize for FormatOnSave { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + match self { + Self::On => serializer.serialize_str("on"), + Self::Off => serializer.serialize_str("off"), + Self::List(list) => list.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for FormatOnSave { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + struct FormatDeserializer; + + impl<'d> Visitor<'d> for FormatDeserializer { + type Value = FormatOnSave; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid on-save formatter kind") + } + fn visit_str(self, v: &str) -> std::result::Result + where + E: serde::de::Error, + { + if v == "on" { + Ok(Self::Value::On) + } else if v == "off" { + Ok(Self::Value::Off) + } else if v == "language_server" { + Ok(Self::Value::List(FormatterList::Single( + Formatter::LanguageServer { name: None }, + ))) + } else { + let ret: Result = + Deserialize::deserialize(v.into_deserializer()); + ret.map(Self::Value::List) + } + } + fn visit_map(self, map: A) -> Result + where + A: MapAccess<'d>, + { + let ret: Result = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); + ret.map(Self::Value::List) + } + fn visit_seq(self, map: A) -> Result + where + A: SeqAccess<'d>, + { + let ret: Result = + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); + ret.map(Self::Value::List) + } + } + deserializer.deserialize_any(FormatDeserializer) + } +} + +/// Controls which formatter should be used when formatting code. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum SelectedFormatter { + /// Format files using Zed's Prettier integration (if applicable), + /// or falling back to formatting via language server. + #[default] + Auto, + List(FormatterList), +} + +impl JsonSchema for SelectedFormatter { + fn schema_name() -> Cow<'static, str> { + "Formatter".into() + } + + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + let formatter_schema = Formatter::json_schema(generator); + + json_schema!({ + "oneOf": [ + { + "type": "array", + "items": formatter_schema + }, + { + "type": "string", + "enum": ["auto", "language_server"] + }, + formatter_schema + ] + }) + } +} + +impl Serialize for SelectedFormatter { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + match self { + SelectedFormatter::Auto => serializer.serialize_str("auto"), + SelectedFormatter::List(list) => list.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for SelectedFormatter { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + struct FormatDeserializer; + + impl<'d> Visitor<'d> for FormatDeserializer { + type Value = SelectedFormatter; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid formatter kind") + } + fn visit_str(self, v: &str) -> std::result::Result + where + E: serde::de::Error, + { + if v == "auto" { + Ok(Self::Value::Auto) + } else if v == "language_server" { + Ok(Self::Value::List(FormatterList::Single( + Formatter::LanguageServer { name: None }, + ))) + } else { + let ret: Result = + Deserialize::deserialize(v.into_deserializer()); + ret.map(SelectedFormatter::List) + } + } + fn visit_map(self, map: A) -> Result + where + A: MapAccess<'d>, + { + let ret: Result = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); + ret.map(SelectedFormatter::List) + } + fn visit_seq(self, map: A) -> Result + where + A: SeqAccess<'d>, + { + let ret: Result = + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); + ret.map(SelectedFormatter::List) + } + } + deserializer.deserialize_any(FormatDeserializer) + } +} + +/// Controls which formatters should be used when formatting code. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(untagged)] +pub enum FormatterList { + Single(Formatter), + Vec(Vec), +} + +impl Default for FormatterList { + fn default() -> Self { + Self::Single(Formatter::default()) + } +} + +impl AsRef<[Formatter]> for FormatterList { + fn as_ref(&self) -> &[Formatter] { + match &self { + Self::Single(single) => std::slice::from_ref(single), + Self::Vec(v) => v, + } + } +} + +/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration. +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Formatter { + /// Format code using the current language server. + LanguageServer { name: Option }, + /// Format code using Zed's Prettier integration. + #[default] + Prettier, + /// Format code using an external command. + External { + /// The external program to run. + command: Arc, + /// The arguments to pass to the program. + arguments: Option>, + }, + /// Files should be formatted using code actions executed by language servers. + CodeActions(HashMap), +} + +/// The settings for indent guides. +#[skip_serializing_none] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct IndentGuideSettingsContent { + /// Whether to display indent guides in the editor. + /// + /// Default: true + pub enabled: Option, + /// The width of the indent guides in pixels, between 1 and 10. + /// + /// Default: 1 + pub line_width: Option, + /// The width of the active indent guide in pixels, between 1 and 10. + /// + /// Default: 1 + pub active_line_width: Option, + /// Determines how indent guides are colored. + /// + /// Default: Fixed + pub coloring: Option, + /// Determines how indent guide backgrounds are colored. + /// + /// Default: Disabled + pub background_coloring: Option, +} + +/// The task settings for a particular language. +#[skip_serializing_none] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct LanguageTaskSettingsContent { + /// Extra task variables to set for a particular language. + #[serde(default)] + pub variables: HashMap, + pub enabled: Option, + /// Use LSP tasks over Zed language extension ones. + /// If no LSP tasks are returned due to error/timeout or regular execution, + /// Zed language extension tasks will be used instead. + /// + /// Other Zed tasks will still be shown: + /// * Zed task from either of the task config file + /// * Zed task from history (e.g. one-off task was spawned before) + pub prefer_lsp: Option, +} + +/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language +/// names in the keys. +#[skip_serializing_none] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct LanguageToSettingsMap(pub HashMap); + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, params, _cx| { + let language_settings_content_ref = generator + .subschema_for::() + .to_value(); + replace_subschema::(generator, || json_schema!({ + "type": "object", + "properties": params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + language_settings_content_ref.clone(), + ) + }) + .collect::>() + })) + } + } +} + +/// Determines how indent guides are colored. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum IndentGuideColoring { + /// Do not render any lines for indent guides. + Disabled, + /// Use the same color for all indentation levels. + #[default] + Fixed, + /// Use a different color for each indentation level. + IndentAware, +} + +/// Determines how indent guide backgrounds are colored. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum IndentGuideBackgroundColoring { + /// Do not render any background for indent guides. + #[default] + Disabled, + /// Use a different color for each indentation level. + IndentAware, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_formatter_deserialization() { + let raw_auto = "{\"formatter\": \"auto\"}"; + let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap(); + assert_eq!(settings.formatter, Some(SelectedFormatter::Auto)); + let raw = "{\"formatter\": \"language_server\"}"; + let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); + assert_eq!( + settings.formatter, + Some(SelectedFormatter::List(FormatterList::Single( + Formatter::LanguageServer { name: None } + ))) + ); + let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}"; + let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); + assert_eq!( + settings.formatter, + Some(SelectedFormatter::List(FormatterList::Vec(vec![ + Formatter::LanguageServer { name: None } + ]))) + ); + let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}"; + let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); + assert_eq!( + settings.formatter, + Some(SelectedFormatter::List(FormatterList::Vec(vec![ + Formatter::LanguageServer { name: None }, + Formatter::Prettier + ]))) + ); + } + + #[test] + fn test_formatter_deserialization_invalid() { + let raw_auto = "{\"formatter\": {}}"; + let result: Result = serde_json::from_str(raw_auto); + assert!(result.is_err()); + } +} diff --git a/crates/settings/src/settings_content/language_model.rs b/crates/settings/src/settings_content/language_model.rs new file mode 100644 index 0000000000000000000000000000000000000000..cad06d5fff78d1fa4ad9cd8830ab24a438d3c9ea --- /dev/null +++ b/crates/settings/src/settings_content/language_model.rs @@ -0,0 +1,419 @@ +use collections::HashMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +use std::sync::Arc; + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct AllLanguageModelSettingsContent { + pub anthropic: Option, + pub bedrock: Option, + pub deepseek: Option, + pub google: Option, + pub lmstudio: Option, + pub mistral: Option, + pub ollama: Option, + pub open_router: Option, + pub openai: Option, + pub openai_compatible: Option, OpenAiCompatibleSettingsContent>>, + pub vercel: Option, + pub x_ai: Option, + #[serde(rename = "zed.dev")] + pub zed_dot_dev: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct AnthropicSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AnthropicAvailableModel { + /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc + pub name: String, + /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. + pub display_name: Option, + /// The model's context window size. + pub max_tokens: u64, + /// A model `name` to substitute when calling tools, in case the primary model doesn't support tool calling. + pub tool_override: Option, + /// Configuration of Anthropic's caching API. + pub cache_configuration: Option, + pub max_output_tokens: Option, + pub default_temperature: Option, + #[serde(default)] + pub extra_beta_headers: Vec, + /// The model's mode (e.g. thinking) + pub mode: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct AmazonBedrockSettingsContent { + pub available_models: Option>, + pub endpoint_url: Option, + pub region: Option, + pub profile: Option, + pub authentication_method: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct BedrockAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub cache_configuration: Option, + pub max_output_tokens: Option, + pub default_temperature: Option, + pub mode: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub enum BedrockAuthMethodContent { + #[serde(rename = "named_profile")] + NamedProfile, + #[serde(rename = "sso")] + SingleSignOn, + /// IMDSv2, PodIdentity, env vars, etc. + #[serde(rename = "default")] + Automatic, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct OllamaSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct OllamaAvailableModel { + /// The model name in the Ollama API (e.g. "llama3.2:latest") + pub name: String, + /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. + pub display_name: Option, + /// The Context Length parameter to the model (aka num_ctx or n_ctx) + pub max_tokens: u64, + /// The number of seconds to keep the connection open after the last request + pub keep_alive: Option, + /// Whether the model supports tools + pub supports_tools: Option, + /// Whether the model supports vision + pub supports_images: Option, + /// Whether to enable think mode + pub supports_thinking: Option, +} + +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema)] +#[serde(untagged)] +pub enum KeepAlive { + /// Keep model alive for N seconds + Seconds(isize), + /// Keep model alive for a fixed duration. Accepts durations like "5m", "10m", "1h", "1d", etc. + Duration(String), +} + +impl KeepAlive { + /// Keep model alive until a new model is loaded or until Ollama shuts down + pub fn indefinite() -> Self { + Self::Seconds(-1) + } +} + +impl Default for KeepAlive { + fn default() -> Self { + Self::indefinite() + } +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct LmStudioSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct LmStudioAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub supports_tool_calls: bool, + pub supports_images: bool, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct DeepseekSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct DeepseekAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct MistralSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct MistralAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, + pub max_completion_tokens: Option, + pub supports_tools: Option, + pub supports_images: Option, + pub supports_thinking: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct OpenAiSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct OpenAiAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, + pub max_completion_tokens: Option, + pub reasoning_effort: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum OpenAiReasoningEffort { + Minimal, + Low, + Medium, + High, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct OpenAiCompatibleSettingsContent { + pub api_url: String, + pub available_models: Vec, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct OpenAiCompatibleAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, + pub max_completion_tokens: Option, + #[serde(default)] + pub capabilities: OpenAiCompatibleModelCapabilities, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct OpenAiCompatibleModelCapabilities { + pub tools: bool, + pub images: bool, + pub parallel_tool_calls: bool, + pub prompt_cache_key: bool, +} + +impl Default for OpenAiCompatibleModelCapabilities { + fn default() -> Self { + Self { + tools: true, + images: false, + parallel_tool_calls: false, + prompt_cache_key: false, + } + } +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct VercelSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct VercelAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, + pub max_completion_tokens: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct GoogleSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct GoogleAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub mode: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct XAiSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct XaiAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, + pub max_completion_tokens: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct ZedDotDevSettingsContent { + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct ZedDotDevAvailableModel { + /// The provider of the language model. + pub provider: ZedDotDevAvailableProvider, + /// The model's name in the provider's API. e.g. claude-3-5-sonnet-20240620 + pub name: String, + /// The name displayed in the UI, such as in the assistant panel model dropdown menu. + pub display_name: Option, + /// The size of the context window, indicating the maximum number of tokens the model can process. + pub max_tokens: usize, + /// The maximum number of output tokens allowed by the model. + pub max_output_tokens: Option, + /// The maximum number of completion tokens allowed by the model (o1-* only) + pub max_completion_tokens: Option, + /// Override this model with a different Anthropic model for tool calls. + pub tool_override: Option, + /// Indicates whether this custom model supports caching. + pub cache_configuration: Option, + /// The default temperature to use for this model. + pub default_temperature: Option, + /// Any extra beta headers to provide when using the model. + #[serde(default)] + pub extra_beta_headers: Vec, + /// The model's mode (e.g. thinking) + pub mode: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ZedDotDevAvailableProvider { + Anthropic, + OpenAi, + Google, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct OpenRouterSettingsContent { + pub api_url: Option, + pub available_models: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct OpenRouterAvailableModel { + pub name: String, + pub display_name: Option, + pub max_tokens: u64, + pub max_output_tokens: Option, + pub max_completion_tokens: Option, + pub supports_tools: Option, + pub supports_images: Option, + pub mode: Option, + pub provider: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct OpenRouterProvider { + order: Option>, + #[serde(default = "default_true")] + allow_fallbacks: bool, + #[serde(default)] + require_parameters: bool, + #[serde(default)] + data_collection: DataCollection, + only: Option>, + ignore: Option>, + quantizations: Option>, + sort: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum DataCollection { + Allow, + Disallow, +} + +impl Default for DataCollection { + fn default() -> Self { + Self::Allow + } +} + +fn default_true() -> bool { + true +} + +/// Configuration for caching language model messages. +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct LanguageModelCacheConfiguration { + pub max_cache_anchors: usize, + pub should_speculate: bool, + pub min_total_token: u64, +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum ModelMode { + #[default] + Default, + Thinking { + /// The maximum number of tokens to use for reasoning. Must be lower than the model's `max_output_tokens`. + budget_tokens: Option, + }, +} diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c154dd8ea337ae165cb1e76872640a8af334fe3 --- /dev/null +++ b/crates/settings/src/settings_content/project.rs @@ -0,0 +1,435 @@ +use std::{path::PathBuf, sync::Arc}; + +use collections::{BTreeMap, HashMap}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use util::serde::default_true; + +use crate::{AllLanguageSettingsContent, SlashCommandSettings}; + +#[skip_serializing_none] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct ProjectSettingsContent { + #[serde(flatten)] + pub all_languages: AllLanguageSettingsContent, + + #[serde(flatten)] + pub worktree: WorktreeSettingsContent, + + /// Configuration for language servers. + /// + /// The following settings can be overridden for specific language servers: + /// - initialization_options + /// + /// To override settings for a language, add an entry for that language server's + /// name to the lsp value. + /// Default: null + #[serde(default)] + pub lsp: HashMap, LspSettings>, + + /// Configuration for Debugger-related features + #[serde(default)] + pub dap: HashMap, DapSettingsContent>, + + /// Settings for context servers used for AI-related features. + #[serde(default)] + pub context_servers: HashMap, ContextServerSettingsContent>, + + /// Configuration for how direnv configuration should be loaded + pub load_direnv: Option, + + /// Settings for slash commands. + pub slash_commands: Option, + + /// The list of custom Git hosting providers. + pub git_hosting_providers: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct WorktreeSettingsContent { + /// The displayed name of this project. If not set, the root directory name + /// will be displayed. + /// + /// Default: none + pub project_name: Option, + + /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides + /// `file_scan_inclusions`. + /// + /// Default: [ + /// "**/.git", + /// "**/.svn", + /// "**/.hg", + /// "**/.jj", + /// "**/CVS", + /// "**/.DS_Store", + /// "**/Thumbs.db", + /// "**/.classpath", + /// "**/.settings" + /// ] + pub file_scan_exclusions: Option>, + + /// Always include files that match these globs when scanning for files, even if they're + /// ignored by git. This setting is overridden by `file_scan_exclusions`. + /// Default: [ + /// ".env*", + /// "docker-compose.*.yml", + /// ] + pub file_scan_inclusions: Option>, + + /// Treat the files matching these globs as `.env` files. + /// Default: ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"] + pub private_files: Option>, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[serde(rename_all = "snake_case")] +pub struct LspSettings { + pub binary: Option, + pub initialization_options: Option, + pub settings: Option, + /// If the server supports sending tasks over LSP extensions, + /// this setting can be used to enable or disable them in Zed. + /// Default: true + #[serde(default = "default_true")] + pub enable_lsp_tasks: bool, + pub fetch: Option, +} + +impl Default for LspSettings { + fn default() -> Self { + Self { + binary: None, + initialization_options: None, + settings: None, + enable_lsp_tasks: true, + fetch: None, + } + } +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +pub struct BinarySettings { + pub path: Option, + pub arguments: Option>, + pub env: Option>, + pub ignore_system_version: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +pub struct FetchSettings { + // Whether to consider pre-releases for fetching + pub pre_release: Option, +} + +/// Common language server settings. +#[skip_serializing_none] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct GlobalLspSettingsContent { + /// Whether to show the LSP servers button in the status bar. + /// + /// Default: `true` + pub button: Option, +} + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct DapSettingsContent { + pub binary: Option, + #[serde(default)] + pub args: Option>, +} + +#[skip_serializing_none] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] +pub struct SessionSettingsContent { + /// Whether or not to restore unsaved buffers on restart. + /// + /// If this is true, user won't be prompted whether to save/discard + /// dirty files when closing the application. + /// + /// Default: true + pub restore_unsaved_buffers: Option, +} + +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(tag = "source", rename_all = "snake_case")] +pub enum ContextServerSettingsContent { + Custom { + /// Whether the context server is enabled. + #[serde(default = "default_true")] + enabled: bool, + + #[serde(flatten)] + command: ContextServerCommand, + }, + Extension { + /// Whether the context server is enabled. + #[serde(default = "default_true")] + enabled: bool, + /// The settings for this context server specified by the extension. + /// + /// Consult the documentation for the context server to see what settings + /// are supported. + settings: serde_json::Value, + }, +} +impl ContextServerSettingsContent { + pub fn set_enabled(&mut self, enabled: bool) { + match self { + ContextServerSettingsContent::Custom { + enabled: custom_enabled, + command: _, + } => { + *custom_enabled = enabled; + } + ContextServerSettingsContent::Extension { + enabled: ext_enabled, + settings: _, + } => *ext_enabled = enabled, + } + } +} + +#[skip_serializing_none] +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)] +pub struct ContextServerCommand { + #[serde(rename = "command")] + pub path: PathBuf, + pub args: Vec, + pub env: Option>, + /// Timeout for tool calls in milliseconds. Defaults to 60000 (60 seconds) if not specified. + pub timeout: Option, +} + +impl std::fmt::Debug for ContextServerCommand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let filtered_env = self.env.as_ref().map(|env| { + env.iter() + .map(|(k, v)| { + ( + k, + if util::redact::should_redact(k) { + "[REDACTED]" + } else { + v + }, + ) + }) + .collect::>() + }); + + f.debug_struct("ContextServerCommand") + .field("path", &self.path) + .field("args", &self.args) + .field("env", &filtered_env) + .finish() + } +} + +#[skip_serializing_none] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +pub struct GitSettings { + /// Whether or not to show the git gutter. + /// + /// Default: tracked_files + pub git_gutter: Option, + /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter. + /// + /// Default: null + pub gutter_debounce: Option, + /// Whether or not to show git blame data inline in + /// the currently focused line. + /// + /// Default: on + pub inline_blame: Option, + /// Which information to show in the branch picker. + /// + /// Default: on + pub branch_picker: Option, + /// How hunks are displayed visually in the editor. + /// + /// Default: staged_hollow + pub hunk_style: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitGutterSetting { + /// Show git gutter in tracked files. + #[default] + TrackedFiles, + /// Hide git gutter + Hide, +} + +#[skip_serializing_none] +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct InlineBlameSettings { + /// Whether or not to show git blame data inline in + /// the currently focused line. + /// + /// Default: true + pub enabled: Option, + /// Whether to only show the inline blame information + /// after a delay once the cursor stops moving. + /// + /// Default: 0 + pub delay_ms: Option, + /// The amount of padding between the end of the source line and the start + /// of the inline blame in units of columns. + /// + /// Default: 7 + pub padding: Option, + /// The minimum column number to show the inline blame information at + /// + /// Default: 0 + pub min_column: Option, + /// Whether to show commit summary as part of the inline blame. + /// + /// Default: false + pub show_commit_summary: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct BranchPickerSettingsContent { + /// Whether to show author name as part of the commit information. + /// + /// Default: false + pub show_author_name: Option, +} + +#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitHunkStyleSetting { + /// Show unstaged hunks with a filled background and staged hunks hollow. + #[default] + StagedHollow, + /// Show unstaged hunks hollow and staged hunks with a filled background. + UnstagedHollow, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct DiagnosticsSettingsContent { + /// Whether to show the project diagnostics button in the status bar. + pub button: Option, + + /// Whether or not to include warning diagnostics. + pub include_warnings: Option, + + /// Settings for using LSP pull diagnostics mechanism in Zed. + pub lsp_pull_diagnostics: Option, + + /// Settings for showing inline diagnostics. + pub inline: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct LspPullDiagnosticsSettingsContent { + /// Whether to pull for diagnostics or not. + /// + /// Default: true + pub enabled: Option, + /// Minimum time to wait before pulling diagnostics from the language server(s). + /// 0 turns the debounce off. + /// + /// Default: 50 + pub debounce_ms: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema, Eq)] +pub struct InlineDiagnosticsSettingsContent { + /// Whether or not to show inline diagnostics + /// + /// Default: false + pub enabled: Option, + /// Whether to only show the inline diagnostics after a delay after the + /// last editor event. + /// + /// Default: 150 + pub update_debounce_ms: Option, + /// The amount of padding between the end of the source line and the start + /// of the inline diagnostic in units of columns. + /// + /// Default: 4 + pub padding: Option, + /// The minimum column to display inline diagnostics. This setting can be + /// used to horizontally align inline diagnostics at some position. Lines + /// longer than this value will still push diagnostics further to the right. + /// + /// Default: 0 + pub min_column: Option, + + pub max_severity: Option, +} + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct NodeBinarySettings { + /// The path to the Node binary. + pub path: Option, + /// The path to the npm binary Zed should use (defaults to `.path/../npm`). + pub npm_path: Option, + /// If enabled, Zed will download its own copy of Node. + pub ignore_system_version: Option, +} + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DirenvSettings { + /// Load direnv configuration through a shell hook + ShellHook, + /// Load direnv configuration directly using `direnv export json` + #[default] + Direct, +} + +#[derive( + Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum DiagnosticSeverityContent { + // No diagnostics are shown. + Off, + Error, + Warning, + Info, + #[serde(alias = "all")] + Hint, +} + +/// A custom Git hosting provider. +#[skip_serializing_none] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +pub struct GitHostingProviderConfig { + /// The type of the provider. + /// + /// Must be one of `github`, `gitlab`, or `bitbucket`. + pub provider: GitHostingProviderKind, + + /// The base URL for the provider (e.g., "https://code.corp.big.com"). + pub base_url: String, + + /// The display name for the provider (e.g., "BigCorp GitHub"). + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitHostingProviderKind { + Github, + Gitlab, + Bitbucket, +} diff --git a/crates/settings/src/settings_content/terminal.rs b/crates/settings/src/settings_content/terminal.rs new file mode 100644 index 0000000000000000000000000000000000000000..e4d76a049e03438dc351b2e1bcf628cfa0e5f5e1 --- /dev/null +++ b/crates/settings/src/settings_content/terminal.rs @@ -0,0 +1,318 @@ +use std::path::PathBuf; + +use collections::HashMap; +use gpui::{AbsoluteLength, FontFeatures, SharedString, px}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +use crate::FontFamilyName; + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +pub struct TerminalSettingsContent { + /// What shell to use when opening a terminal. + /// + /// Default: system + pub shell: Option, + /// What working directory to use when launching the terminal + /// + /// Default: current_project_directory + pub working_directory: Option, + /// Sets the terminal's font size. + /// + /// If this option is not included, + /// the terminal will default to matching the buffer's font size. + pub font_size: Option, + /// Sets the terminal's font family. + /// + /// If this option is not included, + /// the terminal will default to matching the buffer's font family. + pub font_family: Option, + + /// Sets the terminal's font fallbacks. + /// + /// If this option is not included, + /// the terminal will default to matching the buffer's font fallbacks. + #[schemars(extend("uniqueItems" = true))] + pub font_fallbacks: Option>, + + /// Sets the terminal's line height. + /// + /// Default: comfortable + pub line_height: Option, + pub font_features: Option, + /// Sets the terminal's font weight in CSS weight units 0-900. + pub font_weight: Option, + /// Any key-value pairs added to this list will be added to the terminal's + /// environment. Use `:` to separate multiple values. + /// + /// Default: {} + pub env: Option>, + /// Default cursor shape for the terminal. + /// Can be "bar", "block", "underline", or "hollow". + /// + /// Default: None + pub cursor_shape: Option, + /// Sets the cursor blinking behavior in the terminal. + /// + /// Default: terminal_controlled + pub blinking: Option, + /// Sets whether Alternate Scroll mode (code: ?1007) is active by default. + /// Alternate Scroll mode converts mouse scroll events into up / down key + /// presses when in the alternate screen (e.g. when running applications + /// like vim or less). The terminal can still set and unset this mode. + /// + /// Default: on + pub alternate_scroll: Option, + /// Sets whether the option key behaves as the meta key. + /// + /// Default: false + pub option_as_meta: Option, + /// Whether or not selecting text in the terminal will automatically + /// copy to the system clipboard. + /// + /// Default: false + pub copy_on_select: Option, + /// Whether to keep the text selection after copying it to the clipboard. + /// + /// Default: false + pub keep_selection_on_copy: Option, + /// Whether to show the terminal button in the status bar. + /// + /// Default: true + pub button: Option, + pub dock: Option, + /// Default width when the terminal is docked to the left or right. + /// + /// Default: 640 + pub default_width: Option, + /// Default height when the terminal is docked to the bottom. + /// + /// Default: 320 + pub default_height: Option, + /// Activates the python virtual environment, if one is found, in the + /// terminal's working directory (as resolved by the working_directory + /// setting). Set this to "off" to disable this behavior. + /// + /// Default: on + pub detect_venv: Option, + /// The maximum number of lines to keep in the scrollback history. + /// Maximum allowed value is 100_000, all values above that will be treated as 100_000. + /// 0 disables the scrolling. + /// Existing terminals will not pick up this change until they are recreated. + /// See Alacritty documentation for more information. + /// + /// Default: 10_000 + pub max_scroll_history_lines: Option, + /// Toolbar related settings + pub toolbar: Option, + /// Scrollbar-related settings + pub scrollbar: Option, + /// The minimum APCA perceptual contrast between foreground and background colors. + /// + /// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x, + /// especially for dark mode. Values range from 0 to 106. + /// + /// Based on APCA Readability Criterion (ARC) Bronze Simple Mode: + /// https://readtech.org/ARC/tests/bronze-simple-mode/ + /// - 0: No contrast adjustment + /// - 45: Minimum for large fluent text (36px+) + /// - 60: Minimum for other content text + /// - 75: Minimum for body text + /// - 90: Preferred for body text + /// + /// Default: 45 + pub minimum_contrast: Option, +} + +/// Shell configuration to open the terminal with. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Shell { + /// Use the system's default terminal configuration in /etc/passwd + #[default] + System, + /// Use a specific program with no arguments. + Program(String), + /// Use a specific program with arguments. + WithArguments { + /// The program to run. + program: String, + /// The arguments to pass to the program. + args: Vec, + /// An optional string to override the title of the terminal tab + title_override: Option, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WorkingDirectory { + /// Use the current file's project directory. Will Fallback to the + /// first project directory strategy if unsuccessful. + CurrentProjectDirectory, + /// Use the first project in this workspace's directory. + FirstProjectDirectory, + /// Always use this platform's home directory (if it can be found). + AlwaysHome, + /// Always use a specific directory. This value will be shell expanded. + /// If this path is not a valid directory the terminal will default to + /// this platform's home directory (if it can be found). + Always { directory: String }, +} + +#[skip_serializing_none] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ScrollbarSettingsContent { + /// When to show the scrollbar in the terminal. + /// + /// Default: inherits editor scrollbar settings + pub show: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub enum TerminalLineHeight { + /// Use a line height that's comfortable for reading, 1.618 + #[default] + Comfortable, + /// Use a standard line height, 1.3. This option is useful for TUIs, + /// particularly if they use box characters + Standard, + /// Use a custom line height. + Custom(f32), +} + +impl TerminalLineHeight { + pub fn value(&self) -> AbsoluteLength { + let value = match self { + TerminalLineHeight::Comfortable => 1.618, + TerminalLineHeight::Standard => 1.3, + TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.), + }; + px(value).into() + } +} + +/// When to show the scrollbar. +/// +/// Default: auto +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ShowScrollbar { + /// Show the scrollbar if there's important information or + /// follow the system's configured behavior. + Auto, + /// Match the system's configured behavior. + System, + /// Always show the scrollbar. + Always, + /// Never show the scrollbar. + Never, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CursorShapeContent { + /// Cursor is a block like `█`. + #[default] + Block, + /// Cursor is an underscore like `_`. + Underline, + /// Cursor is a vertical bar like `⎸`. + Bar, + /// Cursor is a hollow box like `▯`. + Hollow, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TerminalBlink { + /// Never blink the cursor, ignoring the terminal mode. + Off, + /// Default the cursor blink to off, but allow the terminal to + /// set blinking. + TerminalControlled, + /// Always blink the cursor, ignoring the terminal mode. + On, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AlternateScroll { + On, + Off, +} + +// Toolbar related settings +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct TerminalToolbarContent { + /// Whether to display the terminal title in breadcrumbs inside the terminal pane. + /// Only shown if the terminal title is not empty. + /// + /// The shell running in the terminal needs to be configured to emit the title. + /// Example: `echo -e "\e]2;New Title\007";` + /// + /// Default: true + pub breadcrumbs: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum VenvSettings { + #[default] + Off, + On { + /// Default directories to search for virtual environments, relative + /// to the current working directory. We recommend overriding this + /// in your project's settings, rather than globally. + activate_script: Option, + venv_name: Option, + directories: Option>, + }, +} +#[skip_serializing_none] +pub struct VenvSettingsContent<'a> { + pub activate_script: ActivateScript, + pub venv_name: &'a str, + pub directories: &'a [PathBuf], +} + +impl VenvSettings { + pub fn as_option(&self) -> Option> { + match self { + VenvSettings::Off => None, + VenvSettings::On { + activate_script, + venv_name, + directories, + } => Some(VenvSettingsContent { + activate_script: activate_script.unwrap_or(ActivateScript::Default), + venv_name: venv_name.as_deref().unwrap_or(""), + directories: directories.as_deref().unwrap_or(&[]), + }), + } + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum TerminalDockPosition { + Left, + Bottom, + Right, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ActivateScript { + #[default] + Default, + Csh, + Fish, + Nushell, + PowerShell, + Pyenv, +} diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs new file mode 100644 index 0000000000000000000000000000000000000000..886448d696fa975749a1fcefe615694a903f8d29 --- /dev/null +++ b/crates/settings/src/settings_content/theme.rs @@ -0,0 +1,1076 @@ +use collections::{HashMap, IndexMap}; +use gpui::{FontFallbacks, FontFeatures, FontStyle, FontWeight}; +use schemars::{JsonSchema, JsonSchema_repr}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::sync::Arc; + +use serde_with::skip_serializing_none; + +/// Settings for rendering text in UI and text buffers. + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ThemeSettingsContent { + /// The default font size for text in the UI. + #[serde(default)] + pub ui_font_size: Option, + /// The name of a font to use for rendering in the UI. + #[serde(default)] + pub ui_font_family: Option, + /// The font fallbacks to use for rendering in the UI. + #[serde(default)] + #[schemars(default = "default_font_fallbacks")] + #[schemars(extend("uniqueItems" = true))] + pub ui_font_fallbacks: Option>, + /// The OpenType features to enable for text in the UI. + #[serde(default)] + #[schemars(default = "default_font_features")] + pub ui_font_features: Option, + /// The weight of the UI font in CSS units from 100 to 900. + #[serde(default)] + pub ui_font_weight: Option, + /// The name of a font to use for rendering in text buffers. + #[serde(default)] + pub buffer_font_family: Option, + /// The font fallbacks to use for rendering in text buffers. + #[serde(default)] + #[schemars(extend("uniqueItems" = true))] + pub buffer_font_fallbacks: Option>, + /// The default font size for rendering in text buffers. + #[serde(default)] + pub buffer_font_size: Option, + /// The weight of the editor font in CSS units from 100 to 900. + #[serde(default)] + pub buffer_font_weight: Option, + /// The buffer's line height. + #[serde(default)] + pub buffer_line_height: Option, + /// The OpenType features to enable for rendering in text buffers. + #[serde(default)] + #[schemars(default = "default_font_features")] + pub buffer_font_features: Option, + /// The font size for the agent panel. Falls back to the UI font size if unset. + #[serde(default)] + pub agent_font_size: Option>, + /// The name of the Zed theme to use. + #[serde(default)] + pub theme: Option, + /// The name of the icon theme to use. + #[serde(default)] + pub icon_theme: Option, + + /// UNSTABLE: Expect many elements to be broken. + /// + // Controls the density of the UI. + #[serde(rename = "unstable.ui_density", default)] + pub ui_density: Option, + + /// How much to fade out unused code. + #[serde(default)] + pub unnecessary_code_fade: Option, + + /// EXPERIMENTAL: Overrides for the current theme. + /// + /// These values will override the ones on the current theme specified in `theme`. + #[serde(rename = "experimental.theme_overrides", default)] + pub experimental_theme_overrides: Option, + + /// Overrides per theme + /// + /// These values will override the ones on the specified theme + #[serde(default)] + pub theme_overrides: HashMap, +} + +fn default_font_features() -> Option { + Some(FontFeatures::default()) +} + +fn default_font_fallbacks() -> Option { + Some(FontFallbacks::default()) +} + +/// Represents the selection of a theme, which can be either static or dynamic. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(untagged)] +pub enum ThemeSelection { + /// A static theme selection, represented by a single theme name. + Static(ThemeName), + /// A dynamic theme selection, which can change based the [ThemeMode]. + Dynamic { + /// The mode used to determine which theme to use. + #[serde(default)] + mode: ThemeMode, + /// The theme to use for light mode. + light: ThemeName, + /// The theme to use for dark mode. + dark: ThemeName, + }, +} + +/// Represents the selection of an icon theme, which can be either static or dynamic. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(untagged)] +pub enum IconThemeSelection { + /// A static icon theme selection, represented by a single icon theme name. + Static(IconThemeName), + /// A dynamic icon theme selection, which can change based on the [`ThemeMode`]. + Dynamic { + /// The mode used to determine which theme to use. + #[serde(default)] + mode: ThemeMode, + /// The icon theme to use for light mode. + light: IconThemeName, + /// The icon theme to use for dark mode. + dark: IconThemeName, + }, +} + +// TODO: Rename ThemeMode -> ThemeAppearanceMode +/// The mode use to select a theme. +/// +/// `Light` and `Dark` will select their respective themes. +/// +/// `System` will select the theme based on the system's appearance. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ThemeMode { + /// Use the specified `light` theme. + Light, + + /// Use the specified `dark` theme. + Dark, + + /// Use the theme based on the system's appearance. + #[default] + System, +} + +/// Specifies the density of the UI. +/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078) +#[derive( + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Serialize, + Deserialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum UiDensity { + /// A denser UI with tighter spacing and smaller elements. + #[serde(alias = "compact")] + Compact, + #[default] + #[serde(alias = "default")] + /// The default UI density. + Default, + #[serde(alias = "comfortable")] + /// A looser UI with more spacing and larger elements. + Comfortable, +} + +impl UiDensity { + /// The spacing ratio of a given density. + /// TODO: Standardize usage throughout the app or remove + pub fn spacing_ratio(self) -> f32 { + match self { + UiDensity::Compact => 0.75, + UiDensity::Default => 1.0, + UiDensity::Comfortable => 1.25, + } + } +} + +/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at +/// runtime. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(transparent)] +pub struct FontFamilyName(pub Arc); + +/// The buffer's line height. +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub enum BufferLineHeight { + /// A less dense line height. + #[default] + Comfortable, + /// The default line height. + Standard, + /// A custom line height, where 1.0 is the font's height. Must be at least 1.0. + Custom(#[serde(deserialize_with = "deserialize_line_height")] f32), +} + +fn deserialize_line_height<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let value = f32::deserialize(deserializer)?; + if value < 1.0 { + return Err(serde::de::Error::custom( + "buffer_line_height.custom must be at least 1.0", + )); + } + + Ok(value) +} + +/// The content of a serialized theme. +#[skip_serializing_none] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(default)] +pub struct ThemeStyleContent { + #[serde(default, rename = "background.appearance")] + pub window_background_appearance: Option, + + #[serde(default)] + pub accents: Vec, + + #[serde(flatten, default)] + pub colors: ThemeColorsContent, + + #[serde(flatten, default)] + pub status: StatusColorsContent, + + #[serde(default)] + pub players: Vec, + + /// The styles for syntax nodes. + #[serde(default)] + pub syntax: IndexMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct AccentContent(pub Option); + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct PlayerColorContent { + pub cursor: Option, + pub background: Option, + pub selection: Option, +} + +/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(transparent)] +pub struct ThemeName(pub Arc); + +/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at +/// runtime. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(transparent)] +pub struct IconThemeName(pub Arc); + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(default)] +pub struct ThemeColorsContent { + /// Border color. Used for most borders, is usually a high contrast color. + #[serde(rename = "border")] + pub border: Option, + + /// Border color. Used for deemphasized borders, like a visual divider between two sections + #[serde(rename = "border.variant")] + pub border_variant: Option, + + /// Border color. Used for focused elements, like keyboard focused list item. + #[serde(rename = "border.focused")] + pub border_focused: Option, + + /// Border color. Used for selected elements, like an active search filter or selected checkbox. + #[serde(rename = "border.selected")] + pub border_selected: Option, + + /// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change. + #[serde(rename = "border.transparent")] + pub border_transparent: Option, + + /// Border color. Used for disabled elements, like a disabled input or button. + #[serde(rename = "border.disabled")] + pub border_disabled: Option, + + /// Background color. Used for elevated surfaces, like a context menu, popup, or dialog. + #[serde(rename = "elevated_surface.background")] + pub elevated_surface_background: Option, + + /// Background Color. Used for grounded surfaces like a panel or tab. + #[serde(rename = "surface.background")] + pub surface_background: Option, + + /// Background Color. Used for the app background and blank panels or windows. + #[serde(rename = "background")] + pub background: Option, + + /// Background Color. Used for the background of an element that should have a different background than the surface it's on. + /// + /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... + /// + /// For an element that should have the same background as the surface it's on, use `ghost_element_background`. + #[serde(rename = "element.background")] + pub element_background: Option, + + /// Background Color. Used for the hover state of an element that should have a different background than the surface it's on. + /// + /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. + #[serde(rename = "element.hover")] + pub element_hover: Option, + + /// Background Color. Used for the active state of an element that should have a different background than the surface it's on. + /// + /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed. + #[serde(rename = "element.active")] + pub element_active: Option, + + /// Background Color. Used for the selected state of an element that should have a different background than the surface it's on. + /// + /// Selected states are triggered by the element being selected (or "activated") by the user. + /// + /// This could include a selected checkbox, a toggleable button that is toggled on, etc. + #[serde(rename = "element.selected")] + pub element_selected: Option, + + /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on. + /// + /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. + #[serde(rename = "element.disabled")] + pub element_disabled: Option, + + /// Background Color. Used for the background of selections in a UI element. + #[serde(rename = "element.selection_background")] + pub element_selection_background: Option, + + /// Background Color. Used for the area that shows where a dragged element will be dropped. + #[serde(rename = "drop_target.background")] + pub drop_target_background: Option, + + /// Border Color. Used for the border that shows where a dragged element will be dropped. + #[serde(rename = "drop_target.border")] + pub drop_target_border: Option, + + /// Used for the background of a ghost element that should have the same background as the surface it's on. + /// + /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... + /// + /// For an element that should have a different background than the surface it's on, use `element_background`. + #[serde(rename = "ghost_element.background")] + pub ghost_element_background: Option, + + /// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on. + /// + /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. + #[serde(rename = "ghost_element.hover")] + pub ghost_element_hover: Option, + + /// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on. + /// + /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed. + #[serde(rename = "ghost_element.active")] + pub ghost_element_active: Option, + + /// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on. + /// + /// Selected states are triggered by the element being selected (or "activated") by the user. + /// + /// This could include a selected checkbox, a toggleable button that is toggled on, etc. + #[serde(rename = "ghost_element.selected")] + pub ghost_element_selected: Option, + + /// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on. + /// + /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. + #[serde(rename = "ghost_element.disabled")] + pub ghost_element_disabled: Option, + + /// Text Color. Default text color used for most text. + #[serde(rename = "text")] + pub text: Option, + + /// Text Color. Color of muted or deemphasized text. It is a subdued version of the standard text color. + #[serde(rename = "text.muted")] + pub text_muted: Option, + + /// Text Color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data. + #[serde(rename = "text.placeholder")] + pub text_placeholder: Option, + + /// Text Color. Color used for text denoting disabled elements. Typically, the color is faded or grayed out to emphasize the disabled state. + #[serde(rename = "text.disabled")] + pub text_disabled: Option, + + /// Text Color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search. + #[serde(rename = "text.accent")] + pub text_accent: Option, + + /// Fill Color. Used for the default fill color of an icon. + #[serde(rename = "icon")] + pub icon: Option, + + /// Fill Color. Used for the muted or deemphasized fill color of an icon. + /// + /// This might be used to show an icon in an inactive pane, or to deemphasize a series of icons to give them less visual weight. + #[serde(rename = "icon.muted")] + pub icon_muted: Option, + + /// Fill Color. Used for the disabled fill color of an icon. + /// + /// Disabled states are shown when a user cannot interact with an element, like a icon button. + #[serde(rename = "icon.disabled")] + pub icon_disabled: Option, + + /// Fill Color. Used for the placeholder fill color of an icon. + /// + /// This might be used to show an icon in an input that disappears when the user enters text. + #[serde(rename = "icon.placeholder")] + pub icon_placeholder: Option, + + /// Fill Color. Used for the accent fill color of an icon. + /// + /// This might be used to show when a toggleable icon button is selected. + #[serde(rename = "icon.accent")] + pub icon_accent: Option, + + /// Color used to accent some of the debuggers elements + /// Only accent breakpoint & breakpoint related symbols right now + #[serde(rename = "debugger.accent")] + pub debugger_accent: Option, + + #[serde(rename = "status_bar.background")] + pub status_bar_background: Option, + + #[serde(rename = "title_bar.background")] + pub title_bar_background: Option, + + #[serde(rename = "title_bar.inactive_background")] + pub title_bar_inactive_background: Option, + + #[serde(rename = "toolbar.background")] + pub toolbar_background: Option, + + #[serde(rename = "tab_bar.background")] + pub tab_bar_background: Option, + + #[serde(rename = "tab.inactive_background")] + pub tab_inactive_background: Option, + + #[serde(rename = "tab.active_background")] + pub tab_active_background: Option, + + #[serde(rename = "search.match_background")] + pub search_match_background: Option, + + #[serde(rename = "panel.background")] + pub panel_background: Option, + + #[serde(rename = "panel.focused_border")] + pub panel_focused_border: Option, + + #[serde(rename = "panel.indent_guide")] + pub panel_indent_guide: Option, + + #[serde(rename = "panel.indent_guide_hover")] + pub panel_indent_guide_hover: Option, + + #[serde(rename = "panel.indent_guide_active")] + pub panel_indent_guide_active: Option, + + #[serde(rename = "panel.overlay_background")] + pub panel_overlay_background: Option, + + #[serde(rename = "panel.overlay_hover")] + pub panel_overlay_hover: Option, + + #[serde(rename = "pane.focused_border")] + pub pane_focused_border: Option, + + #[serde(rename = "pane_group.border")] + pub pane_group_border: Option, + + /// The deprecated version of `scrollbar.thumb.background`. + /// + /// Don't use this field. + #[serde(rename = "scrollbar_thumb.background", skip_serializing)] + #[schemars(skip)] + pub deprecated_scrollbar_thumb_background: Option, + + /// The color of the scrollbar thumb. + #[serde(rename = "scrollbar.thumb.background")] + pub scrollbar_thumb_background: Option, + + /// The color of the scrollbar thumb when hovered over. + #[serde(rename = "scrollbar.thumb.hover_background")] + pub scrollbar_thumb_hover_background: Option, + + /// The color of the scrollbar thumb whilst being actively dragged. + #[serde(rename = "scrollbar.thumb.active_background")] + pub scrollbar_thumb_active_background: Option, + + /// The border color of the scrollbar thumb. + #[serde(rename = "scrollbar.thumb.border")] + pub scrollbar_thumb_border: Option, + + /// The background color of the scrollbar track. + #[serde(rename = "scrollbar.track.background")] + pub scrollbar_track_background: Option, + + /// The border color of the scrollbar track. + #[serde(rename = "scrollbar.track.border")] + pub scrollbar_track_border: Option, + + /// The color of the minimap thumb. + #[serde(rename = "minimap.thumb.background")] + pub minimap_thumb_background: Option, + + /// The color of the minimap thumb when hovered over. + #[serde(rename = "minimap.thumb.hover_background")] + pub minimap_thumb_hover_background: Option, + + /// The color of the minimap thumb whilst being actively dragged. + #[serde(rename = "minimap.thumb.active_background")] + pub minimap_thumb_active_background: Option, + + /// The border color of the minimap thumb. + #[serde(rename = "minimap.thumb.border")] + pub minimap_thumb_border: Option, + + #[serde(rename = "editor.foreground")] + pub editor_foreground: Option, + + #[serde(rename = "editor.background")] + pub editor_background: Option, + + #[serde(rename = "editor.gutter.background")] + pub editor_gutter_background: Option, + + #[serde(rename = "editor.subheader.background")] + pub editor_subheader_background: Option, + + #[serde(rename = "editor.active_line.background")] + pub editor_active_line_background: Option, + + #[serde(rename = "editor.highlighted_line.background")] + pub editor_highlighted_line_background: Option, + + /// Background of active line of debugger + #[serde(rename = "editor.debugger_active_line.background")] + pub editor_debugger_active_line_background: Option, + + /// Text Color. Used for the text of the line number in the editor gutter. + #[serde(rename = "editor.line_number")] + pub editor_line_number: Option, + + /// Text Color. Used for the text of the line number in the editor gutter when the line is highlighted. + #[serde(rename = "editor.active_line_number")] + pub editor_active_line_number: Option, + + /// Text Color. Used for the text of the line number in the editor gutter when the line is hovered over. + #[serde(rename = "editor.hover_line_number")] + pub editor_hover_line_number: Option, + + /// Text Color. Used to mark invisible characters in the editor. + /// + /// Example: spaces, tabs, carriage returns, etc. + #[serde(rename = "editor.invisible")] + pub editor_invisible: Option, + + #[serde(rename = "editor.wrap_guide")] + pub editor_wrap_guide: Option, + + #[serde(rename = "editor.active_wrap_guide")] + pub editor_active_wrap_guide: Option, + + #[serde(rename = "editor.indent_guide")] + pub editor_indent_guide: Option, + + #[serde(rename = "editor.indent_guide_active")] + pub editor_indent_guide_active: Option, + + /// Read-access of a symbol, like reading a variable. + /// + /// A document highlight is a range inside a text document which deserves + /// special attention. Usually a document highlight is visualized by changing + /// the background color of its range. + #[serde(rename = "editor.document_highlight.read_background")] + pub editor_document_highlight_read_background: Option, + + /// Read-access of a symbol, like reading a variable. + /// + /// A document highlight is a range inside a text document which deserves + /// special attention. Usually a document highlight is visualized by changing + /// the background color of its range. + #[serde(rename = "editor.document_highlight.write_background")] + pub editor_document_highlight_write_background: Option, + + /// Highlighted brackets background color. + /// + /// Matching brackets in the cursor scope are highlighted with this background color. + #[serde(rename = "editor.document_highlight.bracket_background")] + pub editor_document_highlight_bracket_background: Option, + + /// Terminal background color. + #[serde(rename = "terminal.background")] + pub terminal_background: Option, + + /// Terminal foreground color. + #[serde(rename = "terminal.foreground")] + pub terminal_foreground: Option, + + /// Terminal ANSI background color. + #[serde(rename = "terminal.ansi.background")] + pub terminal_ansi_background: Option, + + /// Bright terminal foreground color. + #[serde(rename = "terminal.bright_foreground")] + pub terminal_bright_foreground: Option, + + /// Dim terminal foreground color. + #[serde(rename = "terminal.dim_foreground")] + pub terminal_dim_foreground: Option, + + /// Black ANSI terminal color. + #[serde(rename = "terminal.ansi.black")] + pub terminal_ansi_black: Option, + + /// Bright black ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_black")] + pub terminal_ansi_bright_black: Option, + + /// Dim black ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_black")] + pub terminal_ansi_dim_black: Option, + + /// Red ANSI terminal color. + #[serde(rename = "terminal.ansi.red")] + pub terminal_ansi_red: Option, + + /// Bright red ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_red")] + pub terminal_ansi_bright_red: Option, + + /// Dim red ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_red")] + pub terminal_ansi_dim_red: Option, + + /// Green ANSI terminal color. + #[serde(rename = "terminal.ansi.green")] + pub terminal_ansi_green: Option, + + /// Bright green ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_green")] + pub terminal_ansi_bright_green: Option, + + /// Dim green ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_green")] + pub terminal_ansi_dim_green: Option, + + /// Yellow ANSI terminal color. + #[serde(rename = "terminal.ansi.yellow")] + pub terminal_ansi_yellow: Option, + + /// Bright yellow ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_yellow")] + pub terminal_ansi_bright_yellow: Option, + + /// Dim yellow ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_yellow")] + pub terminal_ansi_dim_yellow: Option, + + /// Blue ANSI terminal color. + #[serde(rename = "terminal.ansi.blue")] + pub terminal_ansi_blue: Option, + + /// Bright blue ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_blue")] + pub terminal_ansi_bright_blue: Option, + + /// Dim blue ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_blue")] + pub terminal_ansi_dim_blue: Option, + + /// Magenta ANSI terminal color. + #[serde(rename = "terminal.ansi.magenta")] + pub terminal_ansi_magenta: Option, + + /// Bright magenta ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_magenta")] + pub terminal_ansi_bright_magenta: Option, + + /// Dim magenta ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_magenta")] + pub terminal_ansi_dim_magenta: Option, + + /// Cyan ANSI terminal color. + #[serde(rename = "terminal.ansi.cyan")] + pub terminal_ansi_cyan: Option, + + /// Bright cyan ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_cyan")] + pub terminal_ansi_bright_cyan: Option, + + /// Dim cyan ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_cyan")] + pub terminal_ansi_dim_cyan: Option, + + /// White ANSI terminal color. + #[serde(rename = "terminal.ansi.white")] + pub terminal_ansi_white: Option, + + /// Bright white ANSI terminal color. + #[serde(rename = "terminal.ansi.bright_white")] + pub terminal_ansi_bright_white: Option, + + /// Dim white ANSI terminal color. + #[serde(rename = "terminal.ansi.dim_white")] + pub terminal_ansi_dim_white: Option, + + #[serde(rename = "link_text.hover")] + pub link_text_hover: Option, + + /// Added version control color. + #[serde(rename = "version_control.added")] + pub version_control_added: Option, + + /// Deleted version control color. + #[serde(rename = "version_control.deleted")] + pub version_control_deleted: Option, + + /// Modified version control color. + #[serde(rename = "version_control.modified")] + pub version_control_modified: Option, + + /// Renamed version control color. + #[serde(rename = "version_control.renamed")] + pub version_control_renamed: Option, + + /// Conflict version control color. + #[serde(rename = "version_control.conflict")] + pub version_control_conflict: Option, + + /// Ignored version control color. + #[serde(rename = "version_control.ignored")] + pub version_control_ignored: Option, + + /// Background color for row highlights of "ours" regions in merge conflicts. + #[serde(rename = "version_control.conflict_marker.ours")] + pub version_control_conflict_marker_ours: Option, + + /// Background color for row highlights of "theirs" regions in merge conflicts. + #[serde(rename = "version_control.conflict_marker.theirs")] + pub version_control_conflict_marker_theirs: Option, + + /// Deprecated in favor of `version_control_conflict_marker_ours`. + #[deprecated] + pub version_control_conflict_ours_background: Option, + + /// Deprecated in favor of `version_control_conflict_marker_theirs`. + #[deprecated] + pub version_control_conflict_theirs_background: Option, +} + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(default)] +pub struct HighlightStyleContent { + pub color: Option, + + #[serde(deserialize_with = "treat_error_as_none")] + pub background_color: Option, + + #[serde(deserialize_with = "treat_error_as_none")] + pub font_style: Option, + + #[serde(deserialize_with = "treat_error_as_none")] + pub font_weight: Option, +} + +impl HighlightStyleContent { + pub fn is_empty(&self) -> bool { + self.color.is_none() + && self.background_color.is_none() + && self.font_style.is_none() + && self.font_weight.is_none() + } +} + +fn treat_error_as_none<'de, T, D>(deserializer: D) -> Result, D::Error> +where + T: Deserialize<'de>, + D: Deserializer<'de>, +{ + let value: Value = Deserialize::deserialize(deserializer)?; + Ok(T::deserialize(value).ok()) +} + +#[skip_serializing_none] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(default)] +pub struct StatusColorsContent { + /// Indicates some kind of conflict, like a file changed on disk while it was open, or + /// merge conflicts in a Git repository. + #[serde(rename = "conflict")] + pub conflict: Option, + + #[serde(rename = "conflict.background")] + pub conflict_background: Option, + + #[serde(rename = "conflict.border")] + pub conflict_border: Option, + + /// Indicates something new, like a new file added to a Git repository. + #[serde(rename = "created")] + pub created: Option, + + #[serde(rename = "created.background")] + pub created_background: Option, + + #[serde(rename = "created.border")] + pub created_border: Option, + + /// Indicates that something no longer exists, like a deleted file. + #[serde(rename = "deleted")] + pub deleted: Option, + + #[serde(rename = "deleted.background")] + pub deleted_background: Option, + + #[serde(rename = "deleted.border")] + pub deleted_border: Option, + + /// Indicates a system error, a failed operation or a diagnostic error. + #[serde(rename = "error")] + pub error: Option, + + #[serde(rename = "error.background")] + pub error_background: Option, + + #[serde(rename = "error.border")] + pub error_border: Option, + + /// Represents a hidden status, such as a file being hidden in a file tree. + #[serde(rename = "hidden")] + pub hidden: Option, + + #[serde(rename = "hidden.background")] + pub hidden_background: Option, + + #[serde(rename = "hidden.border")] + pub hidden_border: Option, + + /// Indicates a hint or some kind of additional information. + #[serde(rename = "hint")] + pub hint: Option, + + #[serde(rename = "hint.background")] + pub hint_background: Option, + + #[serde(rename = "hint.border")] + pub hint_border: Option, + + /// Indicates that something is deliberately ignored, such as a file or operation ignored by Git. + #[serde(rename = "ignored")] + pub ignored: Option, + + #[serde(rename = "ignored.background")] + pub ignored_background: Option, + + #[serde(rename = "ignored.border")] + pub ignored_border: Option, + + /// Represents informational status updates or messages. + #[serde(rename = "info")] + pub info: Option, + + #[serde(rename = "info.background")] + pub info_background: Option, + + #[serde(rename = "info.border")] + pub info_border: Option, + + /// Indicates a changed or altered status, like a file that has been edited. + #[serde(rename = "modified")] + pub modified: Option, + + #[serde(rename = "modified.background")] + pub modified_background: Option, + + #[serde(rename = "modified.border")] + pub modified_border: Option, + + /// Indicates something that is predicted, like automatic code completion, or generated code. + #[serde(rename = "predictive")] + pub predictive: Option, + + #[serde(rename = "predictive.background")] + pub predictive_background: Option, + + #[serde(rename = "predictive.border")] + pub predictive_border: Option, + + /// Represents a renamed status, such as a file that has been renamed. + #[serde(rename = "renamed")] + pub renamed: Option, + + #[serde(rename = "renamed.background")] + pub renamed_background: Option, + + #[serde(rename = "renamed.border")] + pub renamed_border: Option, + + /// Indicates a successful operation or task completion. + #[serde(rename = "success")] + pub success: Option, + + #[serde(rename = "success.background")] + pub success_background: Option, + + #[serde(rename = "success.border")] + pub success_border: Option, + + /// Indicates some kind of unreachable status, like a block of code that can never be reached. + #[serde(rename = "unreachable")] + pub unreachable: Option, + + #[serde(rename = "unreachable.background")] + pub unreachable_background: Option, + + #[serde(rename = "unreachable.border")] + pub unreachable_border: Option, + + /// Represents a warning status, like an operation that is about to fail. + #[serde(rename = "warning")] + pub warning: Option, + + #[serde(rename = "warning.background")] + pub warning_background: Option, + + #[serde(rename = "warning.border")] + pub warning_border: Option, +} + +/// The background appearance of the window. +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WindowBackgroundContent { + Opaque, + Transparent, + Blurred, +} + +impl Into for WindowBackgroundContent { + fn into(self) -> gpui::WindowBackgroundAppearance { + match self { + WindowBackgroundContent::Opaque => gpui::WindowBackgroundAppearance::Opaque, + WindowBackgroundContent::Transparent => gpui::WindowBackgroundAppearance::Transparent, + WindowBackgroundContent::Blurred => gpui::WindowBackgroundAppearance::Blurred, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum FontStyleContent { + Normal, + Italic, + Oblique, +} + +impl From for FontStyle { + fn from(value: FontStyleContent) -> Self { + match value { + FontStyleContent::Normal => FontStyle::Normal, + FontStyleContent::Italic => FontStyle::Italic, + FontStyleContent::Oblique => FontStyle::Oblique, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, JsonSchema_repr, PartialEq)] +#[repr(u16)] +pub enum FontWeightContent { + Thin = 100, + ExtraLight = 200, + Light = 300, + Normal = 400, + Medium = 500, + Semibold = 600, + Bold = 700, + ExtraBold = 800, + Black = 900, +} + +impl From for FontWeight { + fn from(value: FontWeightContent) -> Self { + match value { + FontWeightContent::Thin => FontWeight::THIN, + FontWeightContent::ExtraLight => FontWeight::EXTRA_LIGHT, + FontWeightContent::Light => FontWeight::LIGHT, + FontWeightContent::Normal => FontWeight::NORMAL, + FontWeightContent::Medium => FontWeight::MEDIUM, + FontWeightContent::Semibold => FontWeight::SEMIBOLD, + FontWeightContent::Bold => FontWeight::BOLD, + FontWeightContent::ExtraBold => FontWeight::EXTRA_BOLD, + FontWeightContent::Black => FontWeight::BLACK, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_buffer_line_height_deserialize_valid() { + assert_eq!( + serde_json::from_value::(json!("comfortable")).unwrap(), + BufferLineHeight::Comfortable + ); + assert_eq!( + serde_json::from_value::(json!("standard")).unwrap(), + BufferLineHeight::Standard + ); + assert_eq!( + serde_json::from_value::(json!({"custom": 1.0})).unwrap(), + BufferLineHeight::Custom(1.0) + ); + assert_eq!( + serde_json::from_value::(json!({"custom": 1.5})).unwrap(), + BufferLineHeight::Custom(1.5) + ); + } + + #[test] + fn test_buffer_line_height_deserialize_invalid() { + assert!( + serde_json::from_value::(json!({"custom": 0.99})) + .err() + .unwrap() + .to_string() + .contains("buffer_line_height.custom must be at least 1.0") + ); + assert!( + serde_json::from_value::(json!({"custom": 0.0})) + .err() + .unwrap() + .to_string() + .contains("buffer_line_height.custom must be at least 1.0") + ); + assert!( + serde_json::from_value::(json!({"custom": -1.0})) + .err() + .unwrap() + .to_string() + .contains("buffer_line_height.custom must be at least 1.0") + ); + } +} diff --git a/crates/settings/src/settings_content/workspace.rs b/crates/settings/src/settings_content/workspace.rs new file mode 100644 index 0000000000000000000000000000000000000000..aaa5817336058fad2e4300e70143e2100c9b72c7 --- /dev/null +++ b/crates/settings/src/settings_content/workspace.rs @@ -0,0 +1,440 @@ +use std::num::NonZeroUsize; + +use collections::HashMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +use crate::{DockPosition, DockSide, ScrollbarSettingsContent, ShowIndentGuides}; + +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +pub struct WorkspaceSettingsContent { + /// Active pane styling settings. + pub active_pane_modifiers: Option, + /// Layout mode for the bottom dock + /// + /// Default: contained + pub bottom_dock_layout: Option, + /// Direction to split horizontally. + /// + /// Default: "up" + pub pane_split_direction_horizontal: Option, + /// Direction to split vertically. + /// + /// Default: "left" + pub pane_split_direction_vertical: Option, + /// Centered layout related settings. + pub centered_layout: Option, + /// Whether or not to prompt the user to confirm before closing the application. + /// + /// Default: false + pub confirm_quit: Option, + /// Whether or not to show the call status icon in the status bar. + /// + /// Default: true + pub show_call_status_icon: Option, + /// When to automatically save edited buffers. + /// + /// Default: off + pub autosave: Option, + /// Controls previous session restoration in freshly launched Zed instance. + /// Values: none, last_workspace, last_session + /// Default: last_session + pub restore_on_startup: Option, + /// Whether to attempt to restore previous file's state when opening it again. + /// The state is stored per pane. + /// When disabled, defaults are applied instead of the state restoration. + /// + /// E.g. for editors, selections, folds and scroll positions are restored, if the same file is closed and, later, opened again in the same pane. + /// When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default. + /// + /// Default: true + pub restore_on_file_reopen: Option, + /// The size of the workspace split drop targets on the outer edges. + /// Given as a fraction that will be multiplied by the smaller dimension of the workspace. + /// + /// Default: `0.2` (20% of the smaller dimension of the workspace) + pub drop_target_size: Option, + /// Whether to close the window when using 'close active item' on a workspace with no tabs + /// + /// Default: auto ("on" on macOS, "off" otherwise) + pub when_closing_with_no_tabs: Option, + /// Whether to use the system provided dialogs for Open and Save As. + /// When set to false, Zed will use the built-in keyboard-first pickers. + /// + /// Default: true + pub use_system_path_prompts: Option, + /// Whether to use the system provided prompts. + /// When set to false, Zed will use the built-in prompts. + /// Note that this setting has no effect on Linux, where Zed will always + /// use the built-in prompts. + /// + /// Default: true + pub use_system_prompts: Option, + /// Aliases for the command palette. When you type a key in this map, + /// it will be assumed to equal the value. + /// + /// Default: true + #[serde(default)] + pub command_aliases: HashMap, + /// Maximum open tabs in a pane. Will not close an unsaved + /// tab. Set to `None` for unlimited tabs. + /// + /// Default: none + pub max_tabs: Option, + /// What to do when the last window is closed + /// + /// Default: auto (nothing on macOS, "app quit" otherwise) + pub on_last_window_closed: Option, + /// Whether to resize all the panels in a dock when resizing the dock. + /// + /// Default: ["left"] + pub resize_all_panels_in_dock: Option>, + /// Whether to automatically close files that have been deleted on disk. + /// + /// Default: false + pub close_on_file_delete: Option, + /// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only). + /// + /// Default: false + pub use_system_window_tabs: Option, + /// Whether to show padding for zoomed panels. + /// When enabled, zoomed bottom panels will have some top padding, + /// while zoomed left/right panels will have padding to the right/left (respectively). + /// + /// Default: true + pub zoomed_padding: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct ItemSettingsContent { + /// Whether to show the Git file status on a tab item. + /// + /// Default: false + pub git_status: Option, + /// Position of the close button in a tab. + /// + /// Default: right + pub close_position: Option, + /// Whether to show the file icon for a tab. + /// + /// Default: false + pub file_icons: Option, + /// What to do after closing the current tab. + /// + /// Default: history + pub activate_on_close: Option, + /// Which files containing diagnostic errors/warnings to mark in the tabs. + /// This setting can take the following three values: + /// + /// Default: off + pub show_diagnostics: Option, + /// Whether to always show the close button on tabs. + /// + /// Default: false + pub show_close_button: Option, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct PreviewTabsSettingsContent { + /// Whether to show opened editors as preview tabs. + /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic. + /// + /// Default: true + pub enabled: Option, + /// Whether to open tabs in preview mode when selected from the file finder. + /// + /// Default: false + pub enable_preview_from_file_finder: Option, + /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab. + /// + /// Default: false + pub enable_preview_from_code_navigation: Option, +} + +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ClosePosition { + Left, + #[default] + Right, +} + +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ShowCloseButton { + Always, + #[default] + Hover, + Hidden, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ShowDiagnostics { + #[default] + Off, + Errors, + All, +} + +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ActivateOnClose { + #[default] + History, + Neighbour, + LeftNeighbour, +} + +#[skip_serializing_none] +#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ActivePanelModifiers { + /// Size of the border surrounding the active pane. + /// When set to 0, the active pane doesn't have any border. + /// The border is drawn inset. + /// + /// Default: `0.0` + pub border_size: Option, + /// Opacity of inactive panels. + /// When set to 1.0, the inactive panes have the same opacity as the active one. + /// If set to 0, the inactive panes content will not be visible at all. + /// Values are clamped to the [0.0, 1.0] range. + /// + /// Default: `1.0` + pub inactive_opacity: Option, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum BottomDockLayout { + /// Contained between the left and right docks + #[default] + Contained, + /// Takes up the full width of the window + Full, + /// Extends under the left dock while snapping to the right dock + LeftAligned, + /// Extends under the right dock while snapping to the left dock + RightAligned, +} + +#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum CloseWindowWhenNoItems { + /// Match platform conventions by default, so "on" on macOS and "off" everywhere else + #[default] + PlatformDefault, + /// Close the window when there are no tabs + CloseWindow, + /// Leave the window open when there are no tabs + KeepWindowOpen, +} + +impl CloseWindowWhenNoItems { + pub fn should_close(&self) -> bool { + match self { + CloseWindowWhenNoItems::PlatformDefault => cfg!(target_os = "macos"), + CloseWindowWhenNoItems::CloseWindow => true, + CloseWindowWhenNoItems::KeepWindowOpen => false, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum RestoreOnStartupBehavior { + /// Always start with an empty editor + None, + /// Restore the workspace that was closed last. + LastWorkspace, + /// Restore all workspaces that were open when quitting Zed. + #[default] + LastSession, +} + +#[skip_serializing_none] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +pub struct TabBarSettingsContent { + /// Whether or not to show the tab bar in the editor. + /// + /// Default: true + pub show: Option, + /// Whether or not to show the navigation history buttons in the tab bar. + /// + /// Default: true + pub show_nav_history_buttons: Option, + /// Whether or not to show the tab bar buttons. + /// + /// Default: true + pub show_tab_bar_buttons: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AutosaveSetting { + /// Disable autosave. + Off, + /// Save after inactivity period of `milliseconds`. + AfterDelay { milliseconds: u64 }, + /// Autosave when focus changes. + OnFocusChange, + /// Autosave when the active window changes. + OnWindowChange, +} + +impl AutosaveSetting { + pub fn should_save_on_close(&self) -> bool { + matches!( + &self, + AutosaveSetting::OnFocusChange + | AutosaveSetting::OnWindowChange + | AutosaveSetting::AfterDelay { .. } + ) + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PaneSplitDirectionHorizontal { + Up, + Down, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PaneSplitDirectionVertical { + Left, + Right, +} + +#[skip_serializing_none] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct CenteredLayoutSettings { + /// The relative width of the left padding of the central pane from the + /// workspace when the centered layout is used. + /// + /// Default: 0.2 + pub left_padding: Option, + // The relative width of the right padding of the central pane from the + // workspace when the centered layout is used. + /// + /// Default: 0.2 + pub right_padding: Option, +} + +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum OnLastWindowClosed { + /// Match platform conventions by default, so don't quit on macOS, and quit on other platforms + #[default] + PlatformDefault, + /// Quit the application the last window is closed + QuitApp, +} + +impl OnLastWindowClosed { + pub fn is_quit_app(&self) -> bool { + match self { + OnLastWindowClosed::PlatformDefault => false, + OnLastWindowClosed::QuitApp => true, + } + } +} + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct ProjectPanelSettingsContent { + /// Whether to show the project panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Whether to hide gitignore files in the project panel. + /// + /// Default: false + pub hide_gitignore: Option, + /// Customize default width (in pixels) taken by project panel + /// + /// Default: 240 + pub default_width: Option, + /// The position of project panel + /// + /// Default: left + pub dock: Option, + /// Spacing between worktree entries in the project panel. + /// + /// Default: comfortable + pub entry_spacing: Option, + /// Whether to show file icons in the project panel. + /// + /// Default: true + pub file_icons: Option, + /// Whether to show folder icons or chevrons for directories in the project panel. + /// + /// Default: true + pub folder_icons: Option, + /// Whether to show the git status in the project panel. + /// + /// Default: true + pub git_status: Option, + /// Amount of indentation (in pixels) for nested items. + /// + /// Default: 20 + pub indent_size: Option, + /// Whether to reveal it in the project panel automatically, + /// when a corresponding project entry becomes active. + /// Gitignored entries are never auto revealed. + /// + /// Default: true + pub auto_reveal_entries: Option, + /// Whether to fold directories automatically + /// when directory has only one directory inside. + /// + /// Default: true + pub auto_fold_dirs: Option, + /// Whether the project panel should open on startup. + /// + /// Default: true + pub starts_open: Option, + /// Scrollbar-related settings + pub scrollbar: Option, + /// Which files containing diagnostic errors/warnings to mark in the project panel. + /// + /// Default: all + pub show_diagnostics: Option, + /// Settings related to indent guides in the project panel. + pub indent_guides: Option, + /// Whether to hide the root entry when only one folder is open in the window. + /// + /// Default: false + pub hide_root: Option, + /// Whether to stick parent directories at top of the project panel. + /// + /// Default: true + pub sticky_scroll: Option, + /// Whether to enable drag-and-drop operations in the project panel. + /// + /// Default: true + pub drag_and_drop: Option, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ProjectPanelEntrySpacing { + /// Comfortable spacing of entries. + #[default] + Comfortable, + /// The standard spacing of entries. + Standard, +} + +#[skip_serializing_none] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ProjectPanelIndentGuidesSettings { + pub show: Option, +} diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index d31dd82da475744d9658bc8ecdc6ec2ad17732fb..b4038aa9168e6f49a49726ade197ec926385ac31 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,4 +1,4 @@ -use crate::{Settings, settings_store::SettingsStore}; +use crate::{settings_content::SettingsContent, settings_store::SettingsStore}; use collections::HashSet; use fs::{Fs, PathEventKind}; use futures::{StreamExt, channel::mpsc}; @@ -126,10 +126,10 @@ pub fn watch_config_dir( rx } -pub fn update_settings_file( +pub fn update_settings_file( fs: Arc, cx: &App, - update: impl 'static + Send + FnOnce(&mut T::FileContent, &App), + update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), ) { - SettingsStore::global(cx).update_settings_file::(fs, update); + SettingsStore::global(cx).update_settings_file(fs, update); } diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs index 614f5eed813a22d96eafe0ab0696161dc21b233f..762842d851b73a661baf93a7060f26fe5714c872 100644 --- a/crates/settings/src/settings_json.rs +++ b/crates/settings/src/settings_json.rs @@ -26,7 +26,6 @@ pub fn update_value_in_json_text<'a>( tab_size: usize, old_value: &'a Value, new_value: &'a Value, - preserved_keys: &[&str], edits: &mut Vec<(Range, String)>, ) { // If the old and new values are both objects, then compare them key by key, @@ -43,7 +42,6 @@ pub fn update_value_in_json_text<'a>( tab_size, old_sub_value, new_sub_value, - preserved_keys, edits, ); } else { @@ -64,17 +62,12 @@ pub fn update_value_in_json_text<'a>( tab_size, &Value::Null, new_sub_value, - preserved_keys, edits, ); } key_path.pop(); } - } else if key_path - .last() - .is_some_and(|key| preserved_keys.contains(key)) - || old_value != new_value - { + } else if old_value != new_value { let mut new_value = new_value.clone(); if let Some(new_object) = new_value.as_object_mut() { new_object.retain(|_, v| !v.is_null()); diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index e06e423e92aa3fe093c69d8436545d4a13d17a82..acbff18bc333515c81d994e8629e3dff21fb4146 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -11,29 +11,28 @@ use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGl use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name}; use schemars::JsonSchema; -use serde::{Serialize, de::DeserializeOwned}; -use serde_json::{Value, json}; +use serde_json::Value; use smallvec::SmallVec; use std::{ any::{Any, TypeId, type_name}, - env, fmt::Debug, ops::Range, path::{Path, PathBuf}, str::{self, FromStr}, sync::Arc, }; -use util::{ - ResultExt as _, merge_non_null_json_value_into, - schemars::{DefaultDenyUnknownFields, add_new_subschema}, -}; +use util::{ResultExt as _, schemars::DefaultDenyUnknownFields}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{ ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry, VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text, - settings_ui_core::SettingsUi, update_value_in_json_text, + settings_content::{ + ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent, + UserSettingsContent, + }, + update_value_in_json_text, }; pub trait SettingsKey: 'static + Send + Sync { @@ -48,7 +47,7 @@ pub trait SettingsKey: 'static + Send + Sync { /// A value that can be defined as a user setting. /// /// Settings can be loaded from a combination of multiple JSON files. -pub trait Settings: 'static + Send + Sync { +pub trait Settings: 'static + Send + Sync + Sized { /// The name of the keys in the [`FileContent`](Self::FileContent) that should /// always be written to a settings file, even if their value matches the default /// value. @@ -58,30 +57,16 @@ pub trait Settings: 'static + Send + Sync { /// user settings match the current version of the settings. const PRESERVED_KEYS: Option<&'static [&'static str]> = None; - /// The type that is stored in an individual JSON file. - type FileContent: Clone - + Default - + Serialize - + DeserializeOwned - + JsonSchema - + SettingsUi - + SettingsKey; - - /* - * let path = Settings - * - * - */ - /// The logic for combining together values from one or more JSON files into the - /// final value for this setting. + /// Read the value from default.json. + /// This function *should* panic if default values are missing, + /// and you should add a default to default.json for documentation. + fn from_defaults(content: &SettingsContent, cx: &mut App) -> Self; + + /// Update the value based on the content from the current file. /// - /// # Warning - /// `Self::FileContent` deserialized field names should match with `Self` deserialized field names - /// otherwise the field won't be deserialized properly and you will get the error: - /// "A default setting must be added to the `default.json` file" - fn load(sources: SettingsSources, cx: &mut App) -> Result - where - Self: Sized; + /// This function *should not* panic if there are problems, as the + /// content of user-provided settings files may be incomplete or invalid. + fn refine(&mut self, content: &SettingsContent, cx: &mut App); fn missing_default() -> anyhow::Error { anyhow::anyhow!("missing default for: {}", std::any::type_name::()) @@ -89,7 +74,7 @@ pub trait Settings: 'static + Send + Sync { /// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known /// equivalent settings from a vscode config to our config - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent); + fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut SettingsContent) {} #[track_caller] fn register(cx: &mut App) @@ -146,69 +131,6 @@ pub trait Settings: 'static + Send + Sync { } } -#[derive(Clone, Copy, Debug)] -pub struct SettingsSources<'a, T> { - /// The default Zed settings. - pub default: &'a T, - /// Global settings (loaded before user settings). - pub global: Option<&'a T>, - /// Settings provided by extensions. - pub extensions: Option<&'a T>, - /// The user settings. - pub user: Option<&'a T>, - /// The user settings for the current release channel. - pub release_channel: Option<&'a T>, - /// The user settings for the current operating system. - pub operating_system: Option<&'a T>, - /// The settings associated with an enabled settings profile - pub profile: Option<&'a T>, - /// The server's settings. - pub server: Option<&'a T>, - /// The project settings, ordered from least specific to most specific. - pub project: &'a [&'a T], -} - -impl<'a, T: Serialize> SettingsSources<'a, T> { - /// Returns an iterator over the default settings as well as all settings customizations. - pub fn defaults_and_customizations(&self) -> impl Iterator { - [self.default].into_iter().chain(self.customizations()) - } - - /// Returns an iterator over all of the settings customizations. - pub fn customizations(&self) -> impl Iterator { - self.global - .into_iter() - .chain(self.extensions) - .chain(self.user) - .chain(self.release_channel) - .chain(self.operating_system) - .chain(self.profile) - .chain(self.server) - .chain(self.project.iter().copied()) - } - - /// Returns the settings after performing a JSON merge of the provided customizations. - /// - /// Customizations later in the iterator win out over the earlier ones. - pub fn json_merge_with( - customizations: impl Iterator, - ) -> Result { - let mut merged = Value::Null; - for value in customizations { - merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); - } - Ok(serde_json::from_value(merged)?) - } - - /// Returns the settings after performing a JSON merge of the customizations into the - /// default settings. - /// - /// More-specific customizations win out over the less-specific ones. - pub fn json_merge(&'a self) -> Result { - Self::json_merge_with(self.defaults_and_customizations()) - } -} - #[derive(Clone, Copy, Debug)] pub struct SettingsLocation<'a> { pub worktree_id: WorktreeId, @@ -218,17 +140,15 @@ pub struct SettingsLocation<'a> { /// A set of strongly-typed setting values defined via multiple config files. pub struct SettingsStore { setting_values: HashMap>, - raw_default_settings: Value, - raw_global_settings: Option, - raw_user_settings: Value, - raw_server_settings: Option, - raw_extension_settings: Value, - raw_local_settings: BTreeMap<(WorktreeId, Arc), Value>, + default_settings: Box, + user_settings: Option, + global_settings: Option>, + + extension_settings: Option>, + server_settings: Option>, + local_settings: BTreeMap<(WorktreeId, Arc), SettingsContent>, raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc), (String, Option)>, - tab_size_callback: Option<( - TypeId, - Box Option + Send + Sync + 'static>, - )>, + _setting_file_updates: Task<()>, setting_file_updates_tx: mpsc::UnboundedSender LocalBoxFuture<'static, Result<()>>>>, @@ -271,51 +191,35 @@ struct SettingValue { } trait AnySettingValue: 'static + Send + Sync { - fn key(&self) -> Option<&'static str>; fn setting_type_name(&self) -> &'static str; - fn deserialize_setting(&self, json: &Value) -> Result { - self.deserialize_setting_with_key(json).1 - } - fn deserialize_setting_with_key( - &self, - json: &Value, - ) -> (Option<&'static str>, Result); - fn load_setting( - &self, - sources: SettingsSources, - cx: &mut App, - ) -> Result>; + + fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box; + fn refine(&self, value: &mut dyn Any, s: &[&SettingsContent], cx: &mut App); + fn value_for_path(&self, path: Option) -> &dyn Any; fn all_local_values(&self) -> Vec<(WorktreeId, Arc, &dyn Any)>; fn set_global_value(&mut self, value: Box); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc, value: Box); - fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema; - fn edits_for_update( + fn import_from_vscode( &self, - raw_settings: &serde_json::Value, - tab_size: usize, vscode_settings: &VsCodeSettings, - text: &mut String, - edits: &mut Vec<(Range, String)>, + settings_content: &mut SettingsContent, ); - fn settings_ui_item(&self) -> SettingsUiEntry; } -struct DeserializedSetting(Box); - impl SettingsStore { - pub fn new(cx: &App) -> Self { + pub fn new(cx: &App, default_settings: &str) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); + let default_settings = parse_json_with_comments(default_settings).unwrap(); Self { setting_values: Default::default(), - raw_default_settings: json!({}), - raw_global_settings: None, - raw_user_settings: json!({}), - raw_server_settings: None, - raw_extension_settings: json!({}), - raw_local_settings: Default::default(), + default_settings, + global_settings: None, + server_settings: None, + user_settings: None, + extension_settings: None, + local_settings: BTreeMap::default(), raw_editorconfig_settings: BTreeMap::default(), - tab_size_callback: Default::default(), setting_file_updates_tx, _setting_file_updates: cx.spawn(async move |cx| { while let Some(setting_file_update) = setting_file_updates_rx.next().await { @@ -354,71 +258,38 @@ impl SettingsStore { local_values: Vec::new(), })); - if let Some(default_settings) = setting_value - .deserialize_setting(&self.raw_default_settings) - .log_err() - { - let user_value = setting_value - .deserialize_setting(&self.raw_user_settings) - .log_err(); - - let mut release_channel_value = None; - if let Some(release_settings) = &self - .raw_user_settings - .get(release_channel::RELEASE_CHANNEL.dev_name()) - { - release_channel_value = setting_value - .deserialize_setting(release_settings) - .log_err(); - } - - let mut os_settings_value = None; - if let Some(os_settings) = &self.raw_user_settings.get(env::consts::OS) { - os_settings_value = setting_value.deserialize_setting(os_settings).log_err(); - } + let mut refinements = Vec::default(); - let mut profile_value = None; - if let Some(active_profile) = cx.try_global::() - && 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(); - } + if let Some(extension_settings) = self.extension_settings.as_deref() { + refinements.push(extension_settings) + } - let server_value = self - .raw_server_settings - .as_ref() - .and_then(|server_setting| { - setting_value.deserialize_setting(server_setting).log_err() - }); + if let Some(global_settings) = self.global_settings.as_deref() { + refinements.push(global_settings) + } - let extension_value = setting_value - .deserialize_setting(&self.raw_extension_settings) - .log_err(); - - if let Some(setting) = setting_value - .load_setting( - SettingsSources { - default: &default_settings, - global: None, - extensions: extension_value.as_ref(), - user: user_value.as_ref(), - release_channel: release_channel_value.as_ref(), - operating_system: os_settings_value.as_ref(), - profile: profile_value.as_ref(), - server: server_value.as_ref(), - project: &[], - }, - cx, - ) - .context("A default setting must be added to the `default.json` file") - .log_err() - { - setting_value.set_global_value(setting); + if let Some(user_settings) = self.user_settings.as_ref() { + refinements.push(&user_settings.content); + if let Some(release_channel) = user_settings.for_release_channel() { + refinements.push(release_channel) + } + if let Some(os) = user_settings.for_os() { + refinements.push(os) } + if let Some(profile) = user_settings.for_profile(cx) { + refinements.push(profile) + } + } + + if let Some(server_settings) = self.server_settings.as_ref() { + refinements.push(server_settings) } + let mut value = T::from_defaults(&self.default_settings, cx); + for refinement in refinements { + value.refine(refinement, cx) + } + + setting_value.set_global_value(Box::new(value)); } /// Get the value of a setting. @@ -476,13 +347,17 @@ impl SettingsStore { /// /// For user-facing functionality use the typed setting interface. /// (e.g. ProjectSettings::get_global(cx)) - pub fn raw_user_settings(&self) -> &Value { - &self.raw_user_settings + pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> { + self.user_settings.as_ref() } /// Replaces current settings with the values from the given JSON. - pub fn set_raw_user_settings(&mut self, new_settings: Value, cx: &mut App) -> Result<()> { - self.raw_user_settings = new_settings; + pub fn set_raw_user_settings( + &mut self, + new_settings: UserSettingsContent, + cx: &mut App, + ) -> Result<()> { + self.user_settings = Some(new_settings); self.recompute_values(None, cx)?; Ok(()) } @@ -493,38 +368,31 @@ impl SettingsStore { new_settings: Option, cx: &mut App, ) -> Result<()> { - self.raw_server_settings = new_settings; + // Rewrite the server settings into a content type + self.server_settings = new_settings + .map(|settings| settings.to_string()) + .and_then(|str| parse_json_with_comments::(&str).ok()) + .map(Box::new); + self.recompute_values(None, cx)?; Ok(()) } /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator { - self.raw_user_settings - .get("profiles") - .and_then(|v| v.as_object()) - .into_iter() - .flat_map(|obj| obj.keys()) - .map(|s| s.as_str()) - } - - /// Access the raw JSON value of the global settings. - pub fn raw_global_settings(&self) -> Option<&Value> { - self.raw_global_settings.as_ref() + self.user_settings + .iter() + .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str())) } /// Access the raw JSON value of the default settings. - pub fn raw_default_settings(&self) -> &Value { - &self.raw_default_settings + pub fn raw_default_settings(&self) -> &SettingsContent { + &self.default_settings } #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut App) -> Self { - let mut this = Self::new(cx); - this.set_default_settings(&crate::test_settings(), cx) - .unwrap(); - this.set_user_settings("{}", cx).unwrap(); - this + Self::new(cx, &crate::test_settings()) } /// Updates the value of a setting in the user's global configuration. @@ -532,13 +400,18 @@ impl SettingsStore { /// This is only for tests. Normally, settings are only loaded from /// JSON files. #[cfg(any(test, feature = "test-support"))] - pub fn update_user_settings( + pub fn update_user_settings( &mut self, cx: &mut App, - update: impl FnOnce(&mut T::FileContent), + update: impl FnOnce(&mut SettingsContent), ) { - let old_text = serde_json::to_string(&self.raw_user_settings).unwrap(); - let new_text = self.new_text_for_update::(old_text, update); + let mut content = self.user_settings.clone().unwrap_or_default().content; + update(&mut content); + let new_text = serde_json::to_string(&UserSettingsContent { + content, + ..Default::default() + }) + .unwrap(); self.set_user_settings(&new_text, cx).unwrap(); } @@ -637,14 +510,14 @@ impl SettingsStore { self.update_settings_file_inner(fs, update) } - pub fn update_settings_file( + pub fn update_settings_file( &self, fs: Arc, - update: impl 'static + Send + FnOnce(&mut T::FileContent, &App), + update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), ) { _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| { cx.read_global(|store: &SettingsStore, cx| { - store.new_text_for_update::(old_text, |content| update(content, cx)) + store.new_text_for_update(old_text, |content| update(content, cx)) }) }); } @@ -662,21 +535,19 @@ impl SettingsStore { } pub fn settings_ui_items(&self) -> impl IntoIterator { - self.setting_values - .values() - .map(|item| item.settings_ui_item()) + [].into_iter() } } impl SettingsStore { /// Updates the value of a setting in a JSON file, returning the new text /// for that JSON file. - pub fn new_text_for_update( + pub fn new_text_for_update( &self, old_text: String, - update: impl FnOnce(&mut T::FileContent), + update: impl FnOnce(&mut SettingsContent), ) -> String { - let edits = self.edits_for_update::(&old_text, update); + let edits = self.edits_for_update(&old_text, update); let mut new_text = old_text; for (range, replacement) in edits.into_iter() { new_text.replace_range(range, &replacement); @@ -684,52 +555,30 @@ impl SettingsStore { new_text } - pub fn get_vscode_edits(&self, mut old_text: String, vscode: &VsCodeSettings) -> String { - let mut new_text = old_text.clone(); - let mut edits: Vec<(Range, String)> = Vec::new(); - let raw_settings = parse_json_with_comments::(&old_text).unwrap_or_default(); - let tab_size = self.json_tab_size(); - for v in self.setting_values.values() { - v.edits_for_update(&raw_settings, tab_size, vscode, &mut old_text, &mut edits); - } - for (range, replacement) in edits.into_iter() { - new_text.replace_range(range, &replacement); - } - new_text + pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String { + self.new_text_for_update(old_text, |settings_content| { + for v in self.setting_values.values() { + v.import_from_vscode(vscode, settings_content) + } + }) } /// Updates the value of a setting in a JSON file, returning a list /// of edits to apply to the JSON file. - pub fn edits_for_update( + pub fn edits_for_update( &self, text: &str, - update: impl FnOnce(&mut T::FileContent), + update: impl FnOnce(&mut SettingsContent), ) -> Vec<(Range, String)> { - let setting_type_id = TypeId::of::(); - - let preserved_keys = T::PRESERVED_KEYS.unwrap_or_default(); - - let setting = self - .setting_values - .get(&setting_type_id) - .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())); - let raw_settings = parse_json_with_comments::(text).unwrap_or_default(); - let (key, deserialized_setting) = setting.deserialize_setting_with_key(&raw_settings); - let old_content = match deserialized_setting { - Ok(content) => content.0.downcast::().unwrap(), - Err(_) => Box::<::FileContent>::default(), - }; + let old_content: UserSettingsContent = + parse_json_with_comments(text).log_err().unwrap_or_default(); let mut new_content = old_content.clone(); - update(&mut new_content); + update(&mut new_content.content); let old_value = serde_json::to_value(&old_content).unwrap(); let new_value = serde_json::to_value(new_content).unwrap(); let mut key_path = Vec::new(); - if let Some(key) = key { - key_path.push(key); - } - let mut edits = Vec::new(); let tab_size = self.json_tab_size(); let mut text = text.to_string(); @@ -739,35 +588,13 @@ impl SettingsStore { tab_size, &old_value, &new_value, - preserved_keys, &mut edits, ); edits } - /// Configure the tab sized when updating JSON files. - pub fn set_json_tab_size_callback( - &mut self, - get_tab_size: fn(&T) -> Option, - ) { - self.tab_size_callback = Some(( - TypeId::of::(), - Box::new(move |value| get_tab_size(value.downcast_ref::().unwrap())), - )); - } - pub fn json_tab_size(&self) -> usize { - const DEFAULT_JSON_TAB_SIZE: usize = 2; - - if let Some((setting_type_id, callback)) = &self.tab_size_callback { - let setting_value = self.setting_values.get(setting_type_id).unwrap(); - let value = setting_value.value_for_path(None); - if let Some(value) = callback(value) { - return value; - } - } - - DEFAULT_JSON_TAB_SIZE + 2 } /// Sets the default settings via a JSON string. @@ -778,29 +605,22 @@ impl SettingsStore { default_settings_content: &str, cx: &mut App, ) -> Result<()> { - let settings: Value = parse_json_with_comments(default_settings_content)?; - anyhow::ensure!(settings.is_object(), "settings must be an object"); - self.raw_default_settings = settings; + self.default_settings = parse_json_with_comments(default_settings_content)?; self.recompute_values(None, cx)?; Ok(()) } /// Sets the user settings via a JSON string. - pub fn set_user_settings( - &mut self, - user_settings_content: &str, - cx: &mut App, - ) -> Result { - let settings: Value = if user_settings_content.is_empty() { + pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> { + let settings: UserSettingsContent = if user_settings_content.is_empty() { parse_json_with_comments("{}")? } else { parse_json_with_comments(user_settings_content)? }; - anyhow::ensure!(settings.is_object(), "settings must be an object"); - self.raw_user_settings = settings.clone(); + self.user_settings = Some(settings); self.recompute_values(None, cx)?; - Ok(settings) + Ok(()) } /// Sets the global settings via a JSON string. @@ -808,17 +628,16 @@ impl SettingsStore { &mut self, global_settings_content: &str, cx: &mut App, - ) -> Result { - let settings: Value = if global_settings_content.is_empty() { + ) -> Result<()> { + let settings: SettingsContent = if global_settings_content.is_empty() { parse_json_with_comments("{}")? } else { parse_json_with_comments(global_settings_content)? }; - anyhow::ensure!(settings.is_object(), "settings must be an object"); - self.raw_global_settings = Some(settings.clone()); + self.global_settings = Some(Box::new(settings)); self.recompute_values(None, cx)?; - Ok(settings) + Ok(()) } pub fn set_server_settings( @@ -826,20 +645,20 @@ impl SettingsStore { server_settings_content: &str, cx: &mut App, ) -> Result<()> { - let settings: Option = if server_settings_content.is_empty() { + let settings: Option = if server_settings_content.is_empty() { None } else { parse_json_with_comments(server_settings_content)? }; - anyhow::ensure!( - settings - .as_ref() - .map(|value| value.is_object()) - .unwrap_or(true), - "settings must be an object" - ); - self.raw_server_settings = settings; + // Rewrite the server settings into a content type + self.server_settings = settings.map(|settings| { + Box::new(SettingsContent { + project: settings.project, + ..Default::default() + }) + }); + self.recompute_values(None, cx)?; Ok(()) } @@ -875,7 +694,7 @@ impl SettingsStore { } (LocalSettingsKind::Settings, None) => { zed_settings_changed = self - .raw_local_settings + .local_settings .remove(&(root_id, directory_path.clone())) .is_some() } @@ -884,24 +703,27 @@ impl SettingsStore { .remove(&(root_id, directory_path.clone())); } (LocalSettingsKind::Settings, Some(settings_contents)) => { - let new_settings = - parse_json_with_comments::(settings_contents).map_err(|e| { - InvalidSettingsError::LocalSettings { - path: directory_path.join(local_settings_file_relative_path()), - message: e.to_string(), - } - })?; - match self - .raw_local_settings - .entry((root_id, directory_path.clone())) - { + let new_settings = parse_json_with_comments::( + settings_contents, + ) + .map_err(|e| InvalidSettingsError::LocalSettings { + path: directory_path.join(local_settings_file_relative_path()), + message: e.to_string(), + })?; + match self.local_settings.entry((root_id, directory_path.clone())) { btree_map::Entry::Vacant(v) => { - v.insert(new_settings); + v.insert(SettingsContent { + project: new_settings, + ..Default::default() + }); zed_settings_changed = true; } btree_map::Entry::Occupied(mut o) => { - if o.get() != &new_settings { - o.insert(new_settings); + if &o.get().project != &new_settings { + o.insert(SettingsContent { + project: new_settings, + ..Default::default() + }); zed_settings_changed = true; } } @@ -953,17 +775,25 @@ impl SettingsStore { Ok(()) } - pub fn set_extension_settings(&mut self, content: T, cx: &mut App) -> Result<()> { - let settings: Value = serde_json::to_value(content)?; - anyhow::ensure!(settings.is_object(), "settings must be an object"); - self.raw_extension_settings = settings; + pub fn set_extension_settings( + &mut self, + content: ExtensionsSettingsContent, + cx: &mut App, + ) -> Result<()> { + self.extension_settings = Some(Box::new(SettingsContent { + project: ProjectSettingsContent { + all_languages: content.all_languages, + ..Default::default() + }, + ..Default::default() + })); self.recompute_values(None, cx)?; Ok(()) } /// Add or remove a set of local settings via a JSON string. pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> { - self.raw_local_settings + self.local_settings .retain(|(worktree_id, _), _| worktree_id != &root_id); self.recompute_values(Some((root_id, "".as_ref())), cx)?; Ok(()) @@ -972,8 +802,8 @@ impl SettingsStore { pub fn local_settings( &self, root_id: WorktreeId, - ) -> impl '_ + Iterator, String)> { - self.raw_local_settings + ) -> impl '_ + Iterator, &ProjectSettingsContent)> { + self.local_settings .range( (root_id, Path::new("").into()) ..( @@ -981,7 +811,7 @@ impl SettingsStore { Path::new("").into(), ), ) - .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap())) + .map(|((_, path), content)| (path.clone(), &content.project)) } pub fn local_editorconfig_settings( @@ -1005,190 +835,15 @@ impl SettingsStore { let mut generator = schemars::generate::SchemaSettings::draft2019_09() .with_transform(DefaultDenyUnknownFields) .into_generator(); - let mut combined_schema = json!({ - "type": "object", - "properties": {} - }); - - // Merge together settings schemas, similarly to json schema's "allOf". This merging is - // recursive, though at time of writing this recursive nature isn't used very much. An - // example of it is the schema for `jupyter` having contribution from both `EditorSettings` - // and `JupyterSettings`. - // - // This logic could be removed in favor of "allOf", but then there isn't the opportunity to - // validate and fully control the merge. - for setting_value in self.setting_values.values() { - 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") - && 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 { - setting_schema.remove("description"); - setting_schema.remove("additionalProperties"); - merge_schema(&mut combined_schema, setting_schema.to_value()); - } - } - fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) { - let (Some(target_obj), serde_json::Value::Object(source_obj)) = - (target.as_object_mut(), source) - else { - return; - }; - - for (source_key, source_value) in source_obj { - match source_key.as_str() { - "properties" => { - let serde_json::Value::Object(source_properties) = source_value else { - log::error!( - "bug: expected object for `{}` json schema field, but got: {}", - source_key, - source_value - ); - continue; - }; - let target_properties = - target_obj.entry(source_key.clone()).or_insert(json!({})); - let Some(target_properties) = target_properties.as_object_mut() else { - log::error!( - "bug: expected object for `{}` json schema field, but got: {}", - source_key, - target_properties - ); - continue; - }; - for (key, value) in source_properties { - if let Some(existing) = target_properties.get_mut(&key) { - merge_schema(existing, value); - } else { - target_properties.insert(key, value); - } - } - } - "allOf" | "anyOf" | "oneOf" => { - let serde_json::Value::Array(source_array) = source_value else { - log::error!( - "bug: expected array for `{}` json schema field, but got: {}", - source_key, - source_value, - ); - continue; - }; - let target_array = - target_obj.entry(source_key.clone()).or_insert(json!([])); - let Some(target_array) = target_array.as_array_mut() else { - log::error!( - "bug: expected array for `{}` json schema field, but got: {}", - source_key, - target_array, - ); - continue; - }; - target_array.extend(source_array); - } - "type" - | "$ref" - | "enum" - | "minimum" - | "maximum" - | "pattern" - | "description" - | "additionalProperties" => { - if let Some(old_value) = - target_obj.insert(source_key.clone(), source_value.clone()) - && old_value != source_value - { - log::error!( - "bug: while merging JSON schemas, \ - mismatch `\"{}\": {}` (before was `{}`)", - source_key, - old_value, - source_value - ); - } - } - _ => { - log::error!( - "bug: while merging settings JSON schemas, \ - encountered unexpected `\"{}\": {}`", - source_key, - source_value - ); - } - } - } - } + let schema = UserSettingsContent::json_schema(&mut generator); // add schemas which are determined at runtime for parameterized_json_schema in inventory::iter::() { (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx); } - // add merged settings schema to the definitions - const ZED_SETTINGS: &str = "ZedSettings"; - let zed_settings_ref = add_new_subschema(&mut generator, ZED_SETTINGS, combined_schema); - - // add `ZedSettingsOverride` which is the same as `ZedSettings` except that unknown - // fields are rejected. This is used for release stage settings and profiles. - let mut zed_settings_override = zed_settings_ref.clone(); - zed_settings_override.insert("unevaluatedProperties".to_string(), false.into()); - let zed_settings_override_ref = add_new_subschema( - &mut generator, - "ZedSettingsOverride", - zed_settings_override.to_value(), - ); - - // Remove `"additionalProperties": false` added by `DefaultDenyUnknownFields` so that - // unknown fields can be handled by the root schema and `ZedSettingsOverride`. - let mut definitions = generator.take_definitions(true); - definitions - .get_mut(ZED_SETTINGS) - .unwrap() - .as_object_mut() - .unwrap() - .remove("additionalProperties"); - - let meta_schema = generator - .settings() - .meta_schema - .as_ref() - .expect("meta_schema should be present in schemars settings") - .to_string(); - - json!({ - "$schema": meta_schema, - "title": "Zed Settings", - "unevaluatedProperties": false, - // ZedSettings + settings overrides for each release stage / OS / profiles - "allOf": [ - zed_settings_ref, - { - "properties": { - "dev": zed_settings_override_ref, - "nightly": zed_settings_override_ref, - "stable": zed_settings_override_ref, - "preview": zed_settings_override_ref, - "linux": zed_settings_override_ref, - "macos": zed_settings_override_ref, - "windows": zed_settings_override_ref, - "profiles": { - "type": "object", - "description": "Configures any number of settings profiles.", - "additionalProperties": zed_settings_override_ref - } - } - } - ], - "$defs": definitions, - }) + schema.to_value() } fn recompute_values( @@ -1197,90 +852,48 @@ impl SettingsStore { cx: &mut App, ) -> std::result::Result<(), InvalidSettingsError> { // Reload the global and local values for every setting. - let mut project_settings_stack = Vec::::new(); + let mut project_settings_stack = Vec::<&SettingsContent>::new(); let mut paths_stack = Vec::>::new(); - for setting_value in self.setting_values.values_mut() { - let default_settings = setting_value - .deserialize_setting(&self.raw_default_settings) - .map_err(|e| InvalidSettingsError::DefaultSettings { - message: e.to_string(), - })?; - let global_settings = self - .raw_global_settings - .as_ref() - .and_then(|setting| setting_value.deserialize_setting(setting).log_err()); - - let extension_settings = setting_value - .deserialize_setting(&self.raw_extension_settings) - .log_err(); - - let user_settings = match setting_value.deserialize_setting(&self.raw_user_settings) { - Ok(settings) => Some(settings), - Err(error) => { - return Err(InvalidSettingsError::UserSettings { - message: error.to_string(), - }); - } - }; + let mut refinements = Vec::default(); - let server_settings = self - .raw_server_settings - .as_ref() - .and_then(|setting| setting_value.deserialize_setting(setting).log_err()); - - let mut release_channel_settings = None; - if let Some(release_settings) = &self - .raw_user_settings - .get(release_channel::RELEASE_CHANNEL.dev_name()) - && let Some(release_settings) = setting_value - .deserialize_setting(release_settings) - .log_err() - { - release_channel_settings = Some(release_settings); - } + if let Some(extension_settings) = self.extension_settings.as_deref() { + refinements.push(extension_settings) + } - let mut os_settings = None; - 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); - } + if let Some(global_settings) = self.global_settings.as_deref() { + refinements.push(global_settings) + } - let mut profile_settings = None; - if let Some(active_profile) = cx.try_global::() - && 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 let Some(user_settings) = self.user_settings.as_ref() { + refinements.push(&user_settings.content); + if let Some(release_channel) = user_settings.for_release_channel() { + refinements.push(release_channel) + } + if let Some(os) = user_settings.for_os() { + refinements.push(os) + } + if let Some(profile) = user_settings.for_profile(cx) { + refinements.push(profile) } + } + + if let Some(server_settings) = self.server_settings.as_ref() { + refinements.push(server_settings) + } + for setting_value in self.setting_values.values_mut() { // If the global settings file changed, reload the global value for the field. - if changed_local_path.is_none() - && let Some(value) = setting_value - .load_setting( - SettingsSources { - default: &default_settings, - global: global_settings.as_ref(), - extensions: extension_settings.as_ref(), - user: user_settings.as_ref(), - release_channel: release_channel_settings.as_ref(), - operating_system: os_settings.as_ref(), - profile: profile_settings.as_ref(), - server: server_settings.as_ref(), - project: &[], - }, - cx, - ) - .log_err() - { + if changed_local_path.is_none() { + let mut value = setting_value.from_default(&self.default_settings, cx); + setting_value.refine(value.as_mut(), &refinements, cx); setting_value.set_global_value(value); } // Reload the local values for the setting. paths_stack.clear(); project_settings_stack.clear(); - for ((root_id, directory_path), local_settings) in &self.raw_local_settings { + for ((root_id, directory_path), local_settings) in &self.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 @@ -1293,49 +906,21 @@ impl SettingsStore { break; } - match setting_value.deserialize_setting(local_settings) { - Ok(local_settings) => { - paths_stack.push(Some((*root_id, directory_path.as_ref()))); - project_settings_stack.push(local_settings); - - // If a local settings file changed, then avoid recomputing local - // settings for any path outside of that directory. - 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) - }, - ) { - continue; - } + paths_stack.push(Some((*root_id, directory_path.as_ref()))); + project_settings_stack.push(local_settings); - if let Some(value) = setting_value - .load_setting( - SettingsSources { - default: &default_settings, - global: global_settings.as_ref(), - extensions: extension_settings.as_ref(), - user: user_settings.as_ref(), - release_channel: release_channel_settings.as_ref(), - operating_system: os_settings.as_ref(), - profile: profile_settings.as_ref(), - server: server_settings.as_ref(), - project: &project_settings_stack.iter().collect::>(), - }, - cx, - ) - .log_err() - { - setting_value.set_local_value(*root_id, directory_path.clone(), value); - } - } - Err(error) => { - return Err(InvalidSettingsError::LocalSettings { - path: directory_path.join(local_settings_file_relative_path()), - message: error.to_string(), - }); - } + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + 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) + }) { + continue; } + + let mut value = setting_value.from_default(&self.default_settings, cx); + setting_value.refine(value.as_mut(), &refinements, cx); + setting_value.refine(value.as_mut(), &project_settings_stack, cx); + setting_value.set_local_value(*root_id, directory_path.clone(), value); } } Ok(()) @@ -1408,108 +993,27 @@ impl Debug for SettingsStore { .map(|value| value.setting_type_name()) .collect::>(), ) - .field("default_settings", &self.raw_default_settings) - .field("user_settings", &self.raw_user_settings) - .field("local_settings", &self.raw_local_settings) + .field("default_settings", &self.default_settings) + .field("user_settings", &self.user_settings) + .field("local_settings", &self.local_settings) .finish_non_exhaustive() } } impl AnySettingValue for SettingValue { - fn key(&self) -> Option<&'static str> { - T::FileContent::KEY - } - - fn setting_type_name(&self) -> &'static str { - type_name::() + fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box { + Box::new(T::from_defaults(s, cx)) as _ } - fn load_setting( - &self, - values: SettingsSources, - cx: &mut App, - ) -> Result> { - Ok(Box::new(T::load( - SettingsSources { - default: values.default.0.downcast_ref::().unwrap(), - global: values - .global - .map(|value| value.0.downcast_ref::().unwrap()), - extensions: values - .extensions - .map(|value| value.0.downcast_ref::().unwrap()), - user: values - .user - .map(|value| value.0.downcast_ref::().unwrap()), - release_channel: values - .release_channel - .map(|value| value.0.downcast_ref::().unwrap()), - operating_system: values - .operating_system - .map(|value| value.0.downcast_ref::().unwrap()), - profile: values - .profile - .map(|value| value.0.downcast_ref::().unwrap()), - server: values - .server - .map(|value| value.0.downcast_ref::().unwrap()), - project: values - .project - .iter() - .map(|value| value.0.downcast_ref().unwrap()) - .collect::>() - .as_slice(), - }, - cx, - )?)) + fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) { + let value = value.downcast_mut::().unwrap(); + for refinement in refinements { + value.refine(refinement, cx) + } } - fn deserialize_setting_with_key( - &self, - mut json: &Value, - ) -> (Option<&'static str>, Result) { - let mut key = None; - if let Some(k) = T::FileContent::KEY { - if let Some(value) = json.get(k) { - json = value; - key = Some(k); - } else if let Some((k, value)) = - T::FileContent::FALLBACK_KEY.and_then(|k| Some((k, json.get(k)?))) - { - json = value; - key = Some(k); - } else { - let value = T::FileContent::default(); - return ( - T::FileContent::KEY, - Ok(DeserializedSetting(Box::new(value))), - ); - } - } - let value = serde_path_to_error::deserialize::<_, T::FileContent>(json) - .map(|value| DeserializedSetting(Box::new(value))) - .map_err(|err| { - // construct a path using the key and reported error path if possible. - // Unfortunately, serde_path_to_error does not expose the necessary - // methods and data to simply add the key to the path - let mut path = String::new(); - if let Some(key) = key { - path.push_str(key); - } - let err_path = err.path().to_string(); - // when the path is empty, serde_path_to_error stringifies the path as ".", - // when the path is unknown, serde_path_to_error stringifies the path as an empty string - if !err_path.is_empty() && !err_path.starts_with(".") { - path.push('.'); - path.push_str(&err_path); - } - if path.is_empty() { - anyhow::Error::from(err.into_inner()) - } else { - anyhow::anyhow!("'{}': {}", err.into_inner(), path) - } - }); - (key, value) + fn setting_type_name(&self) -> &'static str { + type_name::() } fn all_local_values(&self) -> Vec<(WorktreeId, Arc, &dyn Any)> { @@ -1548,117 +1052,155 @@ impl AnySettingValue for SettingValue { } } - fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema { - T::FileContent::json_schema(generator) - } - - fn edits_for_update( + fn import_from_vscode( &self, - raw_settings: &serde_json::Value, - tab_size: usize, vscode_settings: &VsCodeSettings, - text: &mut String, - edits: &mut Vec<(Range, String)>, + settings_content: &mut SettingsContent, ) { - let (key, deserialized_setting) = self.deserialize_setting_with_key(raw_settings); - let old_content = match deserialized_setting { - Ok(content) => content.0.downcast::().unwrap(), - Err(_) => Box::<::FileContent>::default(), - }; - let mut new_content = old_content.clone(); - T::import_from_vscode(vscode_settings, &mut new_content); + T::import_from_vscode(vscode_settings, settings_content); + } +} - let old_value = serde_json::to_value(&old_content).unwrap(); - let new_value = serde_json::to_value(new_content).unwrap(); +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; - let mut key_path = Vec::new(); - if let Some(key) = key { - key_path.push(key); + use crate::{ + TitleBarSettingsContent, TitleBarVisibility, VsCodeSettingsSource, default_settings, + settings_content::LanguageSettingsContent, test_settings, + }; + + use super::*; + use unindent::Unindent; + use util::MergeFrom; + + #[derive(Debug, PartialEq)] + struct AutoUpdateSetting { + auto_update: bool, + } + + impl Settings for AutoUpdateSetting { + fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + AutoUpdateSetting { + auto_update: content.auto_update.unwrap(), + } } - update_value_in_json_text( - text, - &mut key_path, - tab_size, - &old_value, - &new_value, - T::PRESERVED_KEYS.unwrap_or_default(), - edits, - ); + fn refine(&mut self, content: &SettingsContent, _: &mut App) { + if let Some(auto_update) = content.auto_update { + self.auto_update = auto_update; + } + } + + fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {} } - fn settings_ui_item(&self) -> SettingsUiEntry { - <::FileContent as SettingsUi>::settings_ui_entry() + #[derive(Debug, PartialEq)] + struct TitleBarSettings { + show: TitleBarVisibility, + show_branch_name: bool, } -} -#[cfg(test)] -mod tests { - use crate::VsCodeSettingsSource; + impl Settings for TitleBarSettings { + fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + let content = content.title_bar.clone().unwrap(); + TitleBarSettings { + show: content.show.unwrap(), + show_branch_name: content.show_branch_name.unwrap(), + } + } - use super::*; - // This is so the SettingsUi macro can still work properly - use crate as settings; - use serde::Deserialize; - use settings_ui_macros::{SettingsKey, SettingsUi}; - use unindent::Unindent; + fn refine(&mut self, content: &SettingsContent, _: &mut App) { + let Some(content) = content.title_bar.as_ref() else { + return; + }; + self.show.merge_from(&content.show) + } + + fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { + let mut show = None; + + vscode.enum_setting("window.titleBarStyle", &mut show, |value| match value { + "never" => Some(TitleBarVisibility::Never), + "always" => Some(TitleBarVisibility::Always), + _ => None, + }); + if let Some(show) = show { + content.title_bar.get_or_insert_default().show.replace(show); + } + } + } + + #[derive(Debug, PartialEq)] + struct DefaultLanguageSettings { + tab_size: NonZeroU32, + preferred_line_length: u32, + } + + impl Settings for DefaultLanguageSettings { + fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + let content = &content.project.all_languages.defaults; + DefaultLanguageSettings { + tab_size: content.tab_size.unwrap(), + preferred_line_length: content.preferred_line_length.unwrap(), + } + } + + fn refine(&mut self, content: &SettingsContent, _: &mut App) { + let content = &content.project.all_languages.defaults; + self.tab_size.merge_from(&content.tab_size); + self.preferred_line_length + .merge_from(&content.preferred_line_length); + } + + fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { + let content = &mut content.project.all_languages.defaults; + + if let Some(size) = vscode + .read_value("editor.tabSize") + .and_then(|v| v.as_u64()) + .and_then(|n| NonZeroU32::new(n as u32)) + { + content.tab_size = Some(size); + } + } + } #[gpui::test] fn test_settings_store_basic(cx: &mut App) { - let mut store = SettingsStore::new(cx); - store.register_setting::(cx); - store.register_setting::(cx); - store.register_setting::(cx); - store - .set_default_settings( - r#"{ - "turbo": false, - "user": { - "name": "John Doe", - "age": 30, - "staff": false - } - }"#, - cx, - ) - .unwrap(); + let mut store = SettingsStore::new(cx, &default_settings()); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); - assert_eq!(store.get::(None), &TurboSetting(false)); assert_eq!( - store.get::(None), - &UserSettings { - name: "John Doe".to_string(), - age: 30, - staff: false, - } + store.get::(None), + &AutoUpdateSetting { auto_update: true } ); assert_eq!( - store.get::(None), - &MultiKeySettings { - key1: String::new(), - key2: String::new(), - } + store.get::(None).show, + TitleBarVisibility::Always ); store .set_user_settings( r#"{ - "turbo": true, - "user": { "age": 31 }, - "key1": "a" + "auto_update": false, + "title_bar": { + "show": "never" + } }"#, cx, ) .unwrap(); - assert_eq!(store.get::(None), &TurboSetting(true)); assert_eq!( - store.get::(None), - &UserSettings { - name: "John Doe".to_string(), - age: 31, - staff: false - } + store.get::(None), + &AutoUpdateSetting { auto_update: false } + ); + assert_eq!( + store.get::(None).show, + TitleBarVisibility::Never ); store @@ -1666,7 +1208,7 @@ mod tests { WorktreeId::from_usize(1), Path::new("/root1").into(), LocalSettingsKind::Settings, - Some(r#"{ "user": { "staff": true } }"#), + Some(r#"{ "tab_size": 5 }"#), cx, ) .unwrap(); @@ -1675,7 +1217,7 @@ mod tests { WorktreeId::from_usize(1), Path::new("/root1/subdir").into(), LocalSettingsKind::Settings, - Some(r#"{ "user": { "name": "Jane Doe" } }"#), + Some(r#"{ "preferred_line_length": 50 }"#), cx, ) .unwrap(); @@ -1685,108 +1227,82 @@ mod tests { WorktreeId::from_usize(1), Path::new("/root2").into(), LocalSettingsKind::Settings, - Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#), + Some(r#"{ "tab_size": 9, "title_bar": { "show_branch_name": false } }"#), cx, ) .unwrap(); assert_eq!( - store.get::(Some(SettingsLocation { + store.get::(Some(SettingsLocation { worktree_id: WorktreeId::from_usize(1), path: Path::new("/root1/something"), })), - &UserSettings { - name: "John Doe".to_string(), - age: 31, - staff: true + &DefaultLanguageSettings { + preferred_line_length: 80, + tab_size: 5.try_into().unwrap(), } ); assert_eq!( - store.get::(Some(SettingsLocation { + store.get::(Some(SettingsLocation { worktree_id: WorktreeId::from_usize(1), path: Path::new("/root1/subdir/something") })), - &UserSettings { - name: "Jane Doe".to_string(), - age: 31, - staff: true + &DefaultLanguageSettings { + preferred_line_length: 50, + tab_size: 5.try_into().unwrap(), } ); assert_eq!( - store.get::(Some(SettingsLocation { + store.get::(Some(SettingsLocation { worktree_id: WorktreeId::from_usize(1), path: Path::new("/root2/something") })), - &UserSettings { - name: "John Doe".to_string(), - age: 42, - staff: false + &DefaultLanguageSettings { + preferred_line_length: 80, + tab_size: 9.try_into().unwrap(), } ); assert_eq!( - store.get::(Some(SettingsLocation { + store.get::(Some(SettingsLocation { worktree_id: WorktreeId::from_usize(1), path: Path::new("/root2/something") })), - &MultiKeySettings { - key1: "a".to_string(), - key2: "b".to_string(), + &TitleBarSettings { + show: TitleBarVisibility::Never, + show_branch_name: true, } ); } #[gpui::test] fn test_setting_store_assign_json_before_register(cx: &mut App) { - let mut store = SettingsStore::new(cx); - store - .set_default_settings( - r#"{ - "turbo": true, - "user": { - "name": "John Doe", - "age": 30, - "staff": false - }, - "key1": "x" - }"#, - cx, - ) - .unwrap(); + let mut store = SettingsStore::new(cx, &test_settings()); store - .set_user_settings(r#"{ "turbo": false }"#, cx) + .set_user_settings(r#"{ "auto_update": false }"#, cx) .unwrap(); - store.register_setting::(cx); - store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); - assert_eq!(store.get::(None), &TurboSetting(false)); assert_eq!( - store.get::(None), - &UserSettings { - name: "John Doe".to_string(), - age: 30, - staff: false, - } + store.get::(None), + &AutoUpdateSetting { auto_update: false } ); - - store.register_setting::(cx); assert_eq!( - store.get::(None), - &MultiKeySettings { - key1: "x".into(), - key2: String::new(), - } + store.get::(None).show, + TitleBarVisibility::Always, ); } - fn check_settings_update( + #[track_caller] + fn check_settings_update( store: &mut SettingsStore, old_json: String, - update: fn(&mut T::FileContent), + update: fn(&mut SettingsContent), expected_new_json: String, cx: &mut App, ) { store.set_user_settings(&old_json, cx).ok(); - let edits = store.edits_for_update::(&old_json, update); + let edits = store.edits_for_update(&old_json, update); let mut new_json = old_json; for (range, replacement) in edits.into_iter() { new_json.replace_range(range, &replacement); @@ -1796,32 +1312,30 @@ mod tests { #[gpui::test] fn test_setting_store_update(cx: &mut App) { - let mut store = SettingsStore::new(cx); - store.register_setting::(cx); - store.register_setting::(cx); - store.register_setting::(cx); + let mut store = SettingsStore::new(cx, &test_settings()); // entries added and updated - check_settings_update::( + check_settings_update( &mut store, r#"{ "languages": { "JSON": { - "language_setting_1": true + "auto_indent": true } } }"# .unindent(), |settings| { settings - .languages + .languages_mut() .get_mut("JSON") .unwrap() - .language_setting_1 = Some(false); - settings.languages.insert( + .auto_indent = Some(false); + + settings.languages_mut().insert( "Rust".into(), - LanguageSettingEntry { - language_setting_2: Some(true), + LanguageSettingsContent { + auto_indent: Some(true), ..Default::default() }, ); @@ -1829,10 +1343,10 @@ mod tests { r#"{ "languages": { "Rust": { - "language_setting_2": true + "auto_indent": true }, "JSON": { - "language_setting_1": false + "auto_indent": false } } }"# @@ -1841,7 +1355,7 @@ mod tests { ); // entries removed - check_settings_update::( + check_settings_update( &mut store, r#"{ "languages": { @@ -1855,7 +1369,7 @@ mod tests { }"# .unindent(), |settings| { - settings.languages.remove("JSON").unwrap(); + settings.languages_mut().remove("JSON").unwrap(); }, r#"{ "languages": { @@ -1868,7 +1382,7 @@ mod tests { cx, ); - check_settings_update::( + check_settings_update( &mut store, r#"{ "languages": { @@ -1882,7 +1396,7 @@ mod tests { }"# .unindent(), |settings| { - settings.languages.remove("Rust").unwrap(); + settings.languages_mut().remove("Rust").unwrap(); }, r#"{ "languages": { @@ -1896,40 +1410,42 @@ mod tests { ); // weird formatting - check_settings_update::( + check_settings_update( &mut store, r#"{ - "user": { "age": 36, "name": "Max", "staff": true } + "title_bar": { "show": "always", "name": "Max" } }"# .unindent(), - |settings| settings.age = Some(37), + |settings| { + settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibility::Never); + }, r#"{ - "user": { "age": 37, "name": "Max", "staff": true } + "title_bar": { "show": "never", "name": "Max" } }"# .unindent(), cx, ); // single-line formatting, other keys - check_settings_update::( + check_settings_update( &mut store, - r#"{ "one": 1, "two": 2 }"#.unindent(), - |settings| settings.key1 = Some("x".into()), - r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(), + r#"{ "one": 1, "two": 2 }"#.to_owned(), + |settings| settings.auto_update = Some(true), + r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(), cx, ); // empty object - check_settings_update::( + check_settings_update( &mut store, r#"{ - "user": {} + "title_bar": {} }"# .unindent(), - |settings| settings.age = Some(37), + |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true), r#"{ - "user": { - "age": 37 + "title_bar": { + "show_menus": true } }"# .unindent(), @@ -1937,13 +1453,18 @@ mod tests { ); // no content - check_settings_update::( + check_settings_update( &mut store, r#""#.unindent(), - |settings| settings.age = Some(37), + |settings| { + settings.title_bar = Some(TitleBarSettingsContent { + show_branch_name: Some(true), + ..Default::default() + }) + }, r#"{ - "user": { - "age": 37 + "title_bar": { + "show_branch_name": true } } "# @@ -1951,16 +1472,16 @@ mod tests { cx, ); - check_settings_update::( + check_settings_update( &mut store, r#"{ } "# .unindent(), - |settings| settings.age = Some(37), + |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true), r#"{ - "user": { - "age": 37 + "title_bar": { + "show_branch_name": true } } "# @@ -1971,11 +1492,10 @@ mod tests { #[gpui::test] fn test_vscode_import(cx: &mut App) { - let mut store = SettingsStore::new(cx); - store.register_setting::(cx); - store.register_setting::(cx); - store.register_setting::(cx); - store.register_setting::(cx); + let mut store = SettingsStore::new(cx, &test_settings()); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); // create settings that werent present check_vscode_import( @@ -1984,11 +1504,9 @@ mod tests { } "# .unindent(), - r#" { "user.age": 37 } "#.to_owned(), + r#" { "editor.tabSize": 37 } "#.to_owned(), r#"{ - "user": { - "age": 37 - } + "tab_size": 37 } "# .unindent(), @@ -1999,19 +1517,14 @@ mod tests { check_vscode_import( &mut store, r#"{ - "user": { - "staff": true, - "age": 37 - } + "preferred_line_length": 99, } "# .unindent(), - r#"{ "user.age": 42 }"#.to_owned(), + r#"{ "editor.tabSize": 42 }"#.to_owned(), r#"{ - "user": { - "staff": true, - "age": 42 - } + "tab_size": 42, + "preferred_line_length": 99, } "# .unindent(), @@ -2022,19 +1535,15 @@ mod tests { check_vscode_import( &mut store, r#"{ - "user": { - "staff": true, - "age": 37 - } + "preferred_line_length": 99, + "tab_size": 42 } "# .unindent(), r#"{}"#.to_owned(), r#"{ - "user": { - "staff": true, - "age": 37 - } + "preferred_line_length": 99, + "tab_size": 42 } "# .unindent(), @@ -2045,89 +1554,25 @@ mod tests { check_vscode_import( &mut store, r#"{ - "journal": { - "hour_format": "hour12" + "title_bar": { + "show": "always" } } "# .unindent(), - r#"{ "time_format": "24" }"#.to_owned(), + r#"{ "window.titleBarStyle": "never" }"#.to_owned(), r#"{ - "journal": { - "hour_format": "hour24" + "title_bar": { + "show": "never" } } "# .unindent(), cx, ); - - // Multiple keys for one setting - check_vscode_import( - &mut store, - r#"{ - "key1": "value" - } - "# - .unindent(), - r#"{ - "key_1_first": "hello", - "key_1_second": "world" - }"# - .to_owned(), - r#"{ - "key1": "hello world" - } - "# - .unindent(), - cx, - ); - - // Merging lists together entries added and updated - check_vscode_import( - &mut store, - r#"{ - "languages": { - "JSON": { - "language_setting_1": true - }, - "Rust": { - "language_setting_2": true - } - } - }"# - .unindent(), - r#"{ - "vscode_languages": [ - { - "name": "JavaScript", - "language_setting_1": true - }, - { - "name": "Rust", - "language_setting_2": false - } - ] - }"# - .to_owned(), - r#"{ - "languages": { - "JavaScript": { - "language_setting_1": true - }, - "JSON": { - "language_setting_1": true - }, - "Rust": { - "language_setting_2": false - } - } - }"# - .unindent(), - cx, - ); } + #[track_caller] fn check_vscode_import( store: &mut SettingsStore, old: String, @@ -2143,169 +1588,43 @@ mod tests { pretty_assertions::assert_eq!(new, expected); } - #[derive(Debug, PartialEq, Deserialize, SettingsUi)] - struct UserSettings { - name: String, - age: u32, - staff: bool, - } - - #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] - #[settings_key(key = "user")] - struct UserSettingsContent { - name: Option, - age: Option, - staff: Option, - } - - impl Settings for UserSettings { - type FileContent = UserSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() - } - - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - vscode.u32_setting("user.age", &mut current.age); - } - } - - #[derive(Debug, Deserialize, PartialEq)] - struct TurboSetting(bool); - - #[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Default, - serde::Serialize, - serde::Deserialize, - SettingsUi, - SettingsKey, - JsonSchema, - )] - #[serde(default)] - #[settings_key(None)] - pub struct TurboSettingContent { - turbo: Option, - } - - impl Settings for TurboSetting { - type FileContent = TurboSettingContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - Ok(Self( - sources - .user - .or(sources.server) - .unwrap_or(sources.default) - .turbo - .unwrap_or_default(), - )) - } - - fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {} - } - - #[derive(Clone, Debug, PartialEq, Deserialize)] - struct MultiKeySettings { - #[serde(default)] - key1: String, - #[serde(default)] - key2: String, - } - - #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] - #[settings_key(None)] - struct MultiKeySettingsJson { - key1: Option, - key2: Option, - } - - impl Settings for MultiKeySettings { - type FileContent = MultiKeySettingsJson; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() - } - - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - let first_value = vscode.read_string("key_1_first"); - let second_value = vscode.read_string("key_1_second"); - - if let Some((first, second)) = first_value.zip(second_value) { - current.key1 = Some(format!("{} {}", first, second)); + #[gpui::test] + fn test_update_git_settings(cx: &mut App) { + let store = SettingsStore::new(cx, &test_settings()); + + let actual = store.new_text_for_update("{}".to_string(), |current| { + current + .git + .get_or_insert_default() + .inline_blame + .get_or_insert_default() + .enabled = Some(true); + }); + assert_eq!( + actual, + r#"{ + "git": { + "inline_blame": { + "enabled": true + } } } - } - - #[derive(Debug, Deserialize)] - struct JournalSettings { - #[expect(unused)] - pub path: String, - #[expect(unused)] - pub hour_format: HourFormat, - } - - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(rename_all = "snake_case")] - enum HourFormat { - Hour12, - Hour24, - } - - #[derive( - Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey, - )] - #[settings_key(key = "journal")] - struct JournalSettingsJson { - pub path: Option, - pub hour_format: Option, - } - - impl Settings for JournalSettings { - type FileContent = JournalSettingsJson; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() - } - - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - vscode.enum_setting("time_format", &mut current.hour_format, |s| match s { - "12" => Some(HourFormat::Hour12), - "24" => Some(HourFormat::Hour24), - _ => None, - }); - } + "# + .unindent() + ); } #[gpui::test] fn test_global_settings(cx: &mut App) { - let mut store = SettingsStore::new(cx); - store.register_setting::(cx); - store - .set_default_settings( - r#"{ - "user": { - "name": "John Doe", - "age": 30, - "staff": false - } - }"#, - cx, - ) - .unwrap(); + let mut store = SettingsStore::new(cx, &test_settings()); + store.register_setting::(cx); // Set global settings - these should override defaults but not user settings store .set_global_settings( r#"{ - "user": { - "name": "Global User", - "age": 35, - "staff": true + "title_bar": { + "show": "never", } }"#, cx, @@ -2314,11 +1633,10 @@ mod tests { // Before user settings, global settings should apply assert_eq!( - store.get::(None), - &UserSettings { - name: "Global User".to_string(), - age: 35, - staff: true, + store.get::(None), + &TitleBarSettings { + show: TitleBarVisibility::Never, + show_branch_name: true, } ); @@ -2326,8 +1644,8 @@ mod tests { store .set_user_settings( r#"{ - "user": { - "age": 40 + "title_bar": { + "show": "always" } }"#, cx, @@ -2336,60 +1654,11 @@ mod tests { // User settings should override global settings assert_eq!( - store.get::(None), - &UserSettings { - name: "Global User".to_string(), // Name from global settings - age: 40, // Age from user settings - staff: true, // Staff from global settings + store.get::(None), + &TitleBarSettings { + show: TitleBarVisibility::Always, + show_branch_name: true, // Staff from global settings } ); } - - #[derive( - Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey, - )] - #[settings_key(None)] - struct LanguageSettings { - #[serde(default)] - languages: HashMap, - } - - #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] - struct LanguageSettingEntry { - language_setting_1: Option, - language_setting_2: Option, - } - - impl Settings for LanguageSettings { - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() - } - - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - current.languages.extend( - vscode - .read_value("vscode_languages") - .and_then(|value| value.as_array()) - .map(|languages| { - languages - .iter() - .filter_map(|value| value.as_object()) - .filter_map(|item| { - let mut rest = item.clone(); - let name = rest.remove("name")?.as_str()?.to_string(); - let entry = serde_json::from_value::( - serde_json::Value::Object(rest), - ) - .ok()?; - - Some((name, entry)) - }) - }) - .into_iter() - .flatten(), - ); - } - } } diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 53fbf797c3d9e56e49b1d96e7dabcac19ddde8e2..85e9cfacf290dcbe4b63768e990a44203f9aa2a9 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -78,12 +78,7 @@ impl VsCodeSettings { } pub fn read_value(&self, setting: &str) -> Option<&Value> { - if let Some(value) = self.content.get(setting) { - return Some(value); - } - // TODO: maybe check if it's in [platform] settings for current platform as a fallback - // TODO: deal with language specific settings - None + self.content.get(setting) } pub fn read_string(&self, setting: &str) -> Option<&str> { @@ -140,4 +135,8 @@ impl VsCodeSettings { *setting = Some(s) } } + + pub fn read_enum(&self, key: &str, f: impl FnOnce(&str) -> Option) -> Option { + self.content.get(key).and_then(Value::as_str).and_then(f) + } } diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml deleted file mode 100644 index 1d8e5e11226103e2ef7778816f5c1b41cd934b05..0000000000000000000000000000000000000000 --- a/crates/settings_ui/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "settings_ui" -version = "0.1.0" -edition.workspace = true -publish.workspace = true -license = "GPL-3.0-or-later" - -[lints] -workspace = true - -[lib] -path = "src/settings_ui.rs" - -[features] -default = [] -test-support = [] - -[dependencies] -anyhow.workspace = true -command_palette_hooks.workspace = true -editor.workspace = true -feature_flags.workspace = true -gpui.workspace = true -menu.workspace = true -serde.workspace = true -serde_json.workspace = true -settings.workspace = true -smallvec.workspace = true -theme.workspace = true -ui.workspace = true -workspace.workspace = true -workspace-hack.workspace = true - - -[dev-dependencies] -debugger_ui.workspace = true - -# Uncomment other workspace dependencies as needed -# assistant.workspace = true -# client.workspace = true -# project.workspace = true -# settings.workspace = true diff --git a/crates/settings_ui/LICENSE-GPL b/crates/settings_ui/LICENSE-GPL deleted file mode 120000 index 89e542f750cd3860a0598eff0dc34b56d7336dc4..0000000000000000000000000000000000000000 --- a/crates/settings_ui/LICENSE-GPL +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-GPL \ No newline at end of file diff --git a/crates/settings_ui/src/appearance_settings_controls.rs b/crates/settings_ui/src/appearance_settings_controls.rs deleted file mode 100644 index 255f5a36b5868b3fd36085e8c980eb7a17fdc163..0000000000000000000000000000000000000000 --- a/crates/settings_ui/src/appearance_settings_controls.rs +++ /dev/null @@ -1,387 +0,0 @@ -use std::sync::Arc; - -use gpui::{App, FontFeatures, FontWeight}; -use settings::{EditableSettingControl, Settings}; -use theme::{ - FontFamilyCache, FontFamilyName, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings, -}; -use ui::{ - CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, - ToggleButton, prelude::*, -}; - -#[derive(IntoElement)] -pub struct AppearanceSettingsControls {} - -impl AppearanceSettingsControls { - pub fn new() -> Self { - Self {} - } -} - -impl RenderOnce for AppearanceSettingsControls { - fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { - SettingsContainer::new() - .child( - SettingsGroup::new("Theme").child( - h_flex() - .gap_2() - .justify_between() - .child(ThemeControl) - .child(ThemeModeControl), - ), - ) - .child( - SettingsGroup::new("Font") - .child( - h_flex() - .gap_2() - .justify_between() - .child(UiFontFamilyControl) - .child(UiFontWeightControl), - ) - .child(UiFontSizeControl) - .child(UiFontLigaturesControl), - ) - } -} - -#[derive(IntoElement)] -struct ThemeControl; - -impl EditableSettingControl for ThemeControl { - type Value = String; - type Settings = ThemeSettings; - - fn name(&self) -> SharedString { - "Theme".into() - } - - fn read(cx: &App) -> Self::Value { - let settings = ThemeSettings::get_global(cx); - let appearance = SystemAppearance::global(cx); - settings - .theme_selection - .as_ref() - .map(|selection| selection.theme(appearance.0).to_string()) - .unwrap_or_else(|| ThemeSettings::default_theme(*appearance).to_string()) - } - - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - cx: &App, - ) { - let appearance = SystemAppearance::global(cx); - settings.set_theme(value, appearance.0); - } -} - -impl RenderOnce for ThemeControl { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let value = Self::read(cx); - - DropdownMenu::new( - "theme", - value, - ContextMenu::build(window, cx, |mut menu, _, cx| { - let theme_registry = ThemeRegistry::global(cx); - - for theme in theme_registry.list_names() { - menu = menu.custom_entry( - { - let theme = theme.clone(); - move |_window, _cx| Label::new(theme.clone()).into_any_element() - }, - { - let theme = theme.clone(); - move |_window, cx| { - Self::write(theme.to_string(), cx); - } - }, - ) - } - - menu - }), - ) - .full_width(true) - } -} - -#[derive(IntoElement)] -struct ThemeModeControl; - -impl EditableSettingControl for ThemeModeControl { - type Value = ThemeMode; - type Settings = ThemeSettings; - - fn name(&self) -> SharedString { - "Theme Mode".into() - } - - fn read(cx: &App) -> Self::Value { - let settings = ThemeSettings::get_global(cx); - settings - .theme_selection - .as_ref() - .and_then(|selection| selection.mode()) - .unwrap_or_default() - } - - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.set_mode(value); - } -} - -impl RenderOnce for ThemeModeControl { - fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let value = Self::read(cx); - - h_flex() - .child( - ToggleButton::new("light", "Light") - .style(ButtonStyle::Filled) - .size(ButtonSize::Large) - .toggle_state(value == ThemeMode::Light) - .on_click(|_, _, cx| Self::write(ThemeMode::Light, cx)) - .first(), - ) - .child( - ToggleButton::new("system", "System") - .style(ButtonStyle::Filled) - .size(ButtonSize::Large) - .toggle_state(value == ThemeMode::System) - .on_click(|_, _, cx| Self::write(ThemeMode::System, cx)) - .middle(), - ) - .child( - ToggleButton::new("dark", "Dark") - .style(ButtonStyle::Filled) - .size(ButtonSize::Large) - .toggle_state(value == ThemeMode::Dark) - .on_click(|_, _, cx| Self::write(ThemeMode::Dark, cx)) - .last(), - ) - } -} - -#[derive(IntoElement)] -struct UiFontFamilyControl; - -impl EditableSettingControl for UiFontFamilyControl { - type Value = SharedString; - type Settings = ThemeSettings; - - fn name(&self) -> SharedString { - "UI Font Family".into() - } - - fn read(cx: &App) -> Self::Value { - let settings = ThemeSettings::get_global(cx); - settings.ui_font.family.clone() - } - - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.ui_font_family = Some(FontFamilyName(value.into())); - } -} - -impl RenderOnce for UiFontFamilyControl { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let value = Self::read(cx); - - h_flex() - .gap_2() - .child(Icon::new(IconName::Font)) - .child(DropdownMenu::new( - "ui-font-family", - value, - ContextMenu::build(window, cx, |mut menu, _, cx| { - let font_family_cache = FontFamilyCache::global(cx); - - for font_name in font_family_cache.list_font_families(cx) { - menu = menu.custom_entry( - { - let font_name = font_name.clone(); - move |_window, _cx| Label::new(font_name.clone()).into_any_element() - }, - { - let font_name = font_name.clone(); - move |_window, cx| { - Self::write(font_name.clone(), cx); - } - }, - ) - } - - menu - }), - )) - } -} - -#[derive(IntoElement)] -struct UiFontSizeControl; - -impl EditableSettingControl for UiFontSizeControl { - type Value = Pixels; - type Settings = ThemeSettings; - - fn name(&self) -> SharedString { - "UI Font Size".into() - } - - fn read(cx: &App) -> Self::Value { - ThemeSettings::get_global(cx).ui_font_size(cx) - } - - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.ui_font_size = Some(value.into()); - } -} - -impl RenderOnce for UiFontSizeControl { - fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let value = Self::read(cx); - - h_flex() - .gap_2() - .child(Icon::new(IconName::FontSize)) - .child(NumericStepper::new( - "ui-font-size", - value.to_string(), - move |_, _, cx| { - Self::write(value - px(1.), cx); - }, - move |_, _, cx| { - Self::write(value + px(1.), cx); - }, - )) - } -} - -#[derive(IntoElement)] -struct UiFontWeightControl; - -impl EditableSettingControl for UiFontWeightControl { - type Value = FontWeight; - type Settings = ThemeSettings; - - fn name(&self) -> SharedString { - "UI Font Weight".into() - } - - fn read(cx: &App) -> Self::Value { - let settings = ThemeSettings::get_global(cx); - settings.ui_font.weight - } - - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - settings.ui_font_weight = Some(value.0); - } -} - -impl RenderOnce for UiFontWeightControl { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let value = Self::read(cx); - - h_flex() - .gap_2() - .child(Icon::new(IconName::FontWeight)) - .child(DropdownMenu::new( - "ui-font-weight", - value.0.to_string(), - ContextMenu::build(window, cx, |mut menu, _window, _cx| { - for weight in FontWeight::ALL { - menu = menu.custom_entry( - move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(), - { - move |_window, cx| { - Self::write(weight, cx); - } - }, - ) - } - - menu - }), - )) - } -} - -#[derive(IntoElement)] -struct UiFontLigaturesControl; - -impl EditableSettingControl for UiFontLigaturesControl { - type Value = bool; - type Settings = ThemeSettings; - - fn name(&self) -> SharedString { - "UI Font Ligatures".into() - } - - fn read(cx: &App) -> Self::Value { - let settings = ThemeSettings::get_global(cx); - settings.ui_font.features.is_calt_enabled().unwrap_or(true) - } - - fn apply( - settings: &mut ::FileContent, - value: Self::Value, - _cx: &App, - ) { - let value = if value { 1 } else { 0 }; - - let mut features = settings - .ui_font_features - .as_ref() - .map(|features| features.tag_value_list().to_vec()) - .unwrap_or_default(); - - if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") { - features[calt_index].1 = value; - } else { - features.push(("calt".into(), value)); - } - - settings.ui_font_features = Some(FontFeatures(Arc::new(features))); - } -} - -impl RenderOnce for UiFontLigaturesControl { - fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let value = Self::read(cx); - - CheckboxWithLabel::new( - "ui-font-ligatures", - Label::new(self.name()), - value.into(), - |selection, _, cx| { - Self::write( - match selection { - ToggleState::Selected => true, - ToggleState::Unselected | ToggleState::Indeterminate => false, - }, - cx, - ); - }, - ) - } -} diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs deleted file mode 100644 index 6d79b838743bb23f1bce7b409360f04929431df6..0000000000000000000000000000000000000000 --- a/crates/settings_ui/src/settings_ui.rs +++ /dev/null @@ -1,1017 +0,0 @@ -mod appearance_settings_controls; - -use std::{ - num::NonZeroU32, - ops::{Not, Range}, - rc::Rc, -}; - -use anyhow::Context as _; -use editor::{Editor, EditorSettingsControls}; -use feature_flags::{FeatureFlag, FeatureFlagAppExt}; -use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, ReadGlobal, ScrollHandle, actions}; -use settings::{ - NumType, SettingsStore, SettingsUiEntry, SettingsUiEntryMetaData, SettingsUiItem, - SettingsUiItemDynamicMap, SettingsUiItemGroup, SettingsUiItemSingle, SettingsUiItemUnion, - SettingsValue, -}; -use smallvec::SmallVec; -use ui::{ - ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup, ToggleButtonSimple, - prelude::*, -}; -use workspace::{ - Workspace, - item::{Item, ItemEvent}, -}; - -use crate::appearance_settings_controls::AppearanceSettingsControls; - -pub struct SettingsUiFeatureFlag; - -impl FeatureFlag for SettingsUiFeatureFlag { - const NAME: &'static str = "settings-ui"; -} - -actions!( - zed, - [ - /// Opens settings UI. - OpenSettingsUi - ] -); - -pub fn open_settings_editor( - workspace: &mut Workspace, - _: &OpenSettingsUi, - window: &mut Window, - cx: &mut Context, -) { - // todo(settings_ui) open in a local workspace if this is remote. - let existing = workspace - .active_pane() - .read(cx) - .items() - .find_map(|item| item.downcast::()); - - if let Some(existing) = existing { - workspace.activate_item(&existing, true, true, window, cx); - } else { - let settings_page = SettingsPage::new(workspace, cx); - workspace.add_item_to_active_pane(Box::new(settings_page), None, true, window, cx) - } -} - -pub fn init(cx: &mut App) { - cx.observe_new(|workspace: &mut Workspace, _, _| { - workspace.register_action_renderer(|div, _, _, cx| { - let settings_ui_actions = [std::any::TypeId::of::()]; - let has_flag = cx.has_flag::(); - command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _| { - if has_flag { - filter.show_action_types(&settings_ui_actions); - } else { - filter.hide_action_types(&settings_ui_actions); - } - }); - if has_flag { - div.on_action(cx.listener(open_settings_editor)) - } else { - div - } - }); - }) - .detach(); -} - -pub struct SettingsPage { - focus_handle: FocusHandle, - settings_tree: SettingsUiTree, -} - -impl SettingsPage { - pub fn new(_workspace: &Workspace, cx: &mut Context) -> Entity { - cx.new(|cx| Self { - focus_handle: cx.focus_handle(), - settings_tree: SettingsUiTree::new(cx), - }) - } -} - -impl EventEmitter for SettingsPage {} - -impl Focusable for SettingsPage { - fn focus_handle(&self, _cx: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Item for SettingsPage { - type Event = ItemEvent; - - fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { - Some(Icon::new(IconName::Settings)) - } - - fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { - "Settings".into() - } - - fn show_toolbar(&self) -> bool { - false - } - - fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) { - f(*event) - } -} - -// We want to iterate over the side bar with root groups -// - this is a loop over top level groups, and if any are expanded, recursively displaying their items -// - Should be able to get all items from a group (flatten a group) -// - Should be able to toggle/untoggle groups in UI (at least in sidebar) -// - Search should be available -// - there should be an index of text -> item mappings, for using fuzzy::match -// - Do we want to show the parent groups when a item is matched? - -struct UiEntry { - title: SharedString, - path: Option, - documentation: Option, - _depth: usize, - // a - // b < a descendant range < a total descendant range - // f | | - // g | | - // c < | - // d | - // e < - descendant_range: Range, - total_descendant_range: Range, - next_sibling: Option, - // expanded: bool, - render: Option, - dynamic_render: Option, - generate_items: Option<( - SettingsUiItem, - fn(&serde_json::Value, &App) -> Vec, - SmallVec<[SharedString; 1]>, - )>, -} - -impl UiEntry { - fn first_descendant_index(&self) -> Option { - return self - .descendant_range - .is_empty() - .not() - .then_some(self.descendant_range.start); - } - - fn nth_descendant_index(&self, tree: &[UiEntry], n: usize) -> Option { - let first_descendant_index = self.first_descendant_index()?; - let mut current_index = 0; - let mut current_descendant_index = Some(first_descendant_index); - while let Some(descendant_index) = current_descendant_index - && current_index < n - { - current_index += 1; - current_descendant_index = tree[descendant_index].next_sibling; - } - current_descendant_index - } -} - -pub struct SettingsUiTree { - root_entry_indices: Vec, - entries: Vec, - active_entry_index: usize, -} - -fn build_tree_item( - tree: &mut Vec, - entry: SettingsUiEntry, - depth: usize, - prev_index: Option, -) { - // let tree: HashMap; - let index = tree.len(); - tree.push(UiEntry { - title: entry.title.into(), - path: entry.path.map(SharedString::new_static), - documentation: entry.documentation.map(SharedString::new_static), - _depth: depth, - descendant_range: index + 1..index + 1, - total_descendant_range: index + 1..index + 1, - render: None, - next_sibling: None, - dynamic_render: None, - generate_items: None, - }); - if let Some(prev_index) = prev_index { - tree[prev_index].next_sibling = Some(index); - } - match entry.item { - SettingsUiItem::Group(SettingsUiItemGroup { items: group_items }) => { - for group_item in group_items { - let prev_index = tree[index] - .descendant_range - .is_empty() - .not() - .then_some(tree[index].descendant_range.end - 1); - tree[index].descendant_range.end = tree.len() + 1; - build_tree_item(tree, group_item, depth + 1, prev_index); - tree[index].total_descendant_range.end = tree.len(); - } - } - SettingsUiItem::Single(item) => { - tree[index].render = Some(item); - } - SettingsUiItem::Union(dynamic_render) => { - // todo(settings_ui) take from item and store other fields instead of clone - // will also require replacing usage in render_recursive so it can know - // which options were actually rendered - let options = dynamic_render.options.clone(); - tree[index].dynamic_render = Some(dynamic_render); - for option in options { - let Some(option) = option else { continue }; - let prev_index = tree[index] - .descendant_range - .is_empty() - .not() - .then_some(tree[index].descendant_range.end - 1); - tree[index].descendant_range.end = tree.len() + 1; - build_tree_item(tree, option, depth + 1, prev_index); - tree[index].total_descendant_range.end = tree.len(); - } - } - SettingsUiItem::DynamicMap(SettingsUiItemDynamicMap { - item: generate_settings_ui_item, - determine_items, - defaults_path, - }) => { - tree[index].generate_items = Some(( - generate_settings_ui_item(), - determine_items, - defaults_path - .into_iter() - .copied() - .map(SharedString::new_static) - .collect(), - )); - } - SettingsUiItem::None => { - return; - } - } -} - -impl SettingsUiTree { - pub fn new(cx: &App) -> Self { - let settings_store = SettingsStore::global(cx); - let mut tree = vec![]; - let mut root_entry_indices = vec![]; - for item in settings_store.settings_ui_items() { - if matches!(item.item, SettingsUiItem::None) - // todo(settings_ui): How to handle top level single items? BaseKeymap is in this category. Probably need a way to - // link them to other groups - || matches!(item.item, SettingsUiItem::Single(_)) - { - continue; - } - - let prev_root_entry_index = root_entry_indices.last().copied(); - root_entry_indices.push(tree.len()); - build_tree_item(&mut tree, item, 0, prev_root_entry_index); - } - - root_entry_indices.sort_by_key(|i| &tree[*i].title); - - let active_entry_index = root_entry_indices[0]; - Self { - entries: tree, - root_entry_indices, - active_entry_index, - } - } - - // todo(settings_ui): Make sure `Item::None` paths are added to the paths tree, - // so that we can keep none/skip and still test in CI that all settings have - #[cfg(feature = "test-support")] - pub fn all_paths(&self, cx: &App) -> Vec> { - fn all_paths_rec( - tree: &[UiEntry], - paths: &mut Vec>, - current_path: &mut Vec, - idx: usize, - cx: &App, - ) { - let child = &tree[idx]; - let mut pushed_path = false; - if let Some(path) = child.path.as_ref() { - current_path.push(path.clone()); - paths.push(current_path.clone()); - pushed_path = true; - } - // todo(settings_ui): handle dynamic nodes here - let selected_descendant_index = child - .dynamic_render - .as_ref() - .map(|dynamic_render| { - read_settings_value_from_path( - SettingsStore::global(cx).raw_default_settings(), - ¤t_path, - ) - .map(|value| (dynamic_render.determine_option)(value, cx)) - }) - .and_then(|selected_descendant_index| { - selected_descendant_index.map(|index| child.nth_descendant_index(tree, index)) - }); - - if let Some(selected_descendant_index) = selected_descendant_index { - // just silently fail if we didn't find a setting value for the path - if let Some(descendant_index) = selected_descendant_index { - all_paths_rec(tree, paths, current_path, descendant_index, cx); - } - } else if let Some(desc_idx) = child.first_descendant_index() { - let mut desc_idx = Some(desc_idx); - while let Some(descendant_index) = desc_idx { - all_paths_rec(&tree, paths, current_path, descendant_index, cx); - desc_idx = tree[descendant_index].next_sibling; - } - } - if pushed_path { - current_path.pop(); - } - } - - let mut paths = Vec::new(); - for &index in &self.root_entry_indices { - all_paths_rec(&self.entries, &mut paths, &mut Vec::new(), index, cx); - } - paths - } -} - -fn render_nav(tree: &SettingsUiTree, _window: &mut Window, cx: &mut Context) -> Div { - let mut nav = v_flex().p_4().gap_2(); - for &index in &tree.root_entry_indices { - nav = nav.child( - div() - .id(index) - .on_click(cx.listener(move |settings, _, _, _| { - settings.settings_tree.active_entry_index = index; - })) - .child( - Label::new(tree.entries[index].title.clone()) - .size(LabelSize::Large) - .when(tree.active_entry_index == index, |this| { - this.color(Color::Selected) - }), - ), - ); - } - nav -} - -fn render_content( - tree: &SettingsUiTree, - window: &mut Window, - cx: &mut Context, -) -> Div { - let content = v_flex().size_full().gap_4(); - - let mut path = smallvec::smallvec![]; - - return render_recursive( - &tree.entries, - tree.active_entry_index, - &mut path, - content, - &mut None, - true, - window, - cx, - ); -} - -fn render_recursive( - tree: &[UiEntry], - index: usize, - path: &mut SmallVec<[SharedString; 1]>, - mut element: Div, - fallback_path: &mut Option>, - render_next_title: bool, - window: &mut Window, - cx: &mut App, -) -> Div { - let Some(child) = tree.get(index) else { - return element - .child(Label::new(SharedString::new_static("No settings found")).color(Color::Error)); - }; - - if render_next_title { - element = element.child(Label::new(child.title.clone()).size(LabelSize::Large)); - } - - // todo(settings_ui): subgroups? - let mut pushed_path = false; - if let Some(child_path) = child.path.as_ref() { - path.push(child_path.clone()); - if let Some(fallback_path) = fallback_path.as_mut() { - fallback_path.push(child_path.clone()); - } - pushed_path = true; - } - let settings_value = settings_value_from_settings_and_path( - path.clone(), - fallback_path.as_ref().map(|path| path.as_slice()), - child.title.clone(), - child.documentation.clone(), - // PERF: how to structure this better? There feels like there's a way to avoid the clone - // and every value lookup - SettingsStore::global(cx).raw_user_settings(), - SettingsStore::global(cx).raw_default_settings(), - ); - if let Some(dynamic_render) = child.dynamic_render.as_ref() { - let value = settings_value.read(); - let selected_index = (dynamic_render.determine_option)(value, cx); - element = element.child(div().child(render_toggle_button_group_inner( - settings_value.title.clone(), - dynamic_render.labels, - Some(selected_index), - { - let path = settings_value.path.clone(); - let defaults = dynamic_render.defaults.clone(); - move |idx, cx| { - if idx == selected_index { - return; - } - let default = defaults.get(idx).cloned().unwrap_or_default(); - SettingsValue::write_value(&path, default, cx); - } - }, - ))); - // we don't add descendants for unit options, so we adjust the selected index - // by the number of options we didn't add descendants for, to get the descendant index - let selected_descendant_index = selected_index - - dynamic_render.options[..selected_index] - .iter() - .filter(|option| option.is_none()) - .count(); - if dynamic_render.options[selected_index].is_some() - && let Some(descendant_index) = - child.nth_descendant_index(tree, selected_descendant_index) - { - element = render_recursive( - tree, - descendant_index, - path, - element, - fallback_path, - false, - window, - cx, - ); - } - } else if let Some((settings_ui_item, generate_items, defaults_path)) = - child.generate_items.as_ref() - { - let generated_items = generate_items(settings_value.read(), cx); - let mut ui_items = Vec::with_capacity(generated_items.len()); - for item in generated_items { - let settings_ui_entry = SettingsUiEntry { - path: None, - title: "", - documentation: None, - item: settings_ui_item.clone(), - }; - let prev_index = if ui_items.is_empty() { - None - } else { - Some(ui_items.len() - 1) - }; - let item_index = ui_items.len(); - build_tree_item( - &mut ui_items, - settings_ui_entry, - child._depth + 1, - prev_index, - ); - if item_index < ui_items.len() { - ui_items[item_index].path = None; - ui_items[item_index].title = item.title.clone(); - ui_items[item_index].documentation = item.documentation.clone(); - - // push path instead of setting path on ui item so that the path isn't pushed to default_path as well - // when we recurse - path.push(item.path.clone()); - element = render_recursive( - &ui_items, - item_index, - path, - element, - &mut Some(defaults_path.clone()), - true, - window, - cx, - ); - path.pop(); - } - } - } else if let Some(child_render) = child.render.as_ref() { - element = element.child(div().child(render_item_single( - settings_value, - child_render, - window, - cx, - ))); - } else if let Some(child_index) = child.first_descendant_index() { - let mut index = Some(child_index); - while let Some(sub_child_index) = index { - element = render_recursive( - tree, - sub_child_index, - path, - element, - fallback_path, - true, - window, - cx, - ); - index = tree[sub_child_index].next_sibling; - } - } else { - element = element.child(div().child(Label::new("// skipped (for now)").color(Color::Muted))) - } - - if pushed_path { - path.pop(); - if let Some(fallback_path) = fallback_path.as_mut() { - fallback_path.pop(); - } - } - return element; -} - -impl Render for SettingsPage { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let scroll_handle = window.use_state(cx, |_, _| ScrollHandle::new()); - div() - .grid() - .grid_cols(16) - .p_4() - .bg(cx.theme().colors().editor_background) - .size_full() - .child( - div() - .id("settings-ui-nav") - .col_span(2) - .h_full() - .child(render_nav(&self.settings_tree, window, cx)), - ) - .child( - div().col_span(6).h_full().child( - render_content(&self.settings_tree, window, cx) - .id("settings-ui-content") - .track_scroll(scroll_handle.read(cx)) - .overflow_y_scroll(), - ), - ) - } -} - -// todo(settings_ui): remove, only here as inspiration -#[allow(dead_code)] -fn render_old_appearance_settings(cx: &mut App) -> impl IntoElement { - v_flex() - .p_4() - .size_full() - .gap_4() - .child(Label::new("Settings").size(LabelSize::Large)) - .child( - v_flex().gap_1().child(Label::new("Appearance")).child( - v_flex() - .elevation_2(cx) - .child(AppearanceSettingsControls::new()), - ), - ) - .child( - v_flex().gap_1().child(Label::new("Editor")).child( - v_flex() - .elevation_2(cx) - .child(EditorSettingsControls::new()), - ), - ) -} - -fn element_id_from_path(path: &[SharedString]) -> ElementId { - if path.len() == 0 { - panic!("Path length must not be zero"); - } else if path.len() == 1 { - ElementId::Name(path[0].clone()) - } else { - ElementId::from(( - ElementId::from(path[path.len() - 2].clone()), - path[path.len() - 1].clone(), - )) - } -} - -fn render_item_single( - settings_value: SettingsValue, - item: &SettingsUiItemSingle, - window: &mut Window, - cx: &mut App, -) -> AnyElement { - match item { - SettingsUiItemSingle::Custom(_) => div() - .child(format!("Item: {}", settings_value.path.join("."))) - .into_any_element(), - SettingsUiItemSingle::SwitchField => { - render_any_item(settings_value, render_switch_field, window, cx) - } - SettingsUiItemSingle::NumericStepper(num_type) => { - render_any_numeric_stepper(settings_value, *num_type, window, cx) - } - SettingsUiItemSingle::ToggleGroup { - variants: values, - labels: titles, - } => render_toggle_button_group(settings_value, values, titles, window, cx), - SettingsUiItemSingle::DropDown { variants, labels } => { - render_dropdown(settings_value, variants, labels, window, cx) - } - SettingsUiItemSingle::TextField => render_text_field(settings_value, window, cx), - } -} - -pub fn read_settings_value_from_path<'a>( - settings_contents: &'a serde_json::Value, - path: &[impl AsRef], -) -> Option<&'a serde_json::Value> { - // todo(settings_ui) make non recursive, and move to `settings` alongside SettingsValue, and add method to SettingsValue to get nested - let Some((key, remaining)) = path.split_first() else { - return Some(settings_contents); - }; - let Some(value) = settings_contents.get(key.as_ref()) else { - return None; - }; - - read_settings_value_from_path(value, remaining) -} - -fn downcast_any_item( - settings_value: SettingsValue, -) -> SettingsValue { - let value = settings_value.value.map(|value| { - serde_json::from_value::(value.clone()) - .with_context(|| format!("path: {:?}", settings_value.path.join("."))) - .with_context(|| format!("value is not a {}: {}", std::any::type_name::(), value)) - .unwrap() - }); - // todo(settings_ui) Create test that constructs UI tree, and asserts that all elements have default values - let default_value = serde_json::from_value::(settings_value.default_value) - .with_context(|| format!("path: {:?}", settings_value.path.join("."))) - .with_context(|| format!("value is not a {}", std::any::type_name::())) - .unwrap(); - let deserialized_setting_value = SettingsValue { - title: settings_value.title, - path: settings_value.path, - documentation: settings_value.documentation, - value, - default_value, - }; - deserialized_setting_value -} - -fn render_any_item( - settings_value: SettingsValue, - render_fn: impl Fn(SettingsValue, &mut Window, &mut App) -> AnyElement + 'static, - window: &mut Window, - cx: &mut App, -) -> AnyElement { - let deserialized_setting_value = downcast_any_item(settings_value); - render_fn(deserialized_setting_value, window, cx) -} - -fn render_any_numeric_stepper( - settings_value: SettingsValue, - num_type: NumType, - window: &mut Window, - cx: &mut App, -) -> AnyElement { - match num_type { - NumType::U64 => render_numeric_stepper::( - downcast_any_item(settings_value), - |n| u64::saturating_sub(n, 1), - |n| u64::saturating_add(n, 1), - |n| { - serde_json::Number::try_from(n) - .context("Failed to convert u64 to serde_json::Number") - }, - window, - cx, - ), - NumType::U32 => render_numeric_stepper::( - downcast_any_item(settings_value), - |n| u32::saturating_sub(n, 1), - |n| u32::saturating_add(n, 1), - |n| { - serde_json::Number::try_from(n) - .context("Failed to convert u32 to serde_json::Number") - }, - window, - cx, - ), - NumType::F32 => render_numeric_stepper::( - downcast_any_item(settings_value), - |a| a - 1.0, - |a| a + 1.0, - |n| { - serde_json::Number::from_f64(n as f64) - .context("Failed to convert f32 to serde_json::Number") - }, - window, - cx, - ), - NumType::USIZE => render_numeric_stepper::( - downcast_any_item(settings_value), - |n| usize::saturating_sub(n, 1), - |n| usize::saturating_add(n, 1), - |n| { - serde_json::Number::try_from(n) - .context("Failed to convert usize to serde_json::Number") - }, - window, - cx, - ), - NumType::U32NONZERO => render_numeric_stepper::( - downcast_any_item(settings_value), - |a| NonZeroU32::new(u32::saturating_sub(a.get(), 1)).unwrap_or(NonZeroU32::MIN), - |a| NonZeroU32::new(u32::saturating_add(a.get(), 1)).unwrap_or(NonZeroU32::MAX), - |n| { - serde_json::Number::try_from(n.get()) - .context("Failed to convert usize to serde_json::Number") - }, - window, - cx, - ), - } -} - -fn render_numeric_stepper( - value: SettingsValue, - saturating_sub_1: fn(T) -> T, - saturating_add_1: fn(T) -> T, - to_serde_number: fn(T) -> anyhow::Result, - _window: &mut Window, - _cx: &mut App, -) -> AnyElement { - let id = element_id_from_path(&value.path); - let path = value.path.clone(); - let num = *value.read(); - - NumericStepper::new( - id, - num.to_string(), - { - let path = value.path; - move |_, _, cx| { - let Some(number) = to_serde_number(saturating_sub_1(num)).ok() else { - return; - }; - let new_value = serde_json::Value::Number(number); - SettingsValue::write_value(&path, new_value, cx); - } - }, - move |_, _, cx| { - let Some(number) = to_serde_number(saturating_add_1(num)).ok() else { - return; - }; - - let new_value = serde_json::Value::Number(number); - - SettingsValue::write_value(&path, new_value, cx); - }, - ) - .style(ui::NumericStepperStyle::Outlined) - .into_any_element() -} - -fn render_switch_field( - value: SettingsValue, - _window: &mut Window, - _cx: &mut App, -) -> AnyElement { - let id = element_id_from_path(&value.path); - let path = value.path.clone(); - SwitchField::new( - id, - value.title.clone(), - value.documentation.clone(), - match value.read() { - true => ToggleState::Selected, - false => ToggleState::Unselected, - }, - move |toggle_state, _, cx| { - let new_value = serde_json::Value::Bool(match toggle_state { - ToggleState::Indeterminate => { - return; - } - ToggleState::Selected => true, - ToggleState::Unselected => false, - }); - - SettingsValue::write_value(&path, new_value, cx); - }, - ) - .into_any_element() -} - -fn render_text_field( - value: SettingsValue, - window: &mut Window, - cx: &mut App, -) -> AnyElement { - let value = downcast_any_item::(value); - let path = value.path.clone(); - let editor = window.use_state(cx, { - let path = path.clone(); - move |window, cx| { - let mut editor = Editor::single_line(window, cx); - - cx.observe_global_in::(window, move |editor, window, cx| { - let user_settings = SettingsStore::global(cx).raw_user_settings(); - if let Some(value) = read_settings_value_from_path(&user_settings, &path).cloned() - && let Some(value) = value.as_str() - { - editor.set_text(value, window, cx); - } - }) - .detach(); - - editor.set_text(value.read().clone(), window, cx); - editor - } - }); - - let weak_editor = editor.downgrade(); - let theme_colors = cx.theme().colors(); - - div() - .child(editor) - .bg(theme_colors.editor_background) - .border_1() - .rounded_lg() - .border_color(theme_colors.border) - .on_action::({ - move |_, _, cx| { - let new_value = weak_editor.read_with(cx, |editor, cx| editor.text(cx)).ok(); - - if let Some(new_value) = new_value { - SettingsValue::write_value(&path, serde_json::Value::String(new_value), cx); - } - } - }) - .into_any_element() -} - -fn render_toggle_button_group( - value: SettingsValue, - variants: &'static [&'static str], - labels: &'static [&'static str], - _: &mut Window, - _: &mut App, -) -> AnyElement { - let value = downcast_any_item::(value); - let active_value = value.read(); - let selected_idx = variants.iter().position(|v| v == &active_value); - - return render_toggle_button_group_inner(value.title, labels, selected_idx, { - let path = value.path.clone(); - move |variant_index, cx| { - SettingsValue::write_value( - &path, - serde_json::Value::String(variants[variant_index].to_string()), - cx, - ); - } - }); -} - -fn render_dropdown( - value: SettingsValue, - variants: &'static [&'static str], - labels: &'static [&'static str], - window: &mut Window, - cx: &mut App, -) -> AnyElement { - let value = downcast_any_item::(value); - let id = element_id_from_path(&value.path); - - let menu = window.use_keyed_state(id.clone(), cx, |window, cx| { - let path = value.path.clone(); - let handler = Rc::new(move |variant: &'static str, cx: &mut App| { - SettingsValue::write_value(&path, serde_json::Value::String(variant.to_string()), cx); - }); - - ContextMenu::build(window, cx, |mut menu, _, _| { - for (label, variant) in labels.iter().zip(variants) { - menu = menu.entry(*label, None, { - let handler = handler.clone(); - move |_, cx| { - handler(variant, cx); - } - }); - } - - menu - }) - }); - - DropdownMenu::new(id, value.read(), menu.read(cx).clone()) - .style(ui::DropdownStyle::Outlined) - .into_any_element() -} - -fn render_toggle_button_group_inner( - title: SharedString, - labels: &'static [&'static str], - selected_idx: Option, - on_write: impl Fn(usize, &mut App) + 'static, -) -> AnyElement { - fn make_toggle_group( - title: SharedString, - selected_idx: Option, - on_write: Rc, - labels: &'static [&'static str], - ) -> AnyElement { - let labels_array: [&'static str; LEN] = { - let mut arr = ["unused"; LEN]; - arr.copy_from_slice(labels); - arr - }; - - let mut idx = 0; - ToggleButtonGroup::single_row( - title, - labels_array.map(|label| { - idx += 1; - let on_write = on_write.clone(); - ToggleButtonSimple::new(label, move |_, _, cx| { - on_write(idx - 1, cx); - }) - }), - ) - .when_some(selected_idx, |this, ix| this.selected_index(ix)) - .style(ui::ToggleButtonGroupStyle::Filled) - .into_any_element() - } - - let on_write = Rc::new(on_write); - - macro_rules! templ_toggl_with_const_param { - ($len:expr) => { - if labels.len() == $len { - return make_toggle_group::<$len>(title.clone(), selected_idx, on_write, labels); - } - }; - } - templ_toggl_with_const_param!(1); - templ_toggl_with_const_param!(2); - templ_toggl_with_const_param!(3); - templ_toggl_with_const_param!(4); - templ_toggl_with_const_param!(5); - templ_toggl_with_const_param!(6); - unreachable!("Too many variants"); -} - -fn settings_value_from_settings_and_path( - path: SmallVec<[SharedString; 1]>, - fallback_path: Option<&[SharedString]>, - title: SharedString, - documentation: Option, - user_settings: &serde_json::Value, - default_settings: &serde_json::Value, -) -> SettingsValue { - let default_value = read_settings_value_from_path(default_settings, &path) - .or_else(|| { - fallback_path.and_then(|fallback_path| { - read_settings_value_from_path(default_settings, fallback_path) - }) - }) - .with_context(|| format!("No default value for item at path {:?}", path.join("."))) - .expect("Default value set for item") - .clone(); - - let value = read_settings_value_from_path(user_settings, &path).cloned(); - let settings_value = SettingsValue { - default_value, - value, - documentation, - path, - // todo(settings_ui) is title required inside SettingsValue? - title, - }; - return settings_value; -} diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index d0a25257b91eb60b60390cde751d1af8af5866e1..9aa88c65a79032e96c9fe2951b2b37434be6927b 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -2,22 +2,18 @@ use alacritty_terminal::vte::ansi::{ CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle, }; use collections::HashMap; -use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px}; +use gpui::{App, FontFallbacks, FontFeatures, FontWeight, Pixels, px}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{SettingsKey, SettingsSources, SettingsUi}; -use std::path::PathBuf; +pub use settings::AlternateScroll; +use settings::{ + CursorShapeContent, SettingsContent, ShowScrollbar, TerminalBlink, TerminalDockPosition, + TerminalLineHeight, TerminalSettingsContent, VenvSettings, WorkingDirectory, +}; use task::Shell; use theme::FontFamilyName; - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum TerminalDockPosition { - Left, - Bottom, - Right, -} +use util::MergeFrom; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Toolbar { @@ -28,7 +24,7 @@ pub struct Toolbar { pub struct TerminalSettings { pub shell: Shell, pub working_directory: WorkingDirectory, - pub font_size: Option, + pub font_size: Option, // todo(settings_refactor) can be non-optional... pub font_family: Option, pub font_fallbacks: Option, pub font_features: Option, @@ -60,218 +56,135 @@ pub struct ScrollbarSettings { pub show: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ScrollbarSettingsContent { - /// When to show the scrollbar in the terminal. - /// - /// Default: inherits editor scrollbar settings - pub show: Option>, -} - -/// When to show the scrollbar in the terminal. -/// -/// Default: auto -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ShowScrollbar { - /// Show the scrollbar if there's important information or - /// follow the system's configured behavior. - Auto, - /// Match the system's configured behavior. - System, - /// Always show the scrollbar. - Always, - /// Never show the scrollbar. - Never, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum VenvSettings { - #[default] - Off, - On { - /// Default directories to search for virtual environments, relative - /// to the current working directory. We recommend overriding this - /// in your project's settings, rather than globally. - activate_script: Option, - venv_name: Option, - directories: Option>, - }, -} - -pub struct VenvSettingsContent<'a> { - pub activate_script: ActivateScript, - pub venv_name: &'a str, - pub directories: &'a [PathBuf], +fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell { + match shell { + settings::Shell::System => Shell::System, + settings::Shell::Program(program) => Shell::Program(program), + settings::Shell::WithArguments { + program, + args, + title_override, + } => Shell::WithArguments { + program, + args, + title_override, + }, + } } -impl VenvSettings { - pub fn as_option(&self) -> Option> { - match self { - VenvSettings::Off => None, - VenvSettings::On { - activate_script, - venv_name, - directories, - } => Some(VenvSettingsContent { - activate_script: activate_script.unwrap_or(ActivateScript::Default), - venv_name: venv_name.as_deref().unwrap_or(""), - directories: directories.as_deref().unwrap_or(&[]), +impl settings::Settings for TerminalSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let content = content.terminal.clone().unwrap(); + TerminalSettings { + shell: settings_shell_to_task_shell(content.shell.unwrap()), + working_directory: content.working_directory.unwrap(), + font_size: content.font_size.map(px), + font_family: content.font_family, + font_fallbacks: content.font_fallbacks.map(|fallbacks| { + FontFallbacks::from_fonts( + fallbacks + .into_iter() + .map(|family| family.0.to_string()) + .collect(), + ) }), + font_features: content.font_features, + font_weight: content.font_weight.map(FontWeight), + line_height: content.line_height.unwrap(), + env: content.env.unwrap(), + cursor_shape: content.cursor_shape.map(Into::into), + blinking: content.blinking.unwrap(), + alternate_scroll: content.alternate_scroll.unwrap(), + option_as_meta: content.option_as_meta.unwrap(), + copy_on_select: content.copy_on_select.unwrap(), + keep_selection_on_copy: content.keep_selection_on_copy.unwrap(), + button: content.button.unwrap(), + dock: content.dock.unwrap(), + default_width: px(content.default_width.unwrap()), + default_height: px(content.default_height.unwrap()), + detect_venv: content.detect_venv.unwrap(), + max_scroll_history_lines: content.max_scroll_history_lines, + toolbar: Toolbar { + breadcrumbs: content.toolbar.unwrap().breadcrumbs.unwrap(), + }, + scrollbar: ScrollbarSettings { + show: content.scrollbar.unwrap().show.flatten(), + }, + minimum_contrast: content.minimum_contrast.unwrap(), } } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ActivateScript { - #[default] - Default, - Csh, - Fish, - Nushell, - PowerShell, - Pyenv, -} -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "terminal")] -pub struct TerminalSettingsContent { - /// What shell to use when opening a terminal. - /// - /// Default: system - pub shell: Option, - /// What working directory to use when launching the terminal - /// - /// Default: current_project_directory - pub working_directory: Option, - /// Sets the terminal's font size. - /// - /// If this option is not included, - /// the terminal will default to matching the buffer's font size. - pub font_size: Option, - /// Sets the terminal's font family. - /// - /// If this option is not included, - /// the terminal will default to matching the buffer's font family. - pub font_family: Option, - - /// Sets the terminal's font fallbacks. - /// - /// If this option is not included, - /// the terminal will default to matching the buffer's font fallbacks. - #[schemars(extend("uniqueItems" = true))] - pub font_fallbacks: Option>, - - /// Sets the terminal's line height. - /// - /// Default: comfortable - pub line_height: Option, - pub font_features: Option, - /// Sets the terminal's font weight in CSS weight units 0-900. - pub font_weight: Option, - /// Any key-value pairs added to this list will be added to the terminal's - /// environment. Use `:` to separate multiple values. - /// - /// Default: {} - pub env: Option>, - /// Default cursor shape for the terminal. - /// Can be "bar", "block", "underline", or "hollow". - /// - /// Default: None - pub cursor_shape: Option, - /// Sets the cursor blinking behavior in the terminal. - /// - /// Default: terminal_controlled - pub blinking: Option, - /// Sets whether Alternate Scroll mode (code: ?1007) is active by default. - /// Alternate Scroll mode converts mouse scroll events into up / down key - /// presses when in the alternate screen (e.g. when running applications - /// like vim or less). The terminal can still set and unset this mode. - /// - /// Default: on - pub alternate_scroll: Option, - /// Sets whether the option key behaves as the meta key. - /// - /// Default: false - pub option_as_meta: Option, - /// Whether or not selecting text in the terminal will automatically - /// copy to the system clipboard. - /// - /// Default: false - pub copy_on_select: Option, - /// Whether to keep the text selection after copying it to the clipboard. - /// - /// Default: false - pub keep_selection_on_copy: Option, - /// Whether to show the terminal button in the status bar. - /// - /// Default: true - pub button: Option, - pub dock: Option, - /// Default width when the terminal is docked to the left or right. - /// - /// Default: 640 - pub default_width: Option, - /// Default height when the terminal is docked to the bottom. - /// - /// Default: 320 - pub default_height: Option, - /// Activates the python virtual environment, if one is found, in the - /// terminal's working directory (as resolved by the working_directory - /// setting). Set this to "off" to disable this behavior. - /// - /// Default: on - pub detect_venv: Option, - /// The maximum number of lines to keep in the scrollback history. - /// Maximum allowed value is 100_000, all values above that will be treated as 100_000. - /// 0 disables the scrolling. - /// Existing terminals will not pick up this change until they are recreated. - /// See Alacritty documentation for more information. - /// - /// Default: 10_000 - pub max_scroll_history_lines: Option, - /// Toolbar related settings - pub toolbar: Option, - /// Scrollbar-related settings - pub scrollbar: Option, - /// The minimum APCA perceptual contrast between foreground and background colors. - /// - /// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x, - /// especially for dark mode. Values range from 0 to 106. - /// - /// Based on APCA Readability Criterion (ARC) Bronze Simple Mode: - /// https://readtech.org/ARC/tests/bronze-simple-mode/ - /// - 0: No contrast adjustment - /// - 45: Minimum for large fluent text (36px+) - /// - 60: Minimum for other content text - /// - 75: Minimum for body text - /// - 90: Preferred for body text - /// - /// Default: 45 - pub minimum_contrast: Option, -} - -impl settings::Settings for TerminalSettings { - type FileContent = TerminalSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - let settings: Self = sources.json_merge()?; - - // Validate minimum_contrast for APCA - if settings.minimum_contrast < 0.0 || settings.minimum_contrast > 106.0 { - anyhow::bail!( - "terminal.minimum_contrast must be between 0 and 106, but got {}. \ - APCA values: 0 = no adjustment, 75 = recommended for body text, 106 = maximum contrast.", - settings.minimum_contrast - ); + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(content) = &content.terminal else { + return; + }; + self.shell + .merge_from(&content.shell.clone().map(settings_shell_to_task_shell)); + self.working_directory + .merge_from(&content.working_directory); + if let Some(font_size) = content.font_size.map(px) { + self.font_size = Some(font_size) } - - Ok(settings) + if let Some(font_family) = content.font_family.clone() { + self.font_family = Some(font_family); + } + if let Some(fallbacks) = content.font_fallbacks.clone() { + self.font_fallbacks = Some(FontFallbacks::from_fonts( + fallbacks + .into_iter() + .map(|family| family.0.to_string()) + .collect(), + )) + } + if let Some(font_features) = content.font_features.clone() { + self.font_features = Some(font_features) + } + if let Some(font_weight) = content.font_weight { + self.font_weight = Some(FontWeight(font_weight)); + } + self.line_height.merge_from(&content.line_height); + if let Some(env) = &content.env { + for (key, value) in env { + self.env.insert(key.clone(), value.clone()); + } + } + if let Some(cursor_shape) = content.cursor_shape { + self.cursor_shape = Some(cursor_shape.into()) + } + self.blinking.merge_from(&content.blinking); + self.alternate_scroll.merge_from(&content.alternate_scroll); + self.option_as_meta.merge_from(&content.option_as_meta); + self.copy_on_select.merge_from(&content.copy_on_select); + self.keep_selection_on_copy + .merge_from(&content.keep_selection_on_copy); + self.button.merge_from(&content.button); + self.dock.merge_from(&content.dock); + self.default_width + .merge_from(&content.default_width.map(px)); + self.default_height + .merge_from(&content.default_height.map(px)); + self.detect_venv.merge_from(&content.detect_venv); + if let Some(max_scroll_history_lines) = content.max_scroll_history_lines { + self.max_scroll_history_lines = Some(max_scroll_history_lines) + } + self.toolbar.breadcrumbs.merge_from( + &content + .toolbar + .as_ref() + .and_then(|toolbar| toolbar.breadcrumbs), + ); + self.scrollbar.show.merge_from( + &content + .scrollbar + .as_ref() + .and_then(|scrollbar| scrollbar.show), + ); + self.minimum_contrast.merge_from(&content.minimum_contrast); } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) { + let mut default = TerminalSettingsContent::default(); + let current = content.terminal.as_mut().unwrap_or(&mut default); let name = |s| format!("terminal.integrated.{s}"); vscode.f32_setting(&name("fontSize"), &mut current.font_size); @@ -290,9 +203,9 @@ impl settings::Settings for TerminalSettings { &name("cursorStyle"), &mut current.cursor_shape, |s| match s { - "block" => Some(CursorShape::Block), - "line" => Some(CursorShape::Bar), - "underline" => Some(CursorShape::Underline), + "block" => Some(CursorShapeContent::Block), + "line" => Some(CursorShapeContent::Bar), + "underline" => Some(CursorShapeContent::Underline), _ => None, }, ); @@ -316,7 +229,7 @@ impl settings::Settings for TerminalSettings { // TODO: handle arguments let shell_name = format!("{platform}Exec"); if let Some(s) = vscode.read_string(&name(&shell_name)) { - current.shell = Some(Shell::Program(s.to_owned())) + current.shell = Some(settings::Shell::Program(s.to_owned())) } if let Some(env) = vscode @@ -337,81 +250,12 @@ impl settings::Settings for TerminalSettings { } } } + if content.terminal.is_none() && default != TerminalSettingsContent::default() { + content.terminal = Some(default) + } } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] -#[serde(rename_all = "snake_case")] -pub enum TerminalLineHeight { - /// Use a line height that's comfortable for reading, 1.618 - #[default] - Comfortable, - /// Use a standard line height, 1.3. This option is useful for TUIs, - /// particularly if they use box characters - Standard, - /// Use a custom line height. - Custom(f32), -} - -impl TerminalLineHeight { - pub fn value(&self) -> AbsoluteLength { - let value = match self { - TerminalLineHeight::Comfortable => 1.618, - TerminalLineHeight::Standard => 1.3, - TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.), - }; - px(value).into() - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum TerminalBlink { - /// Never blink the cursor, ignoring the terminal mode. - Off, - /// Default the cursor blink to off, but allow the terminal to - /// set blinking. - TerminalControlled, - /// Always blink the cursor, ignoring the terminal mode. - On, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AlternateScroll { - On, - Off, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WorkingDirectory { - /// Use the current file's project directory. Will Fallback to the - /// first project directory strategy if unsuccessful. - CurrentProjectDirectory, - /// Use the first project in this workspace's directory. - FirstProjectDirectory, - /// Always use this platform's home directory (if it can be found). - AlwaysHome, - /// Always use a specific directory. This value will be shell expanded. - /// If this path is not a valid directory the terminal will default to - /// this platform's home directory (if it can be found). - Always { directory: String }, -} - -// Toolbar related settings -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ToolbarContent { - /// Whether to display the terminal title in breadcrumbs inside the terminal pane. - /// Only shown if the terminal title is not empty. - /// - /// The shell running in the terminal needs to be configured to emit the title. - /// Example: `echo -e "\e]2;New Title\007";` - /// - /// Default: true - pub breadcrumbs: Option, -} - #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CursorShape { @@ -426,6 +270,17 @@ pub enum CursorShape { Hollow, } +impl From for CursorShape { + fn from(value: settings::CursorShapeContent) -> Self { + match value { + settings::CursorShapeContent::Block => CursorShape::Block, + settings::CursorShapeContent::Underline => CursorShape::Underline, + settings::CursorShapeContent::Bar => CursorShape::Bar, + settings::CursorShapeContent::Hollow => CursorShape::Hollow, + } + } +} + impl From for AlacCursorShape { fn from(value: CursorShape) -> Self { match value { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ef574728acdc06bb0db686a1cc9b4c8f8bc0bcce..faea73849ba83b3e2899f3efd995d4557c6b1e3c 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -18,12 +18,9 @@ use gpui::{ use itertools::Itertools; use project::{Fs, Project, ProjectEntryId}; use search::{BufferSearchBar, buffer_search::DivRegistrar}; -use settings::Settings; +use settings::{Settings, TerminalDockPosition}; use task::{RevealStrategy, RevealTarget, ShellBuilder, SpawnInTerminal, TaskId}; -use terminal::{ - Terminal, - terminal_settings::{TerminalDockPosition, TerminalSettings}, -}; +use terminal::{Terminal, terminal_settings::TerminalSettings}; use ui::{ ButtonCommon, Clickable, ContextMenu, FluentBuilder, PopoverMenu, Toggleable, Tooltip, prelude::*, @@ -1465,18 +1462,14 @@ impl Panel for TerminalPanel { _window: &mut Window, cx: &mut Context, ) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| { - let dock = match position { - DockPosition::Left => TerminalDockPosition::Left, - DockPosition::Bottom => TerminalDockPosition::Bottom, - DockPosition::Right => TerminalDockPosition::Right, - }; - settings.dock = Some(dock); - }, - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + let dock = match position { + DockPosition::Left => TerminalDockPosition::Left, + DockPosition::Bottom => TerminalDockPosition::Bottom, + DockPosition::Right => TerminalDockPosition::Right, + }; + settings.terminal.get_or_insert_default().dock = Some(dock); + }); } fn size(&self, window: &Window, cx: &App) -> Pixels { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 133bde6175c02e6f8845d83c765f7820b77e37f7..d7adf74acb37e848ccb2d8670f970054d46ea0ae 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -25,7 +25,7 @@ use terminal::{ index::Point, term::{TermMode, point_to_viewport, search::RegexSearch}, }, - terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, + terminal_settings::{CursorShape, TerminalSettings}, }; use terminal_element::TerminalElement; use terminal_panel::TerminalPanel; @@ -50,7 +50,7 @@ use workspace::{ }; use serde::Deserialize; -use settings::{Settings, SettingsStore}; +use settings::{Settings, SettingsStore, TerminalBlink, WorkingDirectory}; use smol::Timer; use zed_actions::assistant::InlineAssist; @@ -997,12 +997,7 @@ impl ScrollbarVisibility for TerminalScrollbarSettingsWrapper { TerminalSettings::get_global(cx) .scrollbar .show - .map(|value| match value { - terminal_settings::ShowScrollbar::Auto => scrollbars::ShowScrollbar::Auto, - terminal_settings::ShowScrollbar::System => scrollbars::ShowScrollbar::System, - terminal_settings::ShowScrollbar::Always => scrollbars::ShowScrollbar::Always, - terminal_settings::ShowScrollbar::Never => scrollbars::ShowScrollbar::Never, - }) + .map(Into::into) .unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show) } } diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 00f8e1868e5ddf00fa1fdaadb042c23d6013f5d8..8c51b991ccc8580d2be3d38273a7c11118b37d52 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -33,7 +33,6 @@ schemars = { workspace = true, features = ["indexmap2"] } serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true -serde_repr.workspace = true settings.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index d2bdbb616a5b6f65f905375769e14dd2a2312ea9..945517daea904e3bb5bdfe4954813a81af2a9e2b 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -1,31 +1,15 @@ #![allow(missing_docs)] -use anyhow::Result; -use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance}; +use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla}; use indexmap::IndexMap; use palette::FromColor; -use schemars::{JsonSchema, JsonSchema_repr}; -use serde::{Deserialize, Deserializer, Serialize}; -use serde_json::Value; -use serde_repr::{Deserialize_repr, Serialize_repr}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::{AccentContent, PlayerColorContent}; +pub use settings::{FontWeightContent, WindowBackgroundContent}; use crate::{StatusColorsRefinement, ThemeColorsRefinement}; -pub(crate) fn try_parse_color(color: &str) -> Result { - let rgba = gpui::Rgba::try_from(color)?; - let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a)); - let hsla = palette::Hsla::from_color(rgba); - - let hsla = gpui::hsla( - hsla.hue.into_positive_degrees() / 360., - hsla.saturation, - hsla.lightness, - hsla.alpha, - ); - - Ok(hsla) -} - fn ensure_non_opaque(color: Hsla) -> Hsla { const MAXIMUM_OPACITY: f32 = 0.7; if color.a <= MAXIMUM_OPACITY { @@ -49,25 +33,6 @@ pub enum AppearanceContent { Dark, } -/// The background appearance of the window. -#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WindowBackgroundContent { - Opaque, - Transparent, - Blurred, -} - -impl From for WindowBackgroundAppearance { - fn from(value: WindowBackgroundContent) -> Self { - match value { - WindowBackgroundContent::Opaque => WindowBackgroundAppearance::Opaque, - WindowBackgroundContent::Transparent => WindowBackgroundAppearance::Transparent, - WindowBackgroundContent::Blurred => WindowBackgroundAppearance::Blurred, - } - } -} - /// The content of a serialized theme family. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct ThemeFamilyContent { @@ -81,7 +46,7 @@ pub struct ThemeFamilyContent { pub struct ThemeContent { pub name: String, pub appearance: AppearanceContent, - pub style: ThemeStyleContent, + pub style: settings::ThemeStyleContent, } /// The content of a serialized theme. @@ -89,1494 +54,748 @@ pub struct ThemeContent { #[serde(default)] pub struct ThemeStyleContent { #[serde(default, rename = "background.appearance")] - pub window_background_appearance: Option, + pub window_background_appearance: Option, #[serde(default)] pub accents: Vec, #[serde(flatten, default)] - pub colors: ThemeColorsContent, + pub colors: settings::ThemeColorsContent, #[serde(flatten, default)] - pub status: StatusColorsContent, + pub status: settings::StatusColorsContent, #[serde(default)] pub players: Vec, /// The styles for syntax nodes. #[serde(default)] - pub syntax: IndexMap, + pub syntax: IndexMap, } -impl ThemeStyleContent { - /// Returns a [`ThemeColorsRefinement`] based on the colors in the [`ThemeContent`]. - #[inline(always)] - pub fn theme_colors_refinement(&self) -> ThemeColorsRefinement { - self.colors - .theme_colors_refinement(&self.status_colors_refinement()) - } - - /// Returns a [`StatusColorsRefinement`] based on the colors in the [`ThemeContent`]. - #[inline(always)] - pub fn status_colors_refinement(&self) -> StatusColorsRefinement { - self.status.status_colors_refinement() - } - - /// Returns the syntax style overrides in the [`ThemeContent`]. - pub fn syntax_overrides(&self) -> Vec<(String, HighlightStyle)> { - self.syntax - .iter() - .map(|(key, style)| { - ( - key.clone(), - HighlightStyle { - color: style - .color - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - background_color: style - .background_color - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - font_style: style.font_style.map(FontStyle::from), - font_weight: style.font_weight.map(FontWeight::from), - ..Default::default() - }, - ) - }) - .collect() - } +/// Returns the syntax style overrides in the [`ThemeContent`]. +pub fn syntax_overrides(this: &settings::ThemeStyleContent) -> Vec<(String, HighlightStyle)> { + this.syntax + .iter() + .map(|(key, style)| { + ( + key.clone(), + HighlightStyle { + color: style + .color + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + background_color: style + .background_color + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + font_style: style.font_style.map(FontStyle::from), + font_weight: style.font_weight.map(FontWeight::from), + ..Default::default() + }, + ) + }) + .collect() } -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] -#[serde(default)] -pub struct ThemeColorsContent { - /// Border color. Used for most borders, is usually a high contrast color. - #[serde(rename = "border")] - pub border: Option, - - /// Border color. Used for deemphasized borders, like a visual divider between two sections - #[serde(rename = "border.variant")] - pub border_variant: Option, - - /// Border color. Used for focused elements, like keyboard focused list item. - #[serde(rename = "border.focused")] - pub border_focused: Option, - - /// Border color. Used for selected elements, like an active search filter or selected checkbox. - #[serde(rename = "border.selected")] - pub border_selected: Option, - - /// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change. - #[serde(rename = "border.transparent")] - pub border_transparent: Option, - - /// Border color. Used for disabled elements, like a disabled input or button. - #[serde(rename = "border.disabled")] - pub border_disabled: Option, - - /// Background color. Used for elevated surfaces, like a context menu, popup, or dialog. - #[serde(rename = "elevated_surface.background")] - pub elevated_surface_background: Option, - - /// Background Color. Used for grounded surfaces like a panel or tab. - #[serde(rename = "surface.background")] - pub surface_background: Option, - - /// Background Color. Used for the app background and blank panels or windows. - #[serde(rename = "background")] - pub background: Option, - - /// Background Color. Used for the background of an element that should have a different background than the surface it's on. - /// - /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... - /// - /// For an element that should have the same background as the surface it's on, use `ghost_element_background`. - #[serde(rename = "element.background")] - pub element_background: Option, - - /// Background Color. Used for the hover state of an element that should have a different background than the surface it's on. - /// - /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. - #[serde(rename = "element.hover")] - pub element_hover: Option, - - /// Background Color. Used for the active state of an element that should have a different background than the surface it's on. - /// - /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed. - #[serde(rename = "element.active")] - pub element_active: Option, - - /// Background Color. Used for the selected state of an element that should have a different background than the surface it's on. - /// - /// Selected states are triggered by the element being selected (or "activated") by the user. - /// - /// This could include a selected checkbox, a toggleable button that is toggled on, etc. - #[serde(rename = "element.selected")] - pub element_selected: Option, - - /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on. - /// - /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. - #[serde(rename = "element.disabled")] - pub element_disabled: Option, - - /// Background Color. Used for the background of selections in a UI element. - #[serde(rename = "element.selection_background")] - pub element_selection_background: Option, - - /// Background Color. Used for the area that shows where a dragged element will be dropped. - #[serde(rename = "drop_target.background")] - pub drop_target_background: Option, - - /// Border Color. Used for the border that shows where a dragged element will be dropped. - #[serde(rename = "drop_target.border")] - pub drop_target_border: Option, - - /// Used for the background of a ghost element that should have the same background as the surface it's on. - /// - /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... - /// - /// For an element that should have a different background than the surface it's on, use `element_background`. - #[serde(rename = "ghost_element.background")] - pub ghost_element_background: Option, - - /// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on. - /// - /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. - #[serde(rename = "ghost_element.hover")] - pub ghost_element_hover: Option, - - /// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on. - /// - /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed. - #[serde(rename = "ghost_element.active")] - pub ghost_element_active: Option, - - /// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on. - /// - /// Selected states are triggered by the element being selected (or "activated") by the user. - /// - /// This could include a selected checkbox, a toggleable button that is toggled on, etc. - #[serde(rename = "ghost_element.selected")] - pub ghost_element_selected: Option, - - /// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on. - /// - /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. - #[serde(rename = "ghost_element.disabled")] - pub ghost_element_disabled: Option, - - /// Text Color. Default text color used for most text. - #[serde(rename = "text")] - pub text: Option, - - /// Text Color. Color of muted or deemphasized text. It is a subdued version of the standard text color. - #[serde(rename = "text.muted")] - pub text_muted: Option, - - /// Text Color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data. - #[serde(rename = "text.placeholder")] - pub text_placeholder: Option, - - /// Text Color. Color used for text denoting disabled elements. Typically, the color is faded or grayed out to emphasize the disabled state. - #[serde(rename = "text.disabled")] - pub text_disabled: Option, - - /// Text Color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search. - #[serde(rename = "text.accent")] - pub text_accent: Option, - - /// Fill Color. Used for the default fill color of an icon. - #[serde(rename = "icon")] - pub icon: Option, - - /// Fill Color. Used for the muted or deemphasized fill color of an icon. - /// - /// This might be used to show an icon in an inactive pane, or to deemphasize a series of icons to give them less visual weight. - #[serde(rename = "icon.muted")] - pub icon_muted: Option, - - /// Fill Color. Used for the disabled fill color of an icon. - /// - /// Disabled states are shown when a user cannot interact with an element, like a icon button. - #[serde(rename = "icon.disabled")] - pub icon_disabled: Option, - - /// Fill Color. Used for the placeholder fill color of an icon. - /// - /// This might be used to show an icon in an input that disappears when the user enters text. - #[serde(rename = "icon.placeholder")] - pub icon_placeholder: Option, - - /// Fill Color. Used for the accent fill color of an icon. - /// - /// This might be used to show when a toggleable icon button is selected. - #[serde(rename = "icon.accent")] - pub icon_accent: Option, - - /// Color used to accent some of the debuggers elements - /// Only accent breakpoint & breakpoint related symbols right now - #[serde(rename = "debugger.accent")] - pub debugger_accent: Option, - - #[serde(rename = "status_bar.background")] - pub status_bar_background: Option, - - #[serde(rename = "title_bar.background")] - pub title_bar_background: Option, - - #[serde(rename = "title_bar.inactive_background")] - pub title_bar_inactive_background: Option, - - #[serde(rename = "toolbar.background")] - pub toolbar_background: Option, - - #[serde(rename = "tab_bar.background")] - pub tab_bar_background: Option, - - #[serde(rename = "tab.inactive_background")] - pub tab_inactive_background: Option, - - #[serde(rename = "tab.active_background")] - pub tab_active_background: Option, - - #[serde(rename = "search.match_background")] - pub search_match_background: Option, - - #[serde(rename = "panel.background")] - pub panel_background: Option, - - #[serde(rename = "panel.focused_border")] - pub panel_focused_border: Option, - - #[serde(rename = "panel.indent_guide")] - pub panel_indent_guide: Option, - - #[serde(rename = "panel.indent_guide_hover")] - pub panel_indent_guide_hover: Option, - - #[serde(rename = "panel.indent_guide_active")] - pub panel_indent_guide_active: Option, - - #[serde(rename = "panel.overlay_background")] - pub panel_overlay_background: Option, - - #[serde(rename = "panel.overlay_hover")] - pub panel_overlay_hover: Option, - - #[serde(rename = "pane.focused_border")] - pub pane_focused_border: Option, - - #[serde(rename = "pane_group.border")] - pub pane_group_border: Option, - - /// The deprecated version of `scrollbar.thumb.background`. - /// - /// Don't use this field. - #[serde(rename = "scrollbar_thumb.background", skip_serializing)] - #[schemars(skip)] - pub deprecated_scrollbar_thumb_background: Option, - - /// The color of the scrollbar thumb. - #[serde(rename = "scrollbar.thumb.background")] - pub scrollbar_thumb_background: Option, - - /// The color of the scrollbar thumb when hovered over. - #[serde(rename = "scrollbar.thumb.hover_background")] - pub scrollbar_thumb_hover_background: Option, - - /// The color of the scrollbar thumb whilst being actively dragged. - #[serde(rename = "scrollbar.thumb.active_background")] - pub scrollbar_thumb_active_background: Option, - - /// The border color of the scrollbar thumb. - #[serde(rename = "scrollbar.thumb.border")] - pub scrollbar_thumb_border: Option, - - /// The background color of the scrollbar track. - #[serde(rename = "scrollbar.track.background")] - pub scrollbar_track_background: Option, - - /// The border color of the scrollbar track. - #[serde(rename = "scrollbar.track.border")] - pub scrollbar_track_border: Option, - - /// The color of the minimap thumb. - #[serde(rename = "minimap.thumb.background")] - pub minimap_thumb_background: Option, - - /// The color of the minimap thumb when hovered over. - #[serde(rename = "minimap.thumb.hover_background")] - pub minimap_thumb_hover_background: Option, - - /// The color of the minimap thumb whilst being actively dragged. - #[serde(rename = "minimap.thumb.active_background")] - pub minimap_thumb_active_background: Option, - - /// The border color of the minimap thumb. - #[serde(rename = "minimap.thumb.border")] - pub minimap_thumb_border: Option, - - #[serde(rename = "editor.foreground")] - pub editor_foreground: Option, - - #[serde(rename = "editor.background")] - pub editor_background: Option, - - #[serde(rename = "editor.gutter.background")] - pub editor_gutter_background: Option, - - #[serde(rename = "editor.subheader.background")] - pub editor_subheader_background: Option, - - #[serde(rename = "editor.active_line.background")] - pub editor_active_line_background: Option, - - #[serde(rename = "editor.highlighted_line.background")] - pub editor_highlighted_line_background: Option, - - /// Background of active line of debugger - #[serde(rename = "editor.debugger_active_line.background")] - pub editor_debugger_active_line_background: Option, - - /// Text Color. Used for the text of the line number in the editor gutter. - #[serde(rename = "editor.line_number")] - pub editor_line_number: Option, - - /// Text Color. Used for the text of the line number in the editor gutter when the line is highlighted. - #[serde(rename = "editor.active_line_number")] - pub editor_active_line_number: Option, - - /// Text Color. Used for the text of the line number in the editor gutter when the line is hovered over. - #[serde(rename = "editor.hover_line_number")] - pub editor_hover_line_number: Option, - - /// Text Color. Used to mark invisible characters in the editor. - /// - /// Example: spaces, tabs, carriage returns, etc. - #[serde(rename = "editor.invisible")] - pub editor_invisible: Option, - - #[serde(rename = "editor.wrap_guide")] - pub editor_wrap_guide: Option, - - #[serde(rename = "editor.active_wrap_guide")] - pub editor_active_wrap_guide: Option, - - #[serde(rename = "editor.indent_guide")] - pub editor_indent_guide: Option, - - #[serde(rename = "editor.indent_guide_active")] - pub editor_indent_guide_active: Option, - - /// Read-access of a symbol, like reading a variable. - /// - /// A document highlight is a range inside a text document which deserves - /// special attention. Usually a document highlight is visualized by changing - /// the background color of its range. - #[serde(rename = "editor.document_highlight.read_background")] - pub editor_document_highlight_read_background: Option, - - /// Read-access of a symbol, like reading a variable. - /// - /// A document highlight is a range inside a text document which deserves - /// special attention. Usually a document highlight is visualized by changing - /// the background color of its range. - #[serde(rename = "editor.document_highlight.write_background")] - pub editor_document_highlight_write_background: Option, - - /// Highlighted brackets background color. - /// - /// Matching brackets in the cursor scope are highlighted with this background color. - #[serde(rename = "editor.document_highlight.bracket_background")] - pub editor_document_highlight_bracket_background: Option, - - /// Terminal background color. - #[serde(rename = "terminal.background")] - pub terminal_background: Option, - - /// Terminal foreground color. - #[serde(rename = "terminal.foreground")] - pub terminal_foreground: Option, - - /// Terminal ANSI background color. - #[serde(rename = "terminal.ansi.background")] - pub terminal_ansi_background: Option, - - /// Bright terminal foreground color. - #[serde(rename = "terminal.bright_foreground")] - pub terminal_bright_foreground: Option, - - /// Dim terminal foreground color. - #[serde(rename = "terminal.dim_foreground")] - pub terminal_dim_foreground: Option, - - /// Black ANSI terminal color. - #[serde(rename = "terminal.ansi.black")] - pub terminal_ansi_black: Option, - - /// Bright black ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_black")] - pub terminal_ansi_bright_black: Option, - - /// Dim black ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_black")] - pub terminal_ansi_dim_black: Option, - - /// Red ANSI terminal color. - #[serde(rename = "terminal.ansi.red")] - pub terminal_ansi_red: Option, - - /// Bright red ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_red")] - pub terminal_ansi_bright_red: Option, - - /// Dim red ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_red")] - pub terminal_ansi_dim_red: Option, - - /// Green ANSI terminal color. - #[serde(rename = "terminal.ansi.green")] - pub terminal_ansi_green: Option, - - /// Bright green ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_green")] - pub terminal_ansi_bright_green: Option, - - /// Dim green ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_green")] - pub terminal_ansi_dim_green: Option, - - /// Yellow ANSI terminal color. - #[serde(rename = "terminal.ansi.yellow")] - pub terminal_ansi_yellow: Option, - - /// Bright yellow ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_yellow")] - pub terminal_ansi_bright_yellow: Option, - - /// Dim yellow ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_yellow")] - pub terminal_ansi_dim_yellow: Option, - - /// Blue ANSI terminal color. - #[serde(rename = "terminal.ansi.blue")] - pub terminal_ansi_blue: Option, - - /// Bright blue ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_blue")] - pub terminal_ansi_bright_blue: Option, - - /// Dim blue ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_blue")] - pub terminal_ansi_dim_blue: Option, - - /// Magenta ANSI terminal color. - #[serde(rename = "terminal.ansi.magenta")] - pub terminal_ansi_magenta: Option, - - /// Bright magenta ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_magenta")] - pub terminal_ansi_bright_magenta: Option, - - /// Dim magenta ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_magenta")] - pub terminal_ansi_dim_magenta: Option, - - /// Cyan ANSI terminal color. - #[serde(rename = "terminal.ansi.cyan")] - pub terminal_ansi_cyan: Option, - - /// Bright cyan ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_cyan")] - pub terminal_ansi_bright_cyan: Option, - - /// Dim cyan ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_cyan")] - pub terminal_ansi_dim_cyan: Option, - - /// White ANSI terminal color. - #[serde(rename = "terminal.ansi.white")] - pub terminal_ansi_white: Option, - - /// Bright white ANSI terminal color. - #[serde(rename = "terminal.ansi.bright_white")] - pub terminal_ansi_bright_white: Option, - - /// Dim white ANSI terminal color. - #[serde(rename = "terminal.ansi.dim_white")] - pub terminal_ansi_dim_white: Option, - - #[serde(rename = "link_text.hover")] - pub link_text_hover: Option, - - /// Added version control color. - #[serde(rename = "version_control.added")] - pub version_control_added: Option, - - /// Deleted version control color. - #[serde(rename = "version_control.deleted")] - pub version_control_deleted: Option, - - /// Modified version control color. - #[serde(rename = "version_control.modified")] - pub version_control_modified: Option, - - /// Renamed version control color. - #[serde(rename = "version_control.renamed")] - pub version_control_renamed: Option, - - /// Conflict version control color. - #[serde(rename = "version_control.conflict")] - pub version_control_conflict: Option, - - /// Ignored version control color. - #[serde(rename = "version_control.ignored")] - pub version_control_ignored: Option, - - /// Background color for row highlights of "ours" regions in merge conflicts. - #[serde(rename = "version_control.conflict_marker.ours")] - pub version_control_conflict_marker_ours: Option, - - /// Background color for row highlights of "theirs" regions in merge conflicts. - #[serde(rename = "version_control.conflict_marker.theirs")] - pub version_control_conflict_marker_theirs: Option, - - /// Deprecated in favor of `version_control_conflict_marker_ours`. - #[deprecated] - pub version_control_conflict_ours_background: Option, - - /// Deprecated in favor of `version_control_conflict_marker_theirs`. - #[deprecated] - pub version_control_conflict_theirs_background: Option, +pub fn status_colors_refinement(colors: &settings::StatusColorsContent) -> StatusColorsRefinement { + StatusColorsRefinement { + conflict: colors + .conflict + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + conflict_background: colors + .conflict_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + conflict_border: colors + .conflict_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + created: colors + .created + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + created_background: colors + .created_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + created_border: colors + .created_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + deleted: colors + .deleted + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + deleted_background: colors + .deleted_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + deleted_border: colors + .deleted_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + error: colors + .error + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + error_background: colors + .error_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + error_border: colors + .error_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + hidden: colors + .hidden + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + hidden_background: colors + .hidden_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + hidden_border: colors + .hidden_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + hint: colors + .hint + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + hint_background: colors + .hint_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + hint_border: colors + .hint_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ignored: colors + .ignored + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ignored_background: colors + .ignored_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ignored_border: colors + .ignored_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + info: colors + .info + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + info_background: colors + .info_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + info_border: colors + .info_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + modified: colors + .modified + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + modified_background: colors + .modified_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + modified_border: colors + .modified_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + predictive: colors + .predictive + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + predictive_background: colors + .predictive_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + predictive_border: colors + .predictive_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + renamed: colors + .renamed + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + renamed_background: colors + .renamed_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + renamed_border: colors + .renamed_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + success: colors + .success + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + success_background: colors + .success_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + success_border: colors + .success_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + unreachable: colors + .unreachable + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + unreachable_background: colors + .unreachable_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + unreachable_border: colors + .unreachable_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + warning: colors + .warning + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + warning_background: colors + .warning_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + warning_border: colors + .warning_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + } } -impl ThemeColorsContent { - /// Returns a [`ThemeColorsRefinement`] based on the colors in the [`ThemeColorsContent`]. - pub fn theme_colors_refinement( - &self, - status_colors: &StatusColorsRefinement, - ) -> ThemeColorsRefinement { - let border = self - .border - .as_ref() - .and_then(|color| try_parse_color(color).ok()); - let editor_document_highlight_read_background = self - .editor_document_highlight_read_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()); - let scrollbar_thumb_background = self - .scrollbar_thumb_background +pub fn theme_colors_refinement( + this: &settings::ThemeColorsContent, + status_colors: &StatusColorsRefinement, +) -> ThemeColorsRefinement { + let border = this + .border + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + let editor_document_highlight_read_background = this + .editor_document_highlight_read_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + let scrollbar_thumb_background = this + .scrollbar_thumb_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or_else(|| { + this.deprecated_scrollbar_thumb_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + }); + let scrollbar_thumb_hover_background = this + .scrollbar_thumb_hover_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + let scrollbar_thumb_active_background = this + .scrollbar_thumb_active_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_background); + let scrollbar_thumb_border = this + .scrollbar_thumb_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + let element_hover = this + .element_hover + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + let panel_background = this + .panel_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + ThemeColorsRefinement { + border, + border_variant: this + .border_variant + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + border_focused: this + .border_focused + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + border_selected: this + .border_selected + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + border_transparent: this + .border_transparent + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + border_disabled: this + .border_disabled + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + elevated_surface_background: this + .elevated_surface_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + surface_background: this + .surface_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + background: this + .background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + element_background: this + .element_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + element_hover, + element_active: this + .element_active + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + element_selected: this + .element_selected + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + element_disabled: this + .element_disabled + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + element_selection_background: this + .element_selection_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + drop_target_background: this + .drop_target_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + drop_target_border: this + .drop_target_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ghost_element_background: this + .ghost_element_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ghost_element_hover: this + .ghost_element_hover + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ghost_element_active: this + .ghost_element_active + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ghost_element_selected: this + .ghost_element_selected + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + ghost_element_disabled: this + .ghost_element_disabled + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + text: this + .text + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + text_muted: this + .text_muted + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + text_placeholder: this + .text_placeholder + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + text_disabled: this + .text_disabled + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + text_accent: this + .text_accent + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + icon: this + .icon + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + icon_muted: this + .icon_muted + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + icon_disabled: this + .icon_disabled + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + icon_placeholder: this + .icon_placeholder + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + icon_accent: this + .icon_accent + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + debugger_accent: this + .debugger_accent + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + status_bar_background: this + .status_bar_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + title_bar_background: this + .title_bar_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + title_bar_inactive_background: this + .title_bar_inactive_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + toolbar_background: this + .toolbar_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + tab_bar_background: this + .tab_bar_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + tab_inactive_background: this + .tab_inactive_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + tab_active_background: this + .tab_active_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + search_match_background: this + .search_match_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + panel_background, + panel_focused_border: this + .panel_focused_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + panel_indent_guide: this + .panel_indent_guide + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + panel_indent_guide_hover: this + .panel_indent_guide_hover + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + panel_indent_guide_active: this + .panel_indent_guide_active + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + panel_overlay_background: this + .panel_overlay_background .as_ref() .and_then(|color| try_parse_color(color).ok()) - .or_else(|| { - self.deprecated_scrollbar_thumb_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - }); - let scrollbar_thumb_hover_background = self - .scrollbar_thumb_hover_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()); - let scrollbar_thumb_active_background = self - .scrollbar_thumb_active_background + .or(panel_background.map(ensure_opaque)), + panel_overlay_hover: this + .panel_overlay_hover .as_ref() .and_then(|color| try_parse_color(color).ok()) - .or(scrollbar_thumb_background); - let scrollbar_thumb_border = self - .scrollbar_thumb_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()); - let element_hover = self - .element_hover - .as_ref() - .and_then(|color| try_parse_color(color).ok()); - let panel_background = self - .panel_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()); - ThemeColorsRefinement { - border, - border_variant: self - .border_variant - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - border_focused: self - .border_focused - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - border_selected: self - .border_selected - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - border_transparent: self - .border_transparent - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - border_disabled: self - .border_disabled - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - elevated_surface_background: self - .elevated_surface_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - surface_background: self - .surface_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - background: self - .background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - element_background: self - .element_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - element_hover, - element_active: self - .element_active - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - element_selected: self - .element_selected - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - element_disabled: self - .element_disabled - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - element_selection_background: self - .element_selection_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - drop_target_background: self - .drop_target_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - drop_target_border: self - .drop_target_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ghost_element_background: self - .ghost_element_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ghost_element_hover: self - .ghost_element_hover - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ghost_element_active: self - .ghost_element_active - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ghost_element_selected: self - .ghost_element_selected - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ghost_element_disabled: self - .ghost_element_disabled - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - text: self - .text - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - text_muted: self - .text_muted - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - text_placeholder: self - .text_placeholder - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - text_disabled: self - .text_disabled - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - text_accent: self - .text_accent - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - icon: self - .icon - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - icon_muted: self - .icon_muted - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - icon_disabled: self - .icon_disabled - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - icon_placeholder: self - .icon_placeholder - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - icon_accent: self - .icon_accent - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - debugger_accent: self - .debugger_accent - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - status_bar_background: self - .status_bar_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - title_bar_background: self - .title_bar_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - title_bar_inactive_background: self - .title_bar_inactive_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - toolbar_background: self - .toolbar_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - tab_bar_background: self - .tab_bar_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - tab_inactive_background: self - .tab_inactive_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - tab_active_background: self - .tab_active_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - search_match_background: self - .search_match_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - panel_background, - panel_focused_border: self - .panel_focused_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - panel_indent_guide: self - .panel_indent_guide - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - panel_indent_guide_hover: self - .panel_indent_guide_hover - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - panel_indent_guide_active: self - .panel_indent_guide_active - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - panel_overlay_background: self - .panel_overlay_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(panel_background.map(ensure_opaque)), - panel_overlay_hover: self - .panel_overlay_hover - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(panel_background - .zip(element_hover) - .map(|(panel_bg, hover_bg)| panel_bg.blend(hover_bg)) - .map(ensure_opaque)), - pane_focused_border: self - .pane_focused_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - pane_group_border: self - .pane_group_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(border), - scrollbar_thumb_background, - scrollbar_thumb_hover_background, - scrollbar_thumb_active_background, - scrollbar_thumb_border, - scrollbar_track_background: self - .scrollbar_track_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - scrollbar_track_border: self - .scrollbar_track_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - minimap_thumb_background: self - .minimap_thumb_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(scrollbar_thumb_background.map(ensure_non_opaque)), - minimap_thumb_hover_background: self - .minimap_thumb_hover_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(scrollbar_thumb_hover_background.map(ensure_non_opaque)), - minimap_thumb_active_background: self - .minimap_thumb_active_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(scrollbar_thumb_active_background.map(ensure_non_opaque)), - minimap_thumb_border: self - .minimap_thumb_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(scrollbar_thumb_border), - editor_foreground: self - .editor_foreground - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_background: self - .editor_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_gutter_background: self - .editor_gutter_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_subheader_background: self - .editor_subheader_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_active_line_background: self - .editor_active_line_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_highlighted_line_background: self - .editor_highlighted_line_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_debugger_active_line_background: self - .editor_debugger_active_line_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_line_number: self - .editor_line_number - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_hover_line_number: self - .editor_hover_line_number - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_active_line_number: self - .editor_active_line_number - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_invisible: self - .editor_invisible - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_wrap_guide: self - .editor_wrap_guide - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_active_wrap_guide: self - .editor_active_wrap_guide - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_indent_guide: self - .editor_indent_guide - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_indent_guide_active: self - .editor_indent_guide_active - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_document_highlight_read_background, - editor_document_highlight_write_background: self - .editor_document_highlight_write_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - editor_document_highlight_bracket_background: self - .editor_document_highlight_bracket_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `editor.document_highlight.read_background`, for backwards compatibility. - .or(editor_document_highlight_read_background), - terminal_background: self - .terminal_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_background: self - .terminal_ansi_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_foreground: self - .terminal_foreground - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_bright_foreground: self - .terminal_bright_foreground - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_dim_foreground: self - .terminal_dim_foreground - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_black: self - .terminal_ansi_black - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_black: self - .terminal_ansi_bright_black - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_black: self - .terminal_ansi_dim_black - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_red: self - .terminal_ansi_red - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_red: self - .terminal_ansi_bright_red - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_red: self - .terminal_ansi_dim_red - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_green: self - .terminal_ansi_green - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_green: self - .terminal_ansi_bright_green - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_green: self - .terminal_ansi_dim_green - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_yellow: self - .terminal_ansi_yellow - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_yellow: self - .terminal_ansi_bright_yellow - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_yellow: self - .terminal_ansi_dim_yellow - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_blue: self - .terminal_ansi_blue - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_blue: self - .terminal_ansi_bright_blue - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_blue: self - .terminal_ansi_dim_blue - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_magenta: self - .terminal_ansi_magenta - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_magenta: self - .terminal_ansi_bright_magenta - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_magenta: self - .terminal_ansi_dim_magenta - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_cyan: self - .terminal_ansi_cyan - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_cyan: self - .terminal_ansi_bright_cyan - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_cyan: self - .terminal_ansi_dim_cyan - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_white: self - .terminal_ansi_white - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_bright_white: self - .terminal_ansi_bright_white - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - terminal_ansi_dim_white: self - .terminal_ansi_dim_white - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - link_text_hover: self - .link_text_hover - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - version_control_added: self - .version_control_added - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `created`, for backwards compatibility. - .or(status_colors.created), - version_control_deleted: self - .version_control_deleted - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `deleted`, for backwards compatibility. - .or(status_colors.deleted), - version_control_modified: self - .version_control_modified - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `modified`, for backwards compatibility. - .or(status_colors.modified), - version_control_renamed: self - .version_control_renamed - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `modified`, for backwards compatibility. - .or(status_colors.modified), - version_control_conflict: self - .version_control_conflict - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `ignored`, for backwards compatibility. - .or(status_colors.ignored), - version_control_ignored: self - .version_control_ignored - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - // Fall back to `conflict`, for backwards compatibility. - .or(status_colors.ignored), - #[allow(deprecated)] - version_control_conflict_marker_ours: self - .version_control_conflict_marker_ours - .as_ref() - .or(self.version_control_conflict_ours_background.as_ref()) - .and_then(|color| try_parse_color(color).ok()), - #[allow(deprecated)] - version_control_conflict_marker_theirs: self - .version_control_conflict_marker_theirs - .as_ref() - .or(self.version_control_conflict_theirs_background.as_ref()) - .and_then(|color| try_parse_color(color).ok()), - } - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] -#[serde(default)] -pub struct StatusColorsContent { - /// Indicates some kind of conflict, like a file changed on disk while it was open, or - /// merge conflicts in a Git repository. - #[serde(rename = "conflict")] - pub conflict: Option, - - #[serde(rename = "conflict.background")] - pub conflict_background: Option, - - #[serde(rename = "conflict.border")] - pub conflict_border: Option, - - /// Indicates something new, like a new file added to a Git repository. - #[serde(rename = "created")] - pub created: Option, - - #[serde(rename = "created.background")] - pub created_background: Option, - - #[serde(rename = "created.border")] - pub created_border: Option, - - /// Indicates that something no longer exists, like a deleted file. - #[serde(rename = "deleted")] - pub deleted: Option, - - #[serde(rename = "deleted.background")] - pub deleted_background: Option, - - #[serde(rename = "deleted.border")] - pub deleted_border: Option, - - /// Indicates a system error, a failed operation or a diagnostic error. - #[serde(rename = "error")] - pub error: Option, - - #[serde(rename = "error.background")] - pub error_background: Option, - - #[serde(rename = "error.border")] - pub error_border: Option, - - /// Represents a hidden status, such as a file being hidden in a file tree. - #[serde(rename = "hidden")] - pub hidden: Option, - - #[serde(rename = "hidden.background")] - pub hidden_background: Option, - - #[serde(rename = "hidden.border")] - pub hidden_border: Option, - - /// Indicates a hint or some kind of additional information. - #[serde(rename = "hint")] - pub hint: Option, - - #[serde(rename = "hint.background")] - pub hint_background: Option, - - #[serde(rename = "hint.border")] - pub hint_border: Option, - - /// Indicates that something is deliberately ignored, such as a file or operation ignored by Git. - #[serde(rename = "ignored")] - pub ignored: Option, - - #[serde(rename = "ignored.background")] - pub ignored_background: Option, - - #[serde(rename = "ignored.border")] - pub ignored_border: Option, - - /// Represents informational status updates or messages. - #[serde(rename = "info")] - pub info: Option, - - #[serde(rename = "info.background")] - pub info_background: Option, - - #[serde(rename = "info.border")] - pub info_border: Option, - - /// Indicates a changed or altered status, like a file that has been edited. - #[serde(rename = "modified")] - pub modified: Option, - - #[serde(rename = "modified.background")] - pub modified_background: Option, - - #[serde(rename = "modified.border")] - pub modified_border: Option, - - /// Indicates something that is predicted, like automatic code completion, or generated code. - #[serde(rename = "predictive")] - pub predictive: Option, - - #[serde(rename = "predictive.background")] - pub predictive_background: Option, - - #[serde(rename = "predictive.border")] - pub predictive_border: Option, - - /// Represents a renamed status, such as a file that has been renamed. - #[serde(rename = "renamed")] - pub renamed: Option, - - #[serde(rename = "renamed.background")] - pub renamed_background: Option, - - #[serde(rename = "renamed.border")] - pub renamed_border: Option, - - /// Indicates a successful operation or task completion. - #[serde(rename = "success")] - pub success: Option, - - #[serde(rename = "success.background")] - pub success_background: Option, - - #[serde(rename = "success.border")] - pub success_border: Option, - - /// Indicates some kind of unreachable status, like a block of code that can never be reached. - #[serde(rename = "unreachable")] - pub unreachable: Option, - - #[serde(rename = "unreachable.background")] - pub unreachable_background: Option, - - #[serde(rename = "unreachable.border")] - pub unreachable_border: Option, - - /// Represents a warning status, like an operation that is about to fail. - #[serde(rename = "warning")] - pub warning: Option, - - #[serde(rename = "warning.background")] - pub warning_background: Option, - - #[serde(rename = "warning.border")] - pub warning_border: Option, -} - -impl StatusColorsContent { - /// Returns a [`StatusColorsRefinement`] based on the colors in the [`StatusColorsContent`]. - pub fn status_colors_refinement(&self) -> StatusColorsRefinement { - StatusColorsRefinement { - conflict: self - .conflict - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - conflict_background: self - .conflict_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - conflict_border: self - .conflict_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - created: self - .created - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - created_background: self - .created_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - created_border: self - .created_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - deleted: self - .deleted - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - deleted_background: self - .deleted_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - deleted_border: self - .deleted_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - error: self - .error - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - error_background: self - .error_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - error_border: self - .error_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - hidden: self - .hidden - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - hidden_background: self - .hidden_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - hidden_border: self - .hidden_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - hint: self - .hint - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - hint_background: self - .hint_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - hint_border: self - .hint_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ignored: self - .ignored - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ignored_background: self - .ignored_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - ignored_border: self - .ignored_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - info: self - .info - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - info_background: self - .info_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - info_border: self - .info_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - modified: self - .modified - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - modified_background: self - .modified_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - modified_border: self - .modified_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - predictive: self - .predictive - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - predictive_background: self - .predictive_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - predictive_border: self - .predictive_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - renamed: self - .renamed - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - renamed_background: self - .renamed_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - renamed_border: self - .renamed_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - success: self - .success - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - success_background: self - .success_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - success_border: self - .success_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - unreachable: self - .unreachable - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - unreachable_background: self - .unreachable_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - unreachable_border: self - .unreachable_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - warning: self - .warning - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - warning_background: self - .warning_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - warning_border: self - .warning_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct AccentContent(pub Option); - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct PlayerColorContent { - pub cursor: Option, - pub background: Option, - pub selection: Option, -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum FontStyleContent { - Normal, - Italic, - Oblique, -} - -impl From for FontStyle { - fn from(value: FontStyleContent) -> Self { - match value { - FontStyleContent::Normal => FontStyle::Normal, - FontStyleContent::Italic => FontStyle::Italic, - FontStyleContent::Oblique => FontStyle::Oblique, - } - } -} - -#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, JsonSchema_repr, PartialEq)] -#[repr(u16)] -pub enum FontWeightContent { - Thin = 100, - ExtraLight = 200, - Light = 300, - Normal = 400, - Medium = 500, - Semibold = 600, - Bold = 700, - ExtraBold = 800, - Black = 900, -} - -impl From for FontWeight { - fn from(value: FontWeightContent) -> Self { - match value { - FontWeightContent::Thin => FontWeight::THIN, - FontWeightContent::ExtraLight => FontWeight::EXTRA_LIGHT, - FontWeightContent::Light => FontWeight::LIGHT, - FontWeightContent::Normal => FontWeight::NORMAL, - FontWeightContent::Medium => FontWeight::MEDIUM, - FontWeightContent::Semibold => FontWeight::SEMIBOLD, - FontWeightContent::Bold => FontWeight::BOLD, - FontWeightContent::ExtraBold => FontWeight::EXTRA_BOLD, - FontWeightContent::Black => FontWeight::BLACK, - } + .or(panel_background + .zip(element_hover) + .map(|(panel_bg, hover_bg)| panel_bg.blend(hover_bg)) + .map(ensure_opaque)), + pane_focused_border: this + .pane_focused_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + pane_group_border: this + .pane_group_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(border), + scrollbar_thumb_background, + scrollbar_thumb_hover_background, + scrollbar_thumb_active_background, + scrollbar_thumb_border, + scrollbar_track_background: this + .scrollbar_track_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + scrollbar_track_border: this + .scrollbar_track_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + minimap_thumb_background: this + .minimap_thumb_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_background.map(ensure_non_opaque)), + minimap_thumb_hover_background: this + .minimap_thumb_hover_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_hover_background.map(ensure_non_opaque)), + minimap_thumb_active_background: this + .minimap_thumb_active_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_active_background.map(ensure_non_opaque)), + minimap_thumb_border: this + .minimap_thumb_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_border), + editor_foreground: this + .editor_foreground + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_background: this + .editor_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_gutter_background: this + .editor_gutter_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_subheader_background: this + .editor_subheader_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_active_line_background: this + .editor_active_line_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_highlighted_line_background: this + .editor_highlighted_line_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_debugger_active_line_background: this + .editor_debugger_active_line_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_line_number: this + .editor_line_number + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_hover_line_number: this + .editor_hover_line_number + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_active_line_number: this + .editor_active_line_number + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_invisible: this + .editor_invisible + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_wrap_guide: this + .editor_wrap_guide + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_active_wrap_guide: this + .editor_active_wrap_guide + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_indent_guide: this + .editor_indent_guide + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_indent_guide_active: this + .editor_indent_guide_active + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_document_highlight_read_background, + editor_document_highlight_write_background: this + .editor_document_highlight_write_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + editor_document_highlight_bracket_background: this + .editor_document_highlight_bracket_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `editor.document_highlight.read_background`, for backwards compatibility. + .or(editor_document_highlight_read_background), + terminal_background: this + .terminal_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_background: this + .terminal_ansi_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_foreground: this + .terminal_foreground + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_bright_foreground: this + .terminal_bright_foreground + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_dim_foreground: this + .terminal_dim_foreground + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_black: this + .terminal_ansi_black + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_black: this + .terminal_ansi_bright_black + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_black: this + .terminal_ansi_dim_black + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_red: this + .terminal_ansi_red + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_red: this + .terminal_ansi_bright_red + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_red: this + .terminal_ansi_dim_red + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_green: this + .terminal_ansi_green + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_green: this + .terminal_ansi_bright_green + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_green: this + .terminal_ansi_dim_green + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_yellow: this + .terminal_ansi_yellow + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_yellow: this + .terminal_ansi_bright_yellow + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_yellow: this + .terminal_ansi_dim_yellow + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_blue: this + .terminal_ansi_blue + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_blue: this + .terminal_ansi_bright_blue + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_blue: this + .terminal_ansi_dim_blue + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_magenta: this + .terminal_ansi_magenta + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_magenta: this + .terminal_ansi_bright_magenta + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_magenta: this + .terminal_ansi_dim_magenta + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_cyan: this + .terminal_ansi_cyan + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_cyan: this + .terminal_ansi_bright_cyan + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_cyan: this + .terminal_ansi_dim_cyan + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_white: this + .terminal_ansi_white + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_bright_white: this + .terminal_ansi_bright_white + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + terminal_ansi_dim_white: this + .terminal_ansi_dim_white + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + link_text_hover: this + .link_text_hover + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + version_control_added: this + .version_control_added + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `created`, for backwards compatibility. + .or(status_colors.created), + version_control_deleted: this + .version_control_deleted + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `deleted`, for backwards compatibility. + .or(status_colors.deleted), + version_control_modified: this + .version_control_modified + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `modified`, for backwards compatibility. + .or(status_colors.modified), + version_control_renamed: this + .version_control_renamed + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `modified`, for backwards compatibility. + .or(status_colors.modified), + version_control_conflict: this + .version_control_conflict + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `ignored`, for backwards compatibility. + .or(status_colors.ignored), + version_control_ignored: this + .version_control_ignored + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + // Fall back to `conflict`, for backwards compatibility. + .or(status_colors.ignored), + #[allow(deprecated)] + version_control_conflict_marker_ours: this + .version_control_conflict_marker_ours + .as_ref() + .or(this.version_control_conflict_ours_background.as_ref()) + .and_then(|color| try_parse_color(color).ok()), + #[allow(deprecated)] + version_control_conflict_marker_theirs: this + .version_control_conflict_marker_theirs + .as_ref() + .or(this.version_control_conflict_theirs_background.as_ref()) + .and_then(|color| try_parse_color(color).ok()), } } -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] -#[serde(default)] -pub struct HighlightStyleContent { - pub color: Option, - - #[serde(deserialize_with = "treat_error_as_none")] - pub background_color: Option, - - #[serde(deserialize_with = "treat_error_as_none")] - pub font_style: Option, - - #[serde(deserialize_with = "treat_error_as_none")] - pub font_weight: Option, -} +pub(crate) fn try_parse_color(color: &str) -> anyhow::Result { + let rgba = gpui::Rgba::try_from(color)?; + let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a)); + let hsla = palette::Hsla::from_color(rgba); -impl HighlightStyleContent { - pub fn is_empty(&self) -> bool { - self.color.is_none() - && self.background_color.is_none() - && self.font_style.is_none() - && self.font_weight.is_none() - } -} + let hsla = gpui::hsla( + hsla.hue.into_positive_degrees() / 360., + hsla.saturation, + hsla.lightness, + hsla.alpha, + ); -fn treat_error_as_none<'de, T, D>(deserializer: D) -> Result, D::Error> -where - T: Deserialize<'de>, - D: Deserializer<'de>, -{ - let value: Value = Deserialize::deserialize(deserializer)?; - Ok(T::deserialize(value).ok()) + Ok(hsla) } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 8409c60b22b03b8d917b84ae20229dc2db63fe4a..2efaa4f20c316952a300e669e8b93eee8e5bad39 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -1,22 +1,23 @@ use crate::fallback_themes::zed_default_dark; use crate::{ Appearance, DEFAULT_ICON_THEME_NAME, IconTheme, IconThemeNotFoundError, SyntaxTheme, Theme, - ThemeNotFoundError, ThemeRegistry, ThemeStyleContent, + ThemeNotFoundError, ThemeRegistry, status_colors_refinement, syntax_overrides, + theme_colors_refinement, }; -use anyhow::Result; use collections::HashMap; use derive_more::{Deref, DerefMut}; use gpui::{ - App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels, - SharedString, Subscription, Window, px, + App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, SharedString, + Subscription, Window, px, }; use refineable::Refineable; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; -use settings::{ParameterizedJsonSchema, Settings, SettingsKey, SettingsSources, SettingsUi}; +pub use settings::{FontFamilyName, IconThemeName, ThemeMode, ThemeName}; +use settings::{ParameterizedJsonSchema, Settings, SettingsContent}; use std::sync::Arc; -use util::ResultExt as _; use util::schemars::replace_subschema; +use util::{MergeFrom, ResultExt as _}; const MIN_FONT_SIZE: Pixels = px(6.0); const MAX_FONT_SIZE: Pixels = px(100.0); @@ -86,6 +87,16 @@ impl From for String { } } +impl From for UiDensity { + fn from(val: settings::UiDensity) -> Self { + match val { + settings::UiDensity::Compact => Self::Compact, + settings::UiDensity::Default => Self::Default, + settings::UiDensity::Comfortable => Self::Comfortable, + } + } +} + /// Customizable settings for the UI and theme system. #[derive(Clone, PartialEq)] pub struct ThemeSettings { @@ -119,9 +130,9 @@ pub struct ThemeSettings { /// Manual overrides for the active theme. /// /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078) - pub experimental_theme_overrides: Option, + pub experimental_theme_overrides: Option, /// Manual overrides per theme - pub theme_overrides: HashMap, + pub theme_overrides: HashMap, /// The current icon theme selection. pub icon_theme_selection: Option, /// The active icon theme. @@ -259,6 +270,45 @@ pub struct AgentFontSize(Pixels); impl Global for AgentFontSize {} +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, _params, cx| { + replace_subschema::(generator, || json_schema!({ + "type": "string", + "enum": ThemeRegistry::global(cx).list_names(), + })) + } + } +} + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, _params, cx| { + replace_subschema::(generator, || json_schema!({ + "type": "string", + "enum": ThemeRegistry::global(cx) + .list_icon_themes() + .into_iter() + .map(|icon_theme| icon_theme.name) + .collect::>(), + })) + } + } +} + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, params, _cx| { + replace_subschema::(generator, || { + json_schema!({ + "type": "string", + "enum": params.font_names, + }) + }) + } + } +} + /// Represents the selection of a theme, which can be either static or dynamic. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(untagged)] @@ -277,24 +327,15 @@ pub enum ThemeSelection { }, } -// TODO: Rename ThemeMode -> ThemeAppearanceMode -/// The mode use to select a theme. -/// -/// `Light` and `Dark` will select their respective themes. -/// -/// `System` will select the theme based on the system's appearance. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ThemeMode { - /// Use the specified `light` theme. - Light, - - /// Use the specified `dark` theme. - Dark, - - /// Use the theme based on the system's appearance. - #[default] - System, +impl From for ThemeSelection { + fn from(selection: settings::ThemeSelection) -> Self { + match selection { + settings::ThemeSelection::Static(theme) => ThemeSelection::Static(theme), + settings::ThemeSelection::Dynamic { mode, light, dark } => { + ThemeSelection::Dynamic { mode, light, dark } + } + } + } } impl ThemeSelection { @@ -323,15 +364,13 @@ impl ThemeSelection { } /// Represents the selection of an icon theme, which can be either static or dynamic. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum IconThemeSelection { /// A static icon theme selection, represented by a single icon theme name. Static(IconThemeName), /// A dynamic icon theme selection, which can change based on the [`ThemeMode`]. Dynamic { /// The mode used to determine which theme to use. - #[serde(default)] mode: ThemeMode, /// The icon theme to use for light mode. light: IconThemeName, @@ -340,6 +379,17 @@ pub enum IconThemeSelection { }, } +impl From for IconThemeSelection { + fn from(selection: settings::IconThemeSelection) -> Self { + match selection { + settings::IconThemeSelection::Static(theme) => IconThemeSelection::Static(theme), + settings::IconThemeSelection::Dynamic { mode, light, dark } => { + IconThemeSelection::Dynamic { mode, light, dark } + } + } + } +} + impl IconThemeSelection { /// Returns the icon theme name based on the given [`Appearance`]. pub fn icon_theme(&self, system_appearance: Appearance) -> &str { @@ -365,189 +415,117 @@ impl IconThemeSelection { } } -/// Settings for rendering text in UI and text buffers. -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct ThemeSettingsContent { - /// The default font size for text in the UI. - #[serde(default)] - pub ui_font_size: Option, - /// The name of a font to use for rendering in the UI. - #[serde(default)] - pub ui_font_family: Option, - /// The font fallbacks to use for rendering in the UI. - #[serde(default)] - #[schemars(default = "default_font_fallbacks")] - #[schemars(extend("uniqueItems" = true))] - pub ui_font_fallbacks: Option>, - /// The OpenType features to enable for text in the UI. - #[serde(default)] - #[schemars(default = "default_font_features")] - pub ui_font_features: Option, - /// The weight of the UI font in CSS units from 100 to 900. - #[serde(default)] - pub ui_font_weight: Option, - /// The name of a font to use for rendering in text buffers. - #[serde(default)] - pub buffer_font_family: Option, - /// The font fallbacks to use for rendering in text buffers. - #[serde(default)] - #[schemars(extend("uniqueItems" = true))] - pub buffer_font_fallbacks: Option>, - /// The default font size for rendering in text buffers. - #[serde(default)] - pub buffer_font_size: Option, - /// The weight of the editor font in CSS units from 100 to 900. - #[serde(default)] - pub buffer_font_weight: Option, - /// The buffer's line height. - #[serde(default)] - pub buffer_line_height: Option, - /// The OpenType features to enable for rendering in text buffers. - #[serde(default)] - #[schemars(default = "default_font_features")] - pub buffer_font_features: Option, - /// The font size for the agent panel. Falls back to the UI font size if unset. - #[serde(default)] - pub agent_font_size: Option>, - /// The name of the Zed theme to use. - #[serde(default)] - pub theme: Option, - /// The name of the icon theme to use. - #[serde(default)] - pub icon_theme: Option, - - /// UNSTABLE: Expect many elements to be broken. - /// - // Controls the density of the UI. - #[serde(rename = "unstable.ui_density", default)] - pub ui_density: Option, - - /// How much to fade out unused code. - #[serde(default)] - pub unnecessary_code_fade: Option, - - /// EXPERIMENTAL: Overrides for the current theme. - /// - /// These values will override the ones on the current theme specified in `theme`. - #[serde(rename = "experimental.theme_overrides", default)] - pub experimental_theme_overrides: Option, - - /// Overrides per theme - /// - /// These values will override the ones on the specified theme - #[serde(default)] - pub theme_overrides: HashMap, -} - -fn default_font_features() -> Option { - Some(FontFeatures::default()) -} - -fn default_font_fallbacks() -> Option { - Some(FontFallbacks::default()) -} - -impl ThemeSettingsContent { - /// Sets the theme for the given appearance to the theme with the specified name. - pub fn set_theme(&mut self, theme_name: impl Into>, appearance: Appearance) { - if let Some(selection) = self.theme.as_mut() { - let theme_to_update = match selection { - ThemeSelection::Static(theme) => theme, - ThemeSelection::Dynamic { mode, light, dark } => match mode { - ThemeMode::Light => light, - ThemeMode::Dark => dark, - ThemeMode::System => match appearance { - Appearance::Light => light, - Appearance::Dark => dark, - }, +// impl ThemeSettingsContent { +/// Sets the theme for the given appearance to the theme with the specified name. +pub fn set_theme( + current: &mut SettingsContent, + theme_name: impl Into>, + appearance: Appearance, +) { + if let Some(selection) = current.theme.theme.as_mut() { + let theme_to_update = match selection { + settings::ThemeSelection::Static(theme) => theme, + settings::ThemeSelection::Dynamic { mode, light, dark } => match mode { + ThemeMode::Light => light, + ThemeMode::Dark => dark, + ThemeMode::System => match appearance { + Appearance::Light => light, + Appearance::Dark => dark, }, - }; - - *theme_to_update = ThemeName(theme_name.into()); - } else { - self.theme = Some(ThemeSelection::Static(ThemeName(theme_name.into()))); - } - } + }, + }; - /// Sets the icon theme for the given appearance to the icon theme with the specified name. - pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) { - if let Some(selection) = self.icon_theme.as_mut() { - let icon_theme_to_update = match selection { - IconThemeSelection::Static(theme) => theme, - IconThemeSelection::Dynamic { mode, light, dark } => match mode { - ThemeMode::Light => light, - ThemeMode::Dark => dark, - ThemeMode::System => match appearance { - Appearance::Light => light, - Appearance::Dark => dark, - }, + *theme_to_update = ThemeName(theme_name.into()); + } else { + current.theme.theme = Some(settings::ThemeSelection::Static(ThemeName( + theme_name.into(), + ))); + } +} + +/// Sets the icon theme for the given appearance to the icon theme with the specified name. +pub fn set_icon_theme( + current: &mut SettingsContent, + icon_theme_name: String, + appearance: Appearance, +) { + if let Some(selection) = current.theme.icon_theme.as_mut() { + let icon_theme_to_update = match selection { + settings::IconThemeSelection::Static(theme) => theme, + settings::IconThemeSelection::Dynamic { mode, light, dark } => match mode { + ThemeMode::Light => light, + ThemeMode::Dark => dark, + ThemeMode::System => match appearance { + Appearance::Light => light, + Appearance::Dark => dark, }, - }; - - *icon_theme_to_update = IconThemeName(icon_theme_name.into()); - } else { - self.icon_theme = Some(IconThemeSelection::Static(IconThemeName( - icon_theme_name.into(), - ))); - } - } + }, + }; - /// Sets the mode for the theme. - pub fn set_mode(&mut self, mode: ThemeMode) { - if let Some(selection) = self.theme.as_mut() { - match selection { - ThemeSelection::Static(theme) => { - // If the theme was previously set to a single static theme, - // we don't know whether it was a light or dark theme, so we - // just use it for both. - self.theme = Some(ThemeSelection::Dynamic { - mode, - light: theme.clone(), - dark: theme.clone(), - }); - } - ThemeSelection::Dynamic { - mode: mode_to_update, - .. - } => *mode_to_update = mode, + *icon_theme_to_update = IconThemeName(icon_theme_name.into()); + } else { + current.theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName( + icon_theme_name.into(), + ))); + } +} + +/// Sets the mode for the theme. +pub fn set_mode(content: &mut SettingsContent, mode: ThemeMode) { + let theme = content.theme.as_mut(); + + if let Some(selection) = theme.theme.as_mut() { + match selection { + settings::ThemeSelection::Static(theme) => { + // If the theme was previously set to a single static theme, + // we don't know whether it was a light or dark theme, so we + // just use it for both. + *selection = settings::ThemeSelection::Dynamic { + mode, + light: theme.clone(), + dark: theme.clone(), + }; } - } else { - self.theme = Some(ThemeSelection::Dynamic { - mode, - light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()), - dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()), - }); + settings::ThemeSelection::Dynamic { + mode: mode_to_update, + .. + } => *mode_to_update = mode, } - - if let Some(selection) = self.icon_theme.as_mut() { - match selection { - IconThemeSelection::Static(icon_theme) => { - // If the icon theme was previously set to a single static - // theme, we don't know whether it was a light or dark - // theme, so we just use it for both. - self.icon_theme = Some(IconThemeSelection::Dynamic { - mode, - light: icon_theme.clone(), - dark: icon_theme.clone(), - }); - } - IconThemeSelection::Dynamic { - mode: mode_to_update, - .. - } => *mode_to_update = mode, + } else { + theme.theme = Some(settings::ThemeSelection::Dynamic { + mode, + light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()), + dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()), + }); + } + + if let Some(selection) = theme.icon_theme.as_mut() { + match selection { + settings::IconThemeSelection::Static(icon_theme) => { + // If the icon theme was previously set to a single static + // theme, we don't know whether it was a light or dark + // theme, so we just use it for both. + *selection = settings::IconThemeSelection::Dynamic { + mode, + light: icon_theme.clone(), + dark: icon_theme.clone(), + }; } - } else { - self.icon_theme = Some(IconThemeSelection::Static(IconThemeName( - DEFAULT_ICON_THEME_NAME.into(), - ))); + settings::IconThemeSelection::Dynamic { + mode: mode_to_update, + .. + } => *mode_to_update = mode, } + } else { + theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName( + DEFAULT_ICON_THEME_NAME.into(), + ))); } } +// } /// The buffer's line height. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Debug, PartialEq, Default)] pub enum BufferLineHeight { /// A less dense line height. #[default] @@ -555,21 +533,19 @@ pub enum BufferLineHeight { /// The default line height. Standard, /// A custom line height, where 1.0 is the font's height. Must be at least 1.0. - Custom(#[serde(deserialize_with = "deserialize_line_height")] f32), + Custom(f32), } -fn deserialize_line_height<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let value = f32::deserialize(deserializer)?; - if value < 1.0 { - return Err(serde::de::Error::custom( - "buffer_line_height.custom must be at least 1.0", - )); +impl From for BufferLineHeight { + fn from(value: settings::BufferLineHeight) -> Self { + match value { + settings::BufferLineHeight::Comfortable => BufferLineHeight::Comfortable, + settings::BufferLineHeight::Standard => BufferLineHeight::Standard, + settings::BufferLineHeight::Custom(line_height) => { + BufferLineHeight::Custom(line_height) + } + } } - - Ok(value) } impl BufferLineHeight { @@ -681,24 +657,22 @@ impl ThemeSettings { } } - fn modify_theme(base_theme: &mut Theme, theme_overrides: &ThemeStyleContent) { + fn modify_theme(base_theme: &mut Theme, theme_overrides: &settings::ThemeStyleContent) { if let Some(window_background_appearance) = theme_overrides.window_background_appearance { base_theme.styles.window_background_appearance = window_background_appearance.into(); } + let status_color_refinement = status_colors_refinement(&theme_overrides.status); - base_theme - .styles - .colors - .refine(&theme_overrides.theme_colors_refinement()); - base_theme - .styles - .status - .refine(&theme_overrides.status_colors_refinement()); + base_theme.styles.colors.refine(&theme_colors_refinement( + &theme_overrides.colors, + &status_color_refinement, + )); + base_theme.styles.status.refine(&status_color_refinement); base_theme.styles.player.merge(&theme_overrides.players); base_theme.styles.accents.merge(&theme_overrides.accents); base_theme.styles.syntax = SyntaxTheme::merge( base_theme.styles.syntax.clone(), - theme_overrides.syntax_overrides(), + syntax_overrides(&theme_overrides), ); } @@ -818,294 +792,178 @@ fn clamp_font_weight(weight: f32) -> FontWeight { FontWeight(weight.clamp(100., 950.)) } -impl settings::Settings for ThemeSettings { - type FileContent = ThemeSettingsContent; +/// font fallback from settings +pub fn font_fallbacks_from_settings( + fallbacks: Option>, +) -> Option { + fallbacks.map(|fallbacks| { + FontFallbacks::from_fonts( + fallbacks + .into_iter() + .map(|font_family| font_family.0.to_string()) + .collect(), + ) + }) +} - fn load(sources: SettingsSources, cx: &mut App) -> Result { +impl settings::Settings for ThemeSettings { + fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self { + let content = &content.theme; + // todo(settings_refactor). This should *not* require cx... let themes = ThemeRegistry::default_global(cx); let system_appearance = SystemAppearance::default_global(cx); - - fn font_fallbacks_from_settings( - fallbacks: Option>, - ) -> Option { - fallbacks.map(|fallbacks| { - FontFallbacks::from_fonts( - fallbacks - .into_iter() - .map(|font_family| font_family.0.to_string()) - .collect(), - ) - }) - } - - let defaults = sources.default; - let mut this = Self { - ui_font_size: defaults.ui_font_size.unwrap().into(), + let theme_selection: ThemeSelection = content.theme.clone().unwrap().into(); + let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into(); + Self { + ui_font_size: content.ui_font_size.unwrap().into(), ui_font: Font { - family: defaults.ui_font_family.as_ref().unwrap().0.clone().into(), - features: defaults.ui_font_features.clone().unwrap(), - fallbacks: font_fallbacks_from_settings(defaults.ui_font_fallbacks.clone()), - weight: defaults.ui_font_weight.map(FontWeight).unwrap(), + family: content.ui_font_family.as_ref().unwrap().0.clone().into(), + features: content.ui_font_features.clone().unwrap(), + fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()), + weight: content.ui_font_weight.map(FontWeight).unwrap(), style: Default::default(), }, buffer_font: Font { - family: defaults + family: content .buffer_font_family .as_ref() .unwrap() .0 .clone() .into(), - features: defaults.buffer_font_features.clone().unwrap(), - fallbacks: font_fallbacks_from_settings(defaults.buffer_font_fallbacks.clone()), - weight: defaults.buffer_font_weight.map(FontWeight).unwrap(), + features: content.buffer_font_features.clone().unwrap(), + fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()), + weight: content.buffer_font_weight.map(FontWeight).unwrap(), style: FontStyle::default(), }, - buffer_font_size: defaults.buffer_font_size.unwrap().into(), - buffer_line_height: defaults.buffer_line_height.unwrap(), - agent_font_size: defaults.agent_font_size.flatten().map(Into::into), - theme_selection: defaults.theme.clone(), + buffer_font_size: content.buffer_font_size.unwrap().into(), + buffer_line_height: content.buffer_line_height.unwrap().into(), + agent_font_size: content.agent_font_size.flatten().map(Into::into), active_theme: themes - .get(defaults.theme.as_ref().unwrap().theme(*system_appearance)) + .get(theme_selection.theme(*system_appearance)) .or(themes.get(&zed_default_dark().name)) .unwrap(), + theme_selection: Some(theme_selection), experimental_theme_overrides: None, theme_overrides: HashMap::default(), - icon_theme_selection: defaults.icon_theme.clone(), - active_icon_theme: defaults - .icon_theme - .as_ref() - .and_then(|selection| { - themes - .get_icon_theme(selection.icon_theme(*system_appearance)) - .ok() - }) - .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()), - ui_density: defaults.ui_density.unwrap_or(UiDensity::Default), - unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0), - }; - - for value in sources - .user - .into_iter() - .chain(sources.release_channel) - .chain(sources.operating_system) - .chain(sources.profile) - .chain(sources.server) - { - if let Some(value) = value.ui_density { - this.ui_density = value; - } - - if let Some(value) = value.buffer_font_family.clone() { - this.buffer_font.family = value.0.into(); - } - if let Some(value) = value.buffer_font_features.clone() { - this.buffer_font.features = value; - } - if let Some(value) = value.buffer_font_fallbacks.clone() { - this.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value)); - } - if let Some(value) = value.buffer_font_weight { - this.buffer_font.weight = clamp_font_weight(value); - } + active_icon_theme: themes + .get_icon_theme(icon_theme_selection.icon_theme(*system_appearance)) + .ok() + .unwrap(), + icon_theme_selection: Some(icon_theme_selection), + ui_density: content.ui_density.unwrap_or_default().into(), + unnecessary_code_fade: content.unnecessary_code_fade.unwrap(), + } + } - if let Some(value) = value.ui_font_family.clone() { - this.ui_font.family = value.0.into(); - } - if let Some(value) = value.ui_font_features.clone() { - this.ui_font.features = value; - } - if let Some(value) = value.ui_font_fallbacks.clone() { - this.ui_font.fallbacks = font_fallbacks_from_settings(Some(value)); - } - if let Some(value) = value.ui_font_weight { - this.ui_font.weight = clamp_font_weight(value); - } + fn refine(&mut self, content: &SettingsContent, cx: &mut App) { + let value = &content.theme; - if let Some(value) = &value.theme { - this.theme_selection = Some(value.clone()); + let themes = ThemeRegistry::default_global(cx); + let system_appearance = SystemAppearance::default_global(cx); - let theme_name = value.theme(*system_appearance); + self.ui_density + .merge_from(&value.ui_density.map(Into::into)); - match themes.get(theme_name) { - Ok(theme) => { - this.active_theme = theme; - } - Err(err @ ThemeNotFoundError(_)) => { - if themes.extensions_loaded() { - log::error!("{err}"); - } - } - } - } + if let Some(value) = value.buffer_font_family.clone() { + self.buffer_font.family = value.0.into(); + } + if let Some(value) = value.buffer_font_features.clone() { + self.buffer_font.features = value; + } + if let Some(value) = value.buffer_font_fallbacks.clone() { + self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value)); + } + if let Some(value) = value.buffer_font_weight { + self.buffer_font.weight = clamp_font_weight(value); + } - this.experimental_theme_overrides - .clone_from(&value.experimental_theme_overrides); - this.theme_overrides.clone_from(&value.theme_overrides); - this.apply_theme_overrides(); + if let Some(value) = value.ui_font_family.clone() { + self.ui_font.family = value.0.into(); + } + if let Some(value) = value.ui_font_features.clone() { + self.ui_font.features = value; + } + if let Some(value) = value.ui_font_fallbacks.clone() { + self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value)); + } + if let Some(value) = value.ui_font_weight { + self.ui_font.weight = clamp_font_weight(value); + } - if let Some(value) = &value.icon_theme { - this.icon_theme_selection = Some(value.clone()); + if let Some(value) = &value.theme { + self.theme_selection = Some(value.clone().into()); - let icon_theme_name = value.icon_theme(*system_appearance); + let theme_name = self + .theme_selection + .as_ref() + .unwrap() + .theme(*system_appearance); - match themes.get_icon_theme(icon_theme_name) { - Ok(icon_theme) => { - this.active_icon_theme = icon_theme; - } - Err(err @ IconThemeNotFoundError(_)) => { - if themes.extensions_loaded() { - log::error!("{err}"); - } + match themes.get(theme_name) { + Ok(theme) => { + self.active_theme = theme; + } + Err(err @ ThemeNotFoundError(_)) => { + if themes.extensions_loaded() { + log::error!("{err}"); } } } - - merge( - &mut this.ui_font_size, - value.ui_font_size.map(Into::into).map(clamp_font_size), - ); - merge( - &mut this.buffer_font_size, - value.buffer_font_size.map(Into::into).map(clamp_font_size), - ); - merge( - &mut this.agent_font_size, - value - .agent_font_size - .map(|value| value.map(Into::into).map(clamp_font_size)), - ); - - merge(&mut this.buffer_line_height, value.buffer_line_height); - - // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely. - merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade); - this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9); } - Ok(this) - } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.f32_setting("editor.fontWeight", &mut current.buffer_font_weight); - vscode.f32_setting("editor.fontSize", &mut current.buffer_font_size); - if let Some(font) = vscode.read_string("editor.font") { - current.buffer_font_family = Some(FontFamilyName(font.into())); - } - // TODO: possibly map editor.fontLigatures to buffer_font_features? - } -} - -/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(transparent)] -pub struct ThemeName(pub Arc); - -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, _params, cx| { - replace_subschema::(generator, || json_schema!({ - "type": "string", - "enum": ThemeRegistry::global(cx).list_names(), - })) - } - } -} + self.experimental_theme_overrides + .clone_from(&value.experimental_theme_overrides); + self.theme_overrides.clone_from(&value.theme_overrides); -/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at -/// runtime. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(transparent)] -pub struct IconThemeName(pub Arc); + self.apply_theme_overrides(); -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, _params, cx| { - replace_subschema::(generator, || json_schema!({ - "type": "string", - "enum": ThemeRegistry::global(cx) - .list_icon_themes() - .into_iter() - .map(|icon_theme| icon_theme.name) - .collect::>(), - })) - } - } -} + if let Some(value) = &value.icon_theme { + self.icon_theme_selection = Some(value.clone().into()); -/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at -/// runtime. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(transparent)] -pub struct FontFamilyName(pub Arc); + let icon_theme_name = self + .icon_theme_selection + .as_ref() + .unwrap() + .icon_theme(*system_appearance); -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - replace_subschema::(generator, || { - json_schema!({ - "type": "string", - "enum": params.font_names, - }) - }) + match themes.get_icon_theme(icon_theme_name) { + Ok(icon_theme) => { + self.active_icon_theme = icon_theme; + } + Err(err @ IconThemeNotFoundError(_)) => { + if themes.extensions_loaded() { + log::error!("{err}"); + } + } + } } - } -} -fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; - } -} + self.ui_font_size + .merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size)); + self.buffer_font_size + .merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size)); + self.agent_font_size.merge_from( + &value + .agent_font_size + .map(|value| value.map(Into::into).map(clamp_font_size)), + ); -#[cfg(test)] -mod tests { - use super::*; - use serde_json::json; + self.buffer_line_height + .merge_from(&value.buffer_line_height.map(Into::into)); - #[test] - fn test_buffer_line_height_deserialize_valid() { - assert_eq!( - serde_json::from_value::(json!("comfortable")).unwrap(), - BufferLineHeight::Comfortable - ); - assert_eq!( - serde_json::from_value::(json!("standard")).unwrap(), - BufferLineHeight::Standard - ); - assert_eq!( - serde_json::from_value::(json!({"custom": 1.0})).unwrap(), - BufferLineHeight::Custom(1.0) - ); - assert_eq!( - serde_json::from_value::(json!({"custom": 1.5})).unwrap(), - BufferLineHeight::Custom(1.5) - ); + // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely. + self.unnecessary_code_fade + .merge_from(&value.unnecessary_code_fade); + self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9); } - #[test] - fn test_buffer_line_height_deserialize_invalid() { - assert!( - serde_json::from_value::(json!({"custom": 0.99})) - .err() - .unwrap() - .to_string() - .contains("buffer_line_height.custom must be at least 1.0") - ); - assert!( - serde_json::from_value::(json!({"custom": 0.0})) - .err() - .unwrap() - .to_string() - .contains("buffer_line_height.custom must be at least 1.0") - ); - assert!( - serde_json::from_value::(json!({"custom": -1.0})) - .err() - .unwrap() - .to_string() - .contains("buffer_line_height.custom must be at least 1.0") - ); + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { + vscode.f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight); + vscode.f32_setting("editor.fontSize", &mut current.theme.buffer_font_size); + if let Some(font) = vscode.read_string("editor.font") { + current.theme.buffer_font_family = Some(FontFamilyName(font.into())); + } + // TODO: possibly map editor.fontLigatures to buffer_font_features? } } diff --git a/crates/theme/src/styles/accents.rs b/crates/theme/src/styles/accents.rs index 52244fee46ce12edbbe361c3bec9bb80139f1167..49ae755bf811da4f5cbd53eb521ae3b300278c99 100644 --- a/crates/theme/src/styles/accents.rs +++ b/crates/theme/src/styles/accents.rs @@ -2,8 +2,8 @@ use gpui::Hsla; use serde::Deserialize; use crate::{ - AccentContent, amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple, - tomato, try_parse_color, + amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple, tomato, + try_parse_color, }; /// A collection of colors that are used to color indent aware lines in the editor. @@ -66,7 +66,7 @@ impl AccentColors { } /// Merges the given accent colors into this [`AccentColors`] instance. - pub fn merge(&mut self, accent_colors: &[AccentContent]) { + pub fn merge(&mut self, accent_colors: &[settings::AccentContent]) { if accent_colors.is_empty() { return; } diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index d10ac61ba6bfeabbf0642d832e68841c39176546..439dbdd437aa64e034004a4495e64a96e76ce87e 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -3,9 +3,7 @@ use gpui::Hsla; use serde::Deserialize; -use crate::{ - PlayerColorContent, amber, blue, jade, lime, orange, pink, purple, red, try_parse_color, -}; +use crate::{amber, blue, jade, lime, orange, pink, purple, red, try_parse_color}; #[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq)] pub struct PlayerColor { @@ -152,7 +150,7 @@ impl PlayerColors { } /// Merges the given player colors into this [`PlayerColors`] instance. - pub fn merge(&mut self, user_player_colors: &[PlayerColorContent]) { + pub fn merge(&mut self, user_player_colors: &[settings::PlayerColorContent]) { if user_player_colors.is_empty() { return; } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c54010b4b0c6fe56168c3e15271d3423921b15da..29269c15074a158acd742c7f6e258b0c94b7bebe 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -44,6 +44,10 @@ pub use crate::scale::*; pub use crate::schema::*; pub use crate::settings::*; pub use crate::styles::*; +pub use ::settings::{ + FontStyleContent, HighlightStyleContent, StatusColorsContent, ThemeColorsContent, + ThemeStyleContent, +}; /// Defines window border radius for platforms that use client side decorations. pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0); @@ -178,7 +182,7 @@ impl ThemeFamily { AppearanceContent::Light => StatusColors::light(), AppearanceContent::Dark => StatusColors::dark(), }; - let mut status_colors_refinement = theme.style.status_colors_refinement(); + let mut status_colors_refinement = status_colors_refinement(&theme.style.status); apply_status_color_defaults(&mut status_colors_refinement); refined_status_colors.refine(&status_colors_refinement); @@ -192,7 +196,8 @@ impl ThemeFamily { AppearanceContent::Light => ThemeColors::light(), AppearanceContent::Dark => ThemeColors::dark(), }; - let mut theme_colors_refinement = theme.style.theme_colors_refinement(); + let mut theme_colors_refinement = + theme_colors_refinement(&theme.style.colors, &status_colors_refinement); apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors); refined_theme_colors.refine(&theme_colors_refinement); diff --git a/crates/theme_importer/Cargo.toml b/crates/theme_importer/Cargo.toml index f9f7daa5b3bd7d48ce0631d26d6a3c21767e5d5e..2fef5a62498d9ac0abfef3913edbd1dc711e5e64 100644 --- a/crates/theme_importer/Cargo.toml +++ b/crates/theme_importer/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] anyhow.workspace = true clap = { workspace = true, features = ["derive"] } +collections.workspace = true gpui.workspace = true indexmap.workspace = true log.workspace = true diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index 339c88adea016dc260867ebce1128962bcd563e1..e10d21e4e297fef1ec96d98dea0e45de0ec7e73f 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use anyhow::{Context as _, Result}; use clap::Parser; -use indexmap::IndexMap; +use collections::IndexMap; use log::LevelFilter; use serde::Deserialize; use simplelog::ColorChoice; @@ -137,7 +137,7 @@ fn main() -> Result<()> { file_name: "".to_string(), }; - let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::new()); + let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::default()); let theme = converter.convert()?; let mut theme = serde_json::to_value(theme).unwrap(); diff --git a/crates/theme_importer/src/vscode/converter.rs b/crates/theme_importer/src/vscode/converter.rs index b3b846d91d5ac3a7e3d88983787d23fe9f0adece..e4a9769978e7f2b987c1404474d0f029bec7977b 100644 --- a/crates/theme_importer/src/vscode/converter.rs +++ b/crates/theme_importer/src/vscode/converter.rs @@ -1,9 +1,9 @@ use anyhow::Result; -use indexmap::IndexMap; +use collections::IndexMap; use strum::IntoEnumIterator; use theme::{ FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent, - ThemeColorsContent, ThemeContent, ThemeStyleContent, + ThemeColorsContent, ThemeContent, ThemeStyleContent, WindowBackgroundContent, }; use crate::ThemeMetadata; @@ -56,7 +56,7 @@ impl VsCodeThemeConverter { name: self.theme_metadata.name, appearance, style: ThemeStyleContent { - window_background_appearance: Some(theme::WindowBackgroundContent::Opaque), + window_background_appearance: Some(WindowBackgroundContent::Opaque), accents: Vec::new(), //TODO can we read this from the theme? colors: theme_colors, status: status_colors, @@ -212,7 +212,7 @@ impl VsCodeThemeConverter { } fn convert_syntax_theme(&self) -> Result> { - let mut highlight_styles = IndexMap::new(); + let mut highlight_styles = IndexMap::default(); for syntax_token in ZedSyntaxToken::iter() { let override_match = self diff --git a/crates/theme_selector/src/icon_theme_selector.rs b/crates/theme_selector/src/icon_theme_selector.rs index 5b26124079d9bf20edac38baa3dd49f2bb579456..5cd04aa8951dff890c68e8512e8082e5288f0f63 100644 --- a/crates/theme_selector/src/icon_theme_selector.rs +++ b/crates/theme_selector/src/icon_theme_selector.rs @@ -180,8 +180,8 @@ impl PickerDelegate for IconThemeSelectorDelegate { let appearance = Appearance::from(window.appearance()); - update_settings_file::(self.fs.clone(), cx, move |settings, _| { - settings.set_icon_theme(theme_name.to_string(), appearance); + update_settings_file(self.fs.clone(), cx, move |settings, _| { + theme::set_icon_theme(settings, theme_name.to_string(), appearance); }); self.selector diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 8cc450c97fb1fe0b8bdf7866156880471d0673d4..de41f3155f3c86cc5144c54c2b187ad0fd217b1c 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -238,8 +238,8 @@ impl PickerDelegate for ThemeSelectorDelegate { let appearance = Appearance::from(window.appearance()); - update_settings_file::(self.fs.clone(), cx, move |settings, _| { - settings.set_theme(theme_name.to_string(), appearance); + update_settings_file(self.fs.clone(), cx, move |settings, _| { + theme::set_theme(settings, theme_name.to_string(), appearance); }); self.selector diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 240c1fd74f0b6f49feef09ce20a81e5f54adfcb1..670d085756c5e27c45f8640e2d053a7f6929aead 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -1,17 +1,9 @@ -use db::anyhow; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +pub use settings::TitleBarVisibility; +use settings::{Settings, SettingsContent}; +use ui::App; +use util::MergeFrom; -#[derive(Copy, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub enum TitleBarVisibility { - Always, - Never, - HideInFullScreen, -} - -#[derive(Copy, Clone, Deserialize, Debug)] +#[derive(Copy, Clone, Debug)] pub struct TitleBarSettings { pub show: TitleBarVisibility, pub show_branch_icon: bool, @@ -23,55 +15,38 @@ pub struct TitleBarSettings { pub show_menus: bool, } -#[derive( - Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey, -)] -#[settings_ui(group = "Title Bar")] -#[settings_key(key = "title_bar")] -pub struct TitleBarSettingsContent { - /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen". - /// - /// Default: "always" - pub show: Option, - /// Whether to show the branch icon beside branch switcher in the title bar. - /// - /// Default: false - pub show_branch_icon: Option, - /// Whether to show onboarding banners in the title bar. - /// - /// Default: true - pub show_onboarding_banner: Option, - /// Whether to show user avatar in the title bar. - /// - /// Default: true - pub show_user_picture: Option, - /// Whether to show the branch name button in the titlebar. - /// - /// Default: true - pub show_branch_name: Option, - /// Whether to show the project host and name in the titlebar. - /// - /// Default: true - pub show_project_items: Option, - /// Whether to show the sign in button in the title bar. - /// - /// Default: true - pub show_sign_in: Option, - /// Whether to show the menus in the title bar. - /// - /// Default: false - pub show_menus: Option, -} - impl Settings for TitleBarSettings { - type FileContent = TitleBarSettingsContent; + fn from_defaults(s: &SettingsContent, _: &mut App) -> Self { + let content = s.title_bar.clone().unwrap(); + TitleBarSettings { + show: content.show.unwrap(), + show_branch_icon: content.show_branch_icon.unwrap(), + show_onboarding_banner: content.show_onboarding_banner.unwrap(), + show_user_picture: content.show_user_picture.unwrap(), + show_branch_name: content.show_branch_name.unwrap(), + show_project_items: content.show_project_items.unwrap(), + show_sign_in: content.show_sign_in.unwrap(), + show_menus: content.show_menus.unwrap(), + } + } + + fn refine(&mut self, s: &SettingsContent, _: &mut App) { + let Some(content) = &s.title_bar else { + return; + }; - fn load(sources: SettingsSources, _: &mut gpui::App) -> anyhow::Result - where - Self: Sized, - { - sources.json_merge() + self.show.merge_from(&content.show); + self.show_branch_icon.merge_from(&content.show_branch_icon); + self.show_onboarding_banner + .merge_from(&content.show_onboarding_banner); + self.show_user_picture + .merge_from(&content.show_user_picture); + self.show_branch_name.merge_from(&content.show_branch_name); + self.show_project_items + .merge_from(&content.show_project_items); + self.show_sign_in.merge_from(&content.show_sign_in); + self.show_menus.merge_from(&content.show_menus); } - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut Self::FileContent) {} + fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) {} } diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index f1d97fa455dded285b10033d721e5028bedbd35a..f3c81ab2bf9d6b6799f8a16d2ee72bfbc497eb1b 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -45,6 +45,17 @@ pub mod scrollbars { Never, } + impl From for ShowScrollbar { + fn from(value: settings::ShowScrollbar) -> Self { + match value { + settings::ShowScrollbar::Auto => ShowScrollbar::Auto, + settings::ShowScrollbar::System => ShowScrollbar::System, + settings::ShowScrollbar::Always => ShowScrollbar::Always, + settings::ShowScrollbar::Never => ShowScrollbar::Never, + } + } + } + impl ShowScrollbar { pub(super) fn show(&self) -> bool { *self != Self::Never diff --git a/crates/ui_macros/src/dynamic_spacing.rs b/crates/ui_macros/src/dynamic_spacing.rs index bd7c72e90eda94508e627781043bb23b63ba576a..15ba3e241ec43d02b83e4143eb620505a0a2f02e 100644 --- a/crates/ui_macros/src/dynamic_spacing.rs +++ b/crates/ui_macros/src/dynamic_spacing.rs @@ -66,9 +66,9 @@ pub fn derive_spacing(input: TokenStream) -> TokenStream { let n = n.base10_parse::().unwrap(); quote! { DynamicSpacing::#variant => match ThemeSettings::get_global(cx).ui_density { - UiDensity::Compact => (#n - 4.0).max(0.0) / BASE_REM_SIZE_IN_PX, - UiDensity::Default => #n / BASE_REM_SIZE_IN_PX, - UiDensity::Comfortable => (#n + 4.0) / BASE_REM_SIZE_IN_PX, + ::theme::UiDensity::Compact => (#n - 4.0).max(0.0) / BASE_REM_SIZE_IN_PX, + ::theme::UiDensity::Default => #n / BASE_REM_SIZE_IN_PX, + ::theme::UiDensity::Comfortable => (#n + 4.0) / BASE_REM_SIZE_IN_PX, } } } @@ -78,9 +78,9 @@ pub fn derive_spacing(input: TokenStream) -> TokenStream { let c = c.base10_parse::().unwrap(); quote! { DynamicSpacing::#variant => match ThemeSettings::get_global(cx).ui_density { - UiDensity::Compact => #a / BASE_REM_SIZE_IN_PX, - UiDensity::Default => #b / BASE_REM_SIZE_IN_PX, - UiDensity::Comfortable => #c / BASE_REM_SIZE_IN_PX, + ::theme::UiDensity::Compact => #a / BASE_REM_SIZE_IN_PX, + ::theme::UiDensity::Default => #b / BASE_REM_SIZE_IN_PX, + ::theme::UiDensity::Comfortable => #c / BASE_REM_SIZE_IN_PX, } } } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index ee18093784e4b0f2b78db9240d918685b6f01b6f..3f3fcd0412e636c60669a6972b3c42bc0c0d7cef 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1390,3 +1390,21 @@ Line 3"# assert_eq!(result[1], (10..15, "world")); // '🦀' is 4 bytes } } + +pub fn refine(dest: &mut T, src: &Option) { + if let Some(src) = src { + *dest = src.clone() + } +} + +pub trait MergeFrom: Sized + Clone { + fn merge_from(&mut self, src: &Option); +} + +impl MergeFrom for T { + fn merge_from(&mut self, src: &Option) { + if let Some(src) = src { + *self = src.clone(); + } + } +} diff --git a/crates/vim/src/digraph.rs b/crates/vim/src/digraph.rs index 796dad94c0329ed56c2e1c39f1cc2e2fc102fe4d..7a2ae08cace066bd6fa73d9914aac847b0fb1136 100644 --- a/crates/vim/src/digraph.rs +++ b/crates/vim/src/digraph.rs @@ -224,7 +224,6 @@ mod test { use settings::SettingsStore; use crate::{ - VimSettings, state::Mode, test::{NeovimBackedTestContext, VimTestContext}, }; @@ -294,11 +293,11 @@ mod test { let mut cx: VimTestContext = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { + store.update_user_settings(cx, |s| { let mut custom_digraphs = HashMap::default(); custom_digraphs.insert("|-".into(), "⊢".into()); custom_digraphs.insert(":)".into(), "👨‍💻".into()); - s.custom_digraphs = Some(custom_digraphs); + s.vim.get_or_insert_default().custom_digraphs = Some(custom_digraphs); }); }); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index b8d1325a8b19aaa2dcbc2611b2ff66df721c17f3..7dfdb973c7603e9ef28bf757a9a716e729b72170 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -989,11 +989,10 @@ impl Vim { mod test { use gpui::{KeyBinding, TestAppContext, UpdateGlobal}; use indoc::indoc; - use language::language_settings::AllLanguageSettings; use settings::SettingsStore; use crate::{ - VimSettings, motion, + motion, state::Mode::{self}, test::{NeovimBackedTestContext, VimTestContext}, }; @@ -1724,8 +1723,8 @@ mod test { async fn test_f_and_t_smartcase(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_smartcase_find = Some(true); + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_smartcase_find = Some(true); }); }); @@ -1891,8 +1890,12 @@ mod test { cx.update(|_, cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.defaults.preferred_line_length = Some(5); + settings.update_user_settings(cx, |settings| { + settings + .project + .all_languages + .defaults + .preferred_line_length = Some(5); }); }) }); diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 933b119d3794540681cc096b0fe22b7f70da00cb..99e84fcaf1a81f7969baeb1630d36373c6852b53 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -311,17 +311,13 @@ impl Vim { #[cfg(test)] mod test { use crate::{ - UseSystemClipboard, VimSettings, state::{Mode, Register}, test::{NeovimBackedTestContext, VimTestContext}, }; use gpui::ClipboardItem; use indoc::indoc; - use language::{ - LanguageName, - language_settings::{AllLanguageSettings, LanguageSettingsContent}, - }; - use settings::SettingsStore; + use language::{LanguageName, language_settings::LanguageSettingsContent}; + use settings::{SettingsStore, UseSystemClipboard}; #[gpui::test] async fn test_paste(cx: &mut gpui::TestAppContext) { @@ -408,8 +404,8 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); }); @@ -444,8 +440,9 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::OnYank) + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_system_clipboard = + Some(UseSystemClipboard::OnYank) }); }); @@ -709,9 +706,9 @@ mod test { Mode::Normal, ); cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.languages.0.insert( - LanguageName::new("Rust"), + store.update_user_settings(cx, |settings| { + settings.project.all_languages.languages.0.insert( + LanguageName::new("Rust").0, LanguageSettingsContent { auto_indent_on_paste: Some(false), ..Default::default() @@ -772,8 +769,8 @@ mod test { let mut cx = NeovimBackedTestContext::new(cx).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); }); @@ -818,8 +815,8 @@ mod test { let mut cx = NeovimBackedTestContext::new(cx).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); }); @@ -847,8 +844,8 @@ mod test { let mut cx = NeovimBackedTestContext::new(cx).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); }); @@ -906,8 +903,8 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + store.update_user_settings(cx, |s| { + s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); }); diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs index eeb98692bc30c5c8c39c0be23ba17b3276b708df..8f1a157013000ae4df2416ddca93743f2c926d29 100644 --- a/crates/vim/src/normal/scroll.rs +++ b/crates/vim/src/normal/scroll.rs @@ -271,7 +271,7 @@ mod test { state::Mode, test::{NeovimBackedTestContext, VimTestContext}, }; - use editor::{EditorSettings, ScrollBeyondLastLine}; + use editor::ScrollBeyondLastLine; use gpui::{AppContext as _, point, px, size}; use indoc::indoc; use language::Point; @@ -427,9 +427,7 @@ mod test { // First test without vertical scroll margin cx.neovim.set_option(&format!("scrolloff={}", 0)).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.vertical_scroll_margin = Some(0.0) - }); + store.update_user_settings(cx, |s| s.editor.vertical_scroll_margin = Some(0.0)); }); let content = "ˇ".to_owned() + &sample_text(26, 2, 'a'); @@ -455,9 +453,7 @@ mod test { cx.neovim.set_option(&format!("scrolloff={}", 3)).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.vertical_scroll_margin = Some(3.0) - }); + store.update_user_settings(cx, |s| s.editor.vertical_scroll_margin = Some(3.0)); }); // scroll down: ctrl-f @@ -485,9 +481,8 @@ mod test { cx.set_shared_state(&content).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { - s.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off); - // s.vertical_scroll_margin = Some(0.); + store.update_user_settings(cx, |s| { + s.editor.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off); }); }); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index a92ec7f6d3a05b26d31feca567e7a2e08f7f2b20..6da18164e42e42b3c1932b094fab5cecf048c155 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -645,7 +645,6 @@ mod test { state::Mode, test::{NeovimBackedTestContext, VimTestContext}, }; - use editor::EditorSettings; use editor::{DisplayPoint, display_map::DisplayRow}; use indoc::indoc; @@ -694,7 +693,7 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| s.search_wrap = Some(false)); + store.update_user_settings(cx, |s| s.editor.search_wrap = Some(false)); }); cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal); @@ -815,7 +814,7 @@ mod test { // check that searching with unable search wrap cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| s.search_wrap = Some(false)); + store.update_user_settings(cx, |s| s.editor.search_wrap = Some(false)); }); cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); cx.simulate_keystrokes("/ c c enter"); diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 84376719d141fa4862a3e7a1b0f6116dd809bfe5..2256c2577ecd282f690ee7b3afe9e2b21b6e8788 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -22,7 +22,6 @@ pub use vim_test_context::*; use indoc::indoc; use search::BufferSearchBar; -use workspace::WorkspaceSettings; use crate::{PushSneak, PushSneakBackward, insert::NormalBefore, motion, state::Mode}; @@ -1562,10 +1561,10 @@ async fn test_plus_minus(cx: &mut gpui::TestAppContext) { async fn test_command_alias(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| { + store.update_user_settings(cx, |s| { let mut aliases = HashMap::default(); aliases.insert("Q".to_string(), "upper".to_string()); - s.command_aliases = Some(aliases) + s.workspace.command_aliases = aliases }); }); diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index 6c9df164e0fe880c81960a412519347aff5959bd..c36307221ed2d9486455f48effdbbc85e92d4ec0 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -6,7 +6,7 @@ use std::{ panic, thread, }; -use language::language_settings::{AllLanguageSettings, SoftWrap}; +use language::language_settings::SoftWrap; use util::test::marked_text_offsets; use super::{VimTestContext, neovim_connection::NeovimConnection}; @@ -245,9 +245,14 @@ impl NeovimBackedTestContext { self.update(|_, cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.defaults.soft_wrap = Some(SoftWrap::PreferredLineLength); - settings.defaults.preferred_line_length = Some(columns); + settings.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.soft_wrap = + Some(SoftWrap::PreferredLineLength); + settings + .project + .all_languages + .defaults + .preferred_line_length = Some(columns); }); }) }) diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 3e6c96ac72f1e6dee568dafbfda6956c1dd5970c..a2db0493d99190bc7355a5af5a0687befcd02f63 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -68,7 +68,7 @@ impl VimTestContext { pub fn init_keybindings(enabled: bool, cx: &mut App) { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| s.vim_mode = Some(enabled)); + store.update_user_settings(cx, |s| s.vim_mode = Some(enabled)); }); let mut default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure( "keymaps/default-macos.json", @@ -137,7 +137,7 @@ impl VimTestContext { pub fn enable_vim(&mut self) { self.cx.update(|_, cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| s.vim_mode = Some(true)); + store.update_user_settings(cx, |s| s.vim_mode = Some(true)); }); }) } @@ -145,7 +145,7 @@ impl VimTestContext { pub fn disable_vim(&mut self) { self.cx.update(|_, cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| s.vim_mode = Some(false)); + store.update_user_settings(cx, |s| s.vim_mode = Some(false)); }); }) } @@ -153,9 +153,7 @@ impl VimTestContext { pub fn enable_helix(&mut self) { self.cx.update(|_, cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| { - s.helix_mode = Some(true) - }); + store.update_user_settings(cx, |s| s.helix_mode = Some(true)); }); }) } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 5fb1f90a08e7a4346619c089cfe93c826bedf156..309806be02a1e283770276c26ad544af2bcedcba 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -19,7 +19,6 @@ mod state; mod surrounds; mod visual; -use anyhow::Result; use collections::HashMap; use editor::{ Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, SelectionEffects, @@ -38,15 +37,15 @@ use normal::search::SearchSubmit; use object::Object; use schemars::JsonSchema; use serde::Deserialize; -use serde::Serialize; -use settings::{ - Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi, update_settings_file, +pub use settings::{ + ModeContent, Settings, SettingsStore, UseSystemClipboard, update_settings_file, }; use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals}; use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; use theme::ThemeSettings; use ui::{IntoElement, SharedString, px}; +use util::MergeFrom; use vim_mode_setting::HelixModeSetting; use vim_mode_setting::VimModeSetting; use workspace::{self, Pane, Workspace}; @@ -266,7 +265,7 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &ToggleVimMode, _, cx| { let fs = workspace.app_state().fs.clone(); let currently_enabled = Vim::enabled(cx); - update_settings_file::(fs, cx, move |setting, _| { + update_settings_file(fs, cx, move |setting, _| { setting.vim_mode = Some(!currently_enabled) }) }); @@ -1793,21 +1792,19 @@ impl Vim { } } -/// Controls when to use system clipboard. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum UseSystemClipboard { - /// Don't use system clipboard. - Never, - /// Use system clipboard. - Always, - /// Use system clipboard for yank operations. - OnYank, +struct VimSettings { + pub default_mode: Mode, + pub toggle_relative_line_numbers: bool, + pub use_system_clipboard: settings::UseSystemClipboard, + pub use_smartcase_find: bool, + pub custom_digraphs: HashMap>, + pub highlight_on_yank_duration: u64, + pub cursor_shape: CursorShapeSettings, } /// The settings for cursor shape. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -struct CursorShapeSettings { +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct CursorShapeSettings { /// Cursor shape for the normal mode. /// /// Default: block @@ -1826,85 +1823,63 @@ struct CursorShapeSettings { pub insert: Option, } -#[derive(Deserialize)] -struct VimSettings { - pub default_mode: Mode, - pub toggle_relative_line_numbers: bool, - pub use_system_clipboard: UseSystemClipboard, - pub use_smartcase_find: bool, - pub custom_digraphs: HashMap>, - pub highlight_on_yank_duration: u64, - pub cursor_shape: CursorShapeSettings, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "vim")] -struct VimSettingsContent { - pub default_mode: Option, - pub toggle_relative_line_numbers: Option, - pub use_system_clipboard: Option, - pub use_smartcase_find: Option, - pub custom_digraphs: Option>>, - pub highlight_on_yank_duration: Option, - pub cursor_shape: Option, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ModeContent { - #[default] - Normal, - Insert, - Replace, - Visual, - VisualLine, - VisualBlock, - HelixNormal, +impl From for CursorShapeSettings { + fn from(settings: settings::CursorShapeSettings) -> Self { + Self { + normal: settings.normal.map(Into::into), + replace: settings.replace.map(Into::into), + visual: settings.visual.map(Into::into), + insert: settings.insert.map(Into::into), + } + } } -impl From for Mode { +impl From for Mode { fn from(mode: ModeContent) -> Self { match mode { ModeContent::Normal => Self::Normal, ModeContent::Insert => Self::Insert, - ModeContent::Replace => Self::Replace, - ModeContent::Visual => Self::Visual, - ModeContent::VisualLine => Self::VisualLine, - ModeContent::VisualBlock => Self::VisualBlock, ModeContent::HelixNormal => Self::HelixNormal, } } } impl Settings for VimSettings { - type FileContent = VimSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - let settings: VimSettingsContent = sources.json_merge()?; - - Ok(Self { - default_mode: settings - .default_mode - .ok_or_else(Self::missing_default)? - .into(), - toggle_relative_line_numbers: settings - .toggle_relative_line_numbers - .ok_or_else(Self::missing_default)?, - use_system_clipboard: settings - .use_system_clipboard - .ok_or_else(Self::missing_default)?, - use_smartcase_find: settings - .use_smartcase_find - .ok_or_else(Self::missing_default)?, - custom_digraphs: settings.custom_digraphs.ok_or_else(Self::missing_default)?, - highlight_on_yank_duration: settings - .highlight_on_yank_duration - .ok_or_else(Self::missing_default)?, - cursor_shape: settings.cursor_shape.ok_or_else(Self::missing_default)?, - }) + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let vim = content.vim.clone().unwrap(); + Self { + default_mode: vim.default_mode.unwrap().into(), + toggle_relative_line_numbers: vim.toggle_relative_line_numbers.unwrap(), + use_system_clipboard: vim.use_system_clipboard.unwrap(), + use_smartcase_find: vim.use_smartcase_find.unwrap(), + custom_digraphs: vim.custom_digraphs.unwrap(), + highlight_on_yank_duration: vim.highlight_on_yank_duration.unwrap(), + cursor_shape: vim.cursor_shape.unwrap().into(), + } + } + + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(vim) = content.vim.as_ref() else { + return; + }; + self.default_mode + .merge_from(&vim.default_mode.map(Into::into)); + self.toggle_relative_line_numbers + .merge_from(&vim.toggle_relative_line_numbers); + self.use_system_clipboard + .merge_from(&vim.use_system_clipboard); + self.use_smartcase_find.merge_from(&vim.use_smartcase_find); + self.custom_digraphs.merge_from(&vim.custom_digraphs); + self.highlight_on_yank_duration + .merge_from(&vim.highlight_on_yank_duration); + self.cursor_shape + .merge_from(&vim.cursor_shape.map(Into::into)); } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) { + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { // TODO: translate vim extension settings } } diff --git a/crates/vim_mode_setting/Cargo.toml b/crates/vim_mode_setting/Cargo.toml index 61d265b958b10fac700bd78577ac5fefb19b7d09..8371cca401fa77c63cba6748dc428645340f48b6 100644 --- a/crates/vim_mode_setting/Cargo.toml +++ b/crates/vim_mode_setting/Cargo.toml @@ -12,9 +12,6 @@ workspace = true path = "src/vim_mode_setting.rs" [dependencies] -anyhow.workspace = true gpui.workspace = true -schemars.workspace = true -serde.workspace = true settings.workspace = true workspace-hack.workspace = true diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs index 7d0f8aa7fe3766f18ef163ce882e55ebb73aa03a..7c0a21e443fe908191bf3cff9d557a1bf38971b9 100644 --- a/crates/vim_mode_setting/src/vim_mode_setting.rs +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -4,10 +4,8 @@ //! disable Vim/Helix modes without having to depend on the `vim` crate in its //! entirety. -use anyhow::Result; use gpui::App; -use schemars::JsonSchema; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::{Settings, SettingsContent}; /// Initializes the `vim_mode_setting` crate. pub fn init(cx: &mut App) { @@ -17,97 +15,34 @@ pub fn init(cx: &mut App) { pub struct VimModeSetting(pub bool); -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Default, - serde::Serialize, - serde::Deserialize, - SettingsUi, - SettingsKey, - JsonSchema, -)] -#[settings_key(None)] -pub struct VimModeSettingContent { - /// Whether or not to enable Vim mode. - /// - /// Default: false - pub vim_mode: Option, -} - impl Settings for VimModeSetting { - type FileContent = VimModeSettingContent; + fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + Self(content.vim_mode.unwrap()) + } - fn load(sources: SettingsSources, _: &mut App) -> Result { - Ok(Self( - [ - sources.profile, - sources.release_channel, - sources.user, - sources.server, - Some(sources.default), - ] - .into_iter() - .flatten() - .filter_map(|mode| mode.vim_mode) - .next() - .ok_or_else(Self::missing_default)?, - )) + fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { + if let Some(vim_mode) = content.vim_mode { + self.0 = vim_mode; + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) { + fn import_from_vscode(_vscode: &settings::VsCodeSettings, _content: &mut SettingsContent) { // TODO: could possibly check if any of the `vim.` keys are set? } } -#[derive(Debug)] pub struct HelixModeSetting(pub bool); -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Default, - serde::Serialize, - serde::Deserialize, - SettingsUi, - SettingsKey, - JsonSchema, -)] -#[settings_key(None)] -pub struct HelixModeSettingContent { - /// Whether or not to enable Helix mode. - /// - /// Default: false - pub helix_mode: Option, -} - impl Settings for HelixModeSetting { - type FileContent = HelixModeSettingContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - Ok(Self( - [ - sources.profile, - sources.release_channel, - sources.user, - sources.server, - Some(sources.default), - ] - .into_iter() - .flatten() - .filter_map(|mode| mode.helix_mode) - .next() - .ok_or_else(Self::missing_default)?, - )) + fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + Self(content.helix_mode.unwrap()) } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) { - // TODO: could possibly check if any of the `helix.` keys are set? + fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { + if let Some(helix_mode) = content.helix_mode { + self.0 = helix_mode; + } } + + fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 149a122c0c6a31b7c4713601acca5091accb96ac..ec33b9af59c2cb070866c0a1c4a5b8670a8d58e6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,8 +9,6 @@ use gpui::{ Render, SharedString, StyleRefinement, Styled, Subscription, WeakEntity, Window, deferred, div, px, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use settings::SettingsStore; use std::sync::Arc; use ui::{ContextMenu, Divider, DividerColor, IconButton, Tooltip, h_flex}; @@ -210,14 +208,33 @@ impl Focusable for Dock { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -#[serde(rename_all = "lowercase")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum DockPosition { Left, Bottom, Right, } +impl From for DockPosition { + fn from(value: settings::DockPosition) -> Self { + match value { + settings::DockPosition::Left => Self::Left, + settings::DockPosition::Bottom => Self::Bottom, + settings::DockPosition::Right => Self::Right, + } + } +} + +impl Into for DockPosition { + fn into(self) -> settings::DockPosition { + match self { + Self::Left => settings::DockPosition::Left, + Self::Bottom => settings::DockPosition::Bottom, + Self::Right => settings::DockPosition::Right, + } + } +} + impl DockPosition { fn label(&self) -> &'static str { match self { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 23fbec470c4d2e305bf7b51679bbe56f6dfeaa95..da4e557776072e33bd691c38d73b0c6623ac5a4c 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -15,9 +15,9 @@ use gpui::{ Focusable, Font, HighlightStyle, Pixels, Point, Render, SharedString, Task, WeakEntity, Window, }; use project::{Project, ProjectEntryId, ProjectPath}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsLocation, SettingsSources, SettingsUi}; +pub use settings::{ + ActivateOnClose, ClosePosition, Settings, SettingsLocation, ShowCloseButton, ShowDiagnostics, +}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -30,7 +30,7 @@ use std::{ }; use theme::Theme; use ui::{Color, Icon, IntoElement, Label, LabelCommon}; -use util::ResultExt; +use util::{MergeFrom as _, ResultExt}; pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); @@ -49,7 +49,6 @@ impl Default for SaveOptions { } } -#[derive(Deserialize)] pub struct ItemSettings { pub git_status: bool, pub close_position: ClosePosition, @@ -59,150 +58,119 @@ pub struct ItemSettings { pub show_close_button: ShowCloseButton, } -#[derive(Deserialize)] pub struct PreviewTabsSettings { pub enabled: bool, pub enable_preview_from_file_finder: bool, pub enable_preview_from_code_navigation: bool, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -pub enum ClosePosition { - Left, - #[default] - Right, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -pub enum ShowCloseButton { - Always, - #[default] - Hover, - Hidden, -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ShowDiagnostics { - #[default] - Off, - Errors, - All, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ActivateOnClose { - #[default] - History, - Neighbour, - LeftNeighbour, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "tabs")] -pub struct ItemSettingsContent { - /// Whether to show the Git file status on a tab item. - /// - /// Default: false - git_status: Option, - /// Position of the close button in a tab. - /// - /// Default: right - close_position: Option, - /// Whether to show the file icon for a tab. - /// - /// Default: false - file_icons: Option, - /// What to do after closing the current tab. - /// - /// Default: history - pub activate_on_close: Option, - /// Which files containing diagnostic errors/warnings to mark in the tabs. - /// This setting can take the following three values: - /// - /// Default: off - show_diagnostics: Option, - /// Whether to always show the close button on tabs. - /// - /// Default: false - show_close_button: Option, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "preview_tabs")] -pub struct PreviewTabsSettingsContent { - /// Whether to show opened editors as preview tabs. - /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic. - /// - /// Default: true - enabled: Option, - /// Whether to open tabs in preview mode when selected from the file finder. - /// - /// Default: false - enable_preview_from_file_finder: Option, - /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab. - /// - /// Default: false - enable_preview_from_code_navigation: Option, -} - impl Settings for ItemSettings { - type FileContent = ItemSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let tabs = content.tabs.as_ref().unwrap(); + Self { + git_status: tabs.git_status.unwrap(), + close_position: tabs.close_position.unwrap(), + activate_on_close: tabs.activate_on_close.unwrap(), + file_icons: tabs.file_icons.unwrap(), + show_diagnostics: tabs.show_diagnostics.unwrap(), + show_close_button: tabs.show_close_button.unwrap(), + } + } - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(tabs) = content.tabs.as_ref() else { + return; + }; + self.git_status.merge_from(&tabs.git_status); + self.close_position.merge_from(&tabs.close_position); + self.activate_on_close.merge_from(&tabs.activate_on_close); + self.file_icons.merge_from(&tabs.file_icons); + self.show_diagnostics.merge_from(&tabs.show_diagnostics); + self.show_close_button.merge_from(&tabs.show_close_button); } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode( + vscode: &settings::VsCodeSettings, + current: &mut settings::SettingsContent, + ) { if let Some(b) = vscode.read_bool("workbench.editor.tabActionCloseVisibility") { - current.show_close_button = Some(if b { + current.tabs.get_or_insert_default().show_close_button = Some(if b { ShowCloseButton::Always } else { ShowCloseButton::Hidden }) } - vscode.enum_setting( - "workbench.editor.tabActionLocation", - &mut current.close_position, - |s| match s { - "right" => Some(ClosePosition::Right), - "left" => Some(ClosePosition::Left), - _ => None, - }, - ); + if let Some(s) = vscode.read_enum("workbench.editor.tabActionLocation", |s| match s { + "right" => Some(ClosePosition::Right), + "left" => Some(ClosePosition::Left), + _ => None, + }) { + current.tabs.get_or_insert_default().close_position = Some(s) + } if let Some(b) = vscode.read_bool("workbench.editor.focusRecentEditorAfterClose") { - current.activate_on_close = Some(if b { + current.tabs.get_or_insert_default().activate_on_close = Some(if b { ActivateOnClose::History } else { ActivateOnClose::LeftNeighbour }) } - vscode.bool_setting("workbench.editor.showIcons", &mut current.file_icons); - vscode.bool_setting("git.decorations.enabled", &mut current.git_status); + if let Some(b) = vscode.read_bool("workbench.editor.showIcons") { + current.tabs.get_or_insert_default().file_icons = Some(b); + }; + if let Some(b) = vscode.read_bool("git.decorations.enabled") { + current.tabs.get_or_insert_default().git_status = Some(b); + } } } impl Settings for PreviewTabsSettings { - type FileContent = PreviewTabsSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() - } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.bool_setting("workbench.editor.enablePreview", &mut current.enabled); - vscode.bool_setting( - "workbench.editor.enablePreviewFromCodeNavigation", - &mut current.enable_preview_from_code_navigation, - ); - vscode.bool_setting( - "workbench.editor.enablePreviewFromQuickOpen", - &mut current.enable_preview_from_file_finder, - ); + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let preview_tabs = content.preview_tabs.as_ref().unwrap(); + Self { + enabled: preview_tabs.enabled.unwrap(), + enable_preview_from_file_finder: preview_tabs.enable_preview_from_file_finder.unwrap(), + enable_preview_from_code_navigation: preview_tabs + .enable_preview_from_code_navigation + .unwrap(), + } + } + + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(preview_tabs) = content.preview_tabs.as_ref() else { + return; + }; + + self.enabled.merge_from(&preview_tabs.enabled); + self.enable_preview_from_file_finder + .merge_from(&preview_tabs.enable_preview_from_file_finder); + self.enable_preview_from_code_navigation + .merge_from(&preview_tabs.enable_preview_from_code_navigation); + } + + fn import_from_vscode( + vscode: &settings::VsCodeSettings, + current: &mut settings::SettingsContent, + ) { + if let Some(enabled) = vscode.read_bool("workbench.editor.enablePreview") { + current.preview_tabs.get_or_insert_default().enabled = Some(enabled); + } + if let Some(enable_preview_from_code_navigation) = + vscode.read_bool("workbench.editor.enablePreviewFromCodeNavigation") + { + current + .preview_tabs + .get_or_insert_default() + .enable_preview_from_code_navigation = Some(enable_preview_from_code_navigation) + } + if let Some(enable_preview_from_file_finder) = + vscode.read_bool("workbench.editor.enablePreviewFromQuickOpen") + { + current + .preview_tabs + .get_or_insert_default() + .enable_preview_from_file_finder = Some(enable_preview_from_file_finder) + } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 0418be7a2fbc858c8623400e65fed0f96e2cdb61..7daf71e57492936bfe33fc3cc94334146657043a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -5816,8 +5816,8 @@ mod tests { async fn test_remove_item_ordering_neighbour(cx: &mut TestAppContext) { init_test(cx); cx.update_global::(|s, cx| { - s.update_user_settings::(cx, |s| { - s.activate_on_close = Some(ActivateOnClose::Neighbour); + s.update_user_settings(cx, |s| { + s.tabs.get_or_insert_default().activate_on_close = Some(ActivateOnClose::Neighbour); }); }); let fs = FakeFs::new(cx.executor()); @@ -5905,8 +5905,9 @@ mod tests { async fn test_remove_item_ordering_left_neighbour(cx: &mut TestAppContext) { init_test(cx); cx.update_global::(|s, cx| { - s.update_user_settings::(cx, |s| { - s.activate_on_close = Some(ActivateOnClose::LeftNeighbour); + s.update_user_settings(cx, |s| { + s.tabs.get_or_insert_default().activate_on_close = + Some(ActivateOnClose::LeftNeighbour); }); }); let fs = FakeFs::new(cx.executor()); @@ -6558,8 +6559,8 @@ mod tests { fn set_max_tabs(cx: &mut TestAppContext, value: Option) { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.max_tabs = value.map(|v| NonZero::new(v).unwrap()) + store.update_user_settings(cx, |settings| { + settings.workspace.max_tabs = value.map(|v| NonZero::new(v).unwrap()) }); }); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5772695310e1258bee2a62953ad4bcc3620cd4ee..f536cd09f1c3d6092d86de27f08310852eae99af 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -52,10 +52,7 @@ pub use item::{ ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle, }; use itertools::Itertools; -use language::{ - Buffer, LanguageRegistry, Rope, - language_settings::{AllLanguageSettings, all_language_settings}, -}; +use language::{Buffer, LanguageRegistry, Rope, language_settings::all_language_settings}; pub use modal_layer::*; use node_runtime::NodeRuntime; use notifications::{ @@ -1695,8 +1692,8 @@ impl Workspace { cx: &mut Context, ) { let fs = self.project().read(cx).fs(); - settings::update_settings_file::(fs.clone(), cx, move |content, _cx| { - content.bottom_dock_layout = Some(layout); + settings::update_settings_file(fs.clone(), cx, move |content, _cx| { + content.workspace.bottom_dock_layout = Some(layout); }); cx.notify(); @@ -6014,8 +6011,8 @@ impl Workspace { ) { let fs = self.project().read(cx).fs().clone(); let show_edit_predictions = all_language_settings(None, cx).show_edit_predictions(None, cx); - update_settings_file::(fs, cx, move |file, _| { - file.defaults.show_edit_predictions = Some(!show_edit_predictions) + update_settings_file(fs, cx, move |file, _| { + file.project.all_languages.defaults.show_edit_predictions = Some(!show_edit_predictions) }); } } @@ -8678,8 +8675,8 @@ mod tests { // Autosave on window change. item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::OnWindowChange); + settings.update_user_settings(cx, |settings| { + settings.workspace.autosave = Some(AutosaveSetting::OnWindowChange); }) }); item.is_dirty = true; @@ -8698,13 +8695,12 @@ mod tests { item.update_in(cx, |item, window, cx| { cx.focus_self(window); SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::OnFocusChange); + settings.update_user_settings(cx, |settings| { + settings.workspace.autosave = Some(AutosaveSetting::OnFocusChange); }) }); item.is_dirty = true; }); - // Blurring the item saves the file. item.update_in(cx, |_, window, _| window.blur()); cx.executor().run_until_parked(); @@ -8721,8 +8717,9 @@ mod tests { // Autosave after delay. item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); + settings.update_user_settings(cx, |settings| { + settings.workspace.autosave = + Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); }) }); item.is_dirty = true; @@ -8770,8 +8767,8 @@ mod tests { // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { - settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::OnFocusChange); + settings.update_user_settings(cx, |settings| { + settings.workspace.autosave = Some(AutosaveSetting::OnFocusChange); }) }); item.is_dirty = true; @@ -9774,8 +9771,8 @@ mod tests { // Enable the close_on_disk_deletion setting cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.close_on_file_delete = Some(true); + store.update_user_settings(cx, |settings| { + settings.workspace.close_on_file_delete = Some(true); }); }); @@ -9842,8 +9839,8 @@ mod tests { // Ensure close_on_disk_deletion is disabled (default) cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.close_on_file_delete = Some(false); + store.update_user_settings(cx, |settings| { + settings.workspace.close_on_file_delete = Some(false); }); }); @@ -9919,8 +9916,8 @@ mod tests { // Enable the close_on_file_delete setting cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.close_on_file_delete = Some(true); + store.update_user_settings(cx, |settings| { + settings.workspace.close_on_file_delete = Some(true); }); }); @@ -9992,8 +9989,8 @@ mod tests { // Enable the close_on_file_delete setting cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.close_on_file_delete = Some(true); + store.update_user_settings(cx, |settings| { + settings.workspace.close_on_file_delete = Some(true); }); }); diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index a86f81f442b0437494d5cc2915a0a0b7d15ca7f3..30bebb7c18b1f03e5bf59425026aea5a0f4bd552 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -1,65 +1,49 @@ use std::num::NonZeroUsize; use crate::DockPosition; -use anyhow::Result; use collections::HashMap; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use serde::Deserialize; +pub use settings::AutosaveSetting; +use settings::Settings; +pub use settings::{ + BottomDockLayout, PaneSplitDirectionHorizontal, PaneSplitDirectionVertical, + RestoreOnStartupBehavior, +}; +use util::MergeFrom as _; -#[derive(Deserialize)] pub struct WorkspaceSettings { pub active_pane_modifiers: ActivePanelModifiers, - pub bottom_dock_layout: BottomDockLayout, - pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal, - pub pane_split_direction_vertical: PaneSplitDirectionVertical, - pub centered_layout: CenteredLayoutSettings, + pub bottom_dock_layout: settings::BottomDockLayout, + pub pane_split_direction_horizontal: settings::PaneSplitDirectionHorizontal, + pub pane_split_direction_vertical: settings::PaneSplitDirectionVertical, + pub centered_layout: settings::CenteredLayoutSettings, pub confirm_quit: bool, pub show_call_status_icon: bool, pub autosave: AutosaveSetting, - pub restore_on_startup: RestoreOnStartupBehavior, + pub restore_on_startup: settings::RestoreOnStartupBehavior, pub restore_on_file_reopen: bool, pub drop_target_size: f32, pub use_system_path_prompts: bool, pub use_system_prompts: bool, pub command_aliases: HashMap, pub max_tabs: Option, - pub when_closing_with_no_tabs: CloseWindowWhenNoItems, - pub on_last_window_closed: OnLastWindowClosed, + pub when_closing_with_no_tabs: settings::CloseWindowWhenNoItems, + pub on_last_window_closed: settings::OnLastWindowClosed, pub resize_all_panels_in_dock: Vec, pub close_on_file_delete: bool, pub use_system_window_tabs: bool, pub zoomed_padding: bool, } -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum OnLastWindowClosed { - /// Match platform conventions by default, so don't quit on macOS, and quit on other platforms - #[default] - PlatformDefault, - /// Quit the application the last window is closed - QuitApp, -} - -impl OnLastWindowClosed { - pub fn is_quit_app(&self) -> bool { - match self { - OnLastWindowClosed::PlatformDefault => false, - OnLastWindowClosed::QuitApp => true, - } - } -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[derive(Copy, Clone, PartialEq, Debug, Default)] pub struct ActivePanelModifiers { /// Size of the border surrounding the active pane. /// When set to 0, the active pane doesn't have any border. /// The border is drawn inset. /// /// Default: `0.0` + // TODO: make this not an option, it is never None pub border_size: Option, /// Opacity of inactive panels. /// When set to 1.0, the inactive panes have the same opacity as the active one. @@ -67,156 +51,10 @@ pub struct ActivePanelModifiers { /// Values are clamped to the [0.0, 1.0] range. /// /// Default: `1.0` + // TODO: make this not an option, it is never None pub inactive_opacity: Option, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum BottomDockLayout { - /// Contained between the left and right docks - #[default] - Contained, - /// Takes up the full width of the window - Full, - /// Extends under the left dock while snapping to the right dock - LeftAligned, - /// Extends under the right dock while snapping to the left dock - RightAligned, -} - -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum CloseWindowWhenNoItems { - /// Match platform conventions by default, so "on" on macOS and "off" everywhere else - #[default] - PlatformDefault, - /// Close the window when there are no tabs - CloseWindow, - /// Leave the window open when there are no tabs - KeepWindowOpen, -} - -impl CloseWindowWhenNoItems { - pub fn should_close(&self) -> bool { - match self { - CloseWindowWhenNoItems::PlatformDefault => cfg!(target_os = "macos"), - CloseWindowWhenNoItems::CloseWindow => true, - CloseWindowWhenNoItems::KeepWindowOpen => false, - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum RestoreOnStartupBehavior { - /// Always start with an empty editor - None, - /// Restore the workspace that was closed last. - LastWorkspace, - /// Restore all workspaces that were open when quitting Zed. - #[default] - LastSession, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct WorkspaceSettingsContent { - /// Active pane styling settings. - pub active_pane_modifiers: Option, - /// Layout mode for the bottom dock - /// - /// Default: contained - pub bottom_dock_layout: Option, - /// Direction to split horizontally. - /// - /// Default: "up" - pub pane_split_direction_horizontal: Option, - /// Direction to split vertically. - /// - /// Default: "left" - pub pane_split_direction_vertical: Option, - /// Centered layout related settings. - pub centered_layout: Option, - /// Whether or not to prompt the user to confirm before closing the application. - /// - /// Default: false - pub confirm_quit: Option, - /// Whether or not to show the call status icon in the status bar. - /// - /// Default: true - pub show_call_status_icon: Option, - /// When to automatically save edited buffers. - /// - /// Default: off - pub autosave: Option, - /// Controls previous session restoration in freshly launched Zed instance. - /// Values: none, last_workspace, last_session - /// Default: last_session - pub restore_on_startup: Option, - /// Whether to attempt to restore previous file's state when opening it again. - /// The state is stored per pane. - /// When disabled, defaults are applied instead of the state restoration. - /// - /// E.g. for editors, selections, folds and scroll positions are restored, if the same file is closed and, later, opened again in the same pane. - /// When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default. - /// - /// Default: true - pub restore_on_file_reopen: Option, - /// The size of the workspace split drop targets on the outer edges. - /// Given as a fraction that will be multiplied by the smaller dimension of the workspace. - /// - /// Default: `0.2` (20% of the smaller dimension of the workspace) - pub drop_target_size: Option, - /// Whether to close the window when using 'close active item' on a workspace with no tabs - /// - /// Default: auto ("on" on macOS, "off" otherwise) - pub when_closing_with_no_tabs: Option, - /// Whether to use the system provided dialogs for Open and Save As. - /// When set to false, Zed will use the built-in keyboard-first pickers. - /// - /// Default: true - pub use_system_path_prompts: Option, - /// Whether to use the system provided prompts. - /// When set to false, Zed will use the built-in prompts. - /// Note that this setting has no effect on Linux, where Zed will always - /// use the built-in prompts. - /// - /// Default: true - pub use_system_prompts: Option, - /// Aliases for the command palette. When you type a key in this map, - /// it will be assumed to equal the value. - /// - /// Default: true - pub command_aliases: Option>, - /// Maximum open tabs in a pane. Will not close an unsaved - /// tab. Set to `None` for unlimited tabs. - /// - /// Default: none - pub max_tabs: Option, - /// What to do when the last window is closed - /// - /// Default: auto (nothing on macOS, "app quit" otherwise) - pub on_last_window_closed: Option, - /// Whether to resize all the panels in a dock when resizing the dock. - /// - /// Default: ["left"] - pub resize_all_panels_in_dock: Option>, - /// Whether to automatically close files that have been deleted on disk. - /// - /// Default: false - pub close_on_file_delete: Option, - /// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only). - /// - /// Default: false - pub use_system_window_tabs: Option, - /// Whether to show padding for zoomed panels. - /// When enabled, zoomed bottom panels will have some top padding, - /// while zoomed left/right panels will have padding to the right/left (respectively). - /// - /// Default: true - pub zoomed_padding: Option, -} - #[derive(Deserialize)] pub struct TabBarSettings { pub show: bool, @@ -224,84 +62,118 @@ pub struct TabBarSettings { pub show_tab_bar_buttons: bool, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(key = "tab_bar")] -pub struct TabBarSettingsContent { - /// Whether or not to show the tab bar in the editor. - /// - /// Default: true - pub show: Option, - /// Whether or not to show the navigation history buttons in the tab bar. - /// - /// Default: true - pub show_nav_history_buttons: Option, - /// Whether or not to show the tab bar buttons. - /// - /// Default: true - pub show_tab_bar_buttons: Option, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AutosaveSetting { - /// Disable autosave. - Off, - /// Save after inactivity period of `milliseconds`. - AfterDelay { milliseconds: u64 }, - /// Autosave when focus changes. - OnFocusChange, - /// Autosave when the active window changes. - OnWindowChange, -} - -impl AutosaveSetting { - pub fn should_save_on_close(&self) -> bool { - matches!( - &self, - AutosaveSetting::OnFocusChange - | AutosaveSetting::OnWindowChange - | AutosaveSetting::AfterDelay { .. } - ) +impl Settings for WorkspaceSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let workspace = &content.workspace; + Self { + active_pane_modifiers: ActivePanelModifiers { + border_size: Some( + workspace + .active_pane_modifiers + .unwrap() + .border_size + .unwrap(), + ), + inactive_opacity: Some( + workspace + .active_pane_modifiers + .unwrap() + .inactive_opacity + .unwrap(), + ), + }, + bottom_dock_layout: workspace.bottom_dock_layout.unwrap(), + pane_split_direction_horizontal: workspace.pane_split_direction_horizontal.unwrap(), + pane_split_direction_vertical: workspace.pane_split_direction_vertical.unwrap(), + centered_layout: workspace.centered_layout.unwrap(), + confirm_quit: workspace.confirm_quit.unwrap(), + show_call_status_icon: workspace.show_call_status_icon.unwrap(), + autosave: workspace.autosave.unwrap(), + restore_on_startup: workspace.restore_on_startup.unwrap(), + restore_on_file_reopen: workspace.restore_on_file_reopen.unwrap(), + drop_target_size: workspace.drop_target_size.unwrap(), + use_system_path_prompts: workspace.use_system_path_prompts.unwrap(), + use_system_prompts: workspace.use_system_prompts.unwrap(), + command_aliases: workspace.command_aliases.clone(), + max_tabs: workspace.max_tabs, + when_closing_with_no_tabs: workspace.when_closing_with_no_tabs.unwrap(), + on_last_window_closed: workspace.on_last_window_closed.unwrap(), + resize_all_panels_in_dock: workspace + .resize_all_panels_in_dock + .clone() + .unwrap() + .into_iter() + .map(Into::into) + .collect(), + close_on_file_delete: workspace.close_on_file_delete.unwrap(), + use_system_window_tabs: workspace.use_system_window_tabs.unwrap(), + zoomed_padding: workspace.zoomed_padding.unwrap(), + } } -} -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum PaneSplitDirectionHorizontal { - Up, - Down, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum PaneSplitDirectionVertical { - Left, - Right, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)] -#[serde(rename_all = "snake_case")] -pub struct CenteredLayoutSettings { - /// The relative width of the left padding of the central pane from the - /// workspace when the centered layout is used. - /// - /// Default: 0.2 - pub left_padding: Option, - // The relative width of the right padding of the central pane from the - // workspace when the centered layout is used. - /// - /// Default: 0.2 - pub right_padding: Option, -} + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let workspace = &content.workspace; + if let Some(border_size) = workspace + .active_pane_modifiers + .and_then(|modifier| modifier.border_size) + { + self.active_pane_modifiers.border_size = Some(border_size); + } -impl Settings for WorkspaceSettings { - type FileContent = WorkspaceSettingsContent; + if let Some(inactive_opacity) = workspace + .active_pane_modifiers + .and_then(|modifier| modifier.inactive_opacity) + { + self.active_pane_modifiers.inactive_opacity = Some(inactive_opacity); + } - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + self.bottom_dock_layout + .merge_from(&workspace.bottom_dock_layout); + self.pane_split_direction_horizontal + .merge_from(&workspace.pane_split_direction_horizontal); + self.pane_split_direction_vertical + .merge_from(&workspace.pane_split_direction_vertical); + self.centered_layout.merge_from(&workspace.centered_layout); + self.confirm_quit.merge_from(&workspace.confirm_quit); + self.show_call_status_icon + .merge_from(&workspace.show_call_status_icon); + self.autosave.merge_from(&workspace.autosave); + self.restore_on_startup + .merge_from(&workspace.restore_on_startup); + self.restore_on_file_reopen + .merge_from(&workspace.restore_on_file_reopen); + self.drop_target_size + .merge_from(&workspace.drop_target_size); + self.use_system_path_prompts + .merge_from(&workspace.use_system_path_prompts); + self.use_system_prompts + .merge_from(&workspace.use_system_prompts); + self.command_aliases + .extend(workspace.command_aliases.clone()); + if let Some(max_tabs) = workspace.max_tabs { + self.max_tabs = Some(max_tabs); + } + self.when_closing_with_no_tabs + .merge_from(&workspace.when_closing_with_no_tabs); + self.on_last_window_closed + .merge_from(&workspace.on_last_window_closed); + self.resize_all_panels_in_dock.merge_from( + &workspace + .resize_all_panels_in_dock + .as_ref() + .map(|resize| resize.clone().into_iter().map(Into::into).collect()), + ); + self.close_on_file_delete + .merge_from(&workspace.close_on_file_delete); + self.use_system_window_tabs + .merge_from(&workspace.use_system_window_tabs); + self.zoomed_padding.merge_from(&workspace.zoomed_padding); } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode( + vscode: &settings::VsCodeSettings, + current: &mut settings::SettingsContent, + ) { if vscode .read_bool("accessibility.dimUnfocused.enabled") .unwrap_or_default() @@ -309,19 +181,16 @@ impl Settings for WorkspaceSettings { .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() - }) - } + current + .workspace + .active_pane_modifiers + .get_or_insert_default() + .inactive_opacity = Some(opacity as f32); } vscode.enum_setting( "window.confirmBeforeClose", - &mut current.confirm_quit, + &mut current.workspace.confirm_quit, |s| match s { "always" | "keyboardOnly" => Some(true), "never" => Some(false), @@ -331,22 +200,22 @@ impl Settings for WorkspaceSettings { vscode.bool_setting( "workbench.editor.restoreViewState", - &mut current.restore_on_file_reopen, + &mut current.workspace.restore_on_file_reopen, ); if let Some(b) = vscode.read_bool("window.closeWhenEmpty") { - current.when_closing_with_no_tabs = Some(if b { - CloseWindowWhenNoItems::CloseWindow + current.workspace.when_closing_with_no_tabs = Some(if b { + settings::CloseWindowWhenNoItems::CloseWindow } else { - CloseWindowWhenNoItems::KeepWindowOpen - }) + settings::CloseWindowWhenNoItems::KeepWindowOpen + }); } if let Some(b) = vscode.read_bool("files.simpleDialog.enable") { - current.use_system_path_prompts = Some(!b); + current.workspace.use_system_path_prompts = Some(!b); } - vscode.enum_setting("files.autoSave", &mut current.autosave, |s| match s { + if let Some(v) = vscode.read_enum("files.autoSave", |s| match s { "off" => Some(AutosaveSetting::Off), "afterDelay" => Some(AutosaveSetting::AfterDelay { milliseconds: vscode @@ -357,7 +226,9 @@ impl Settings for WorkspaceSettings { "onFocusChange" => Some(AutosaveSetting::OnFocusChange), "onWindowChange" => Some(AutosaveSetting::OnWindowChange), _ => None, - }); + }) { + current.workspace.autosave = Some(v); + } // workbench.editor.limit contains "enabled", "value", and "perEditorGroup" // our semantics match if those are set to true, some N, and true respectively. @@ -370,10 +241,12 @@ impl Settings for WorkspaceSettings { .read_bool("workbench.editor.limit.enabled") .unwrap_or_default() { - current.max_tabs = Some(n) + current.workspace.max_tabs = Some(n) } - vscode.bool_setting("window.nativeTabs", &mut current.use_system_window_tabs); + if let Some(b) = vscode.read_bool("window.nativeTabs") { + current.workspace.use_system_window_tabs = Some(b); + } // some combination of "window.restoreWindows" and "workbench.startupEditor" might // map to our "restore_on_startup" @@ -384,24 +257,39 @@ impl Settings for WorkspaceSettings { } impl Settings for TabBarSettings { - type FileContent = TabBarSettingsContent; + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let tab_bar = content.tab_bar.clone().unwrap(); + TabBarSettings { + show: tab_bar.show.unwrap(), + show_nav_history_buttons: tab_bar.show_nav_history_buttons.unwrap(), + show_tab_bar_buttons: tab_bar.show_tab_bar_buttons.unwrap(), + } + } - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { + let Some(tab_bar) = &content.tab_bar else { + return; + }; + self.show.merge_from(&tab_bar.show); + self.show_nav_history_buttons + .merge_from(&tab_bar.show_nav_history_buttons); + self.show_tab_bar_buttons + .merge_from(&tab_bar.show_tab_bar_buttons); } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.enum_setting( - "workbench.editor.showTabs", - &mut current.show, - |s| match s { - "multiple" => Some(true), - "single" | "none" => Some(false), - _ => None, - }, - ); + fn import_from_vscode( + vscode: &settings::VsCodeSettings, + current: &mut settings::SettingsContent, + ) { + if let Some(b) = vscode.read_enum("workbench.editor.showTabs", |s| match s { + "multiple" => Some(true), + "single" | "none" => Some(false), + _ => None, + }) { + current.tab_bar.get_or_insert_default().show = Some(b); + } if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") { - current.show_tab_bar_buttons = Some(false) + current.tab_bar.get_or_insert_default().show_tab_bar_buttons = Some(false) } } } diff --git a/crates/worktree/Cargo.toml b/crates/worktree/Cargo.toml index 507b95c00d52435411e307f8d68dafd718177598..fdeca37b7ac73759fe9851f722985349e0a183b7 100644 --- a/crates/worktree/Cargo.toml +++ b/crates/worktree/Cargo.toml @@ -38,7 +38,6 @@ parking_lot.workspace = true paths.workspace = true postage.workspace = true rpc = { workspace = true, features = ["gpui"] } -schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index ca7714fa7315117a5c2e31b06d2f059ad43cb1a9..5093988ac2ac66682aba9447a6347ecfc54ecf76 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -2,10 +2,8 @@ use std::path::Path; use anyhow::Context as _; use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; -use util::paths::PathMatcher; +use settings::{Settings, SettingsContent}; +use util::{ResultExt, paths::PathMatcher}; #[derive(Clone, PartialEq, Eq)] pub struct WorktreeSettings { @@ -32,57 +30,13 @@ impl WorktreeSettings { } } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct WorktreeSettingsContent { - /// The displayed name of this project. If not set, the root directory name - /// will be displayed. - /// - /// Default: none - #[serde(default)] - pub project_name: Option, - - /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides - /// `file_scan_inclusions`. - /// - /// Default: [ - /// "**/.git", - /// "**/.svn", - /// "**/.hg", - /// "**/.jj", - /// "**/CVS", - /// "**/.DS_Store", - /// "**/Thumbs.db", - /// "**/.classpath", - /// "**/.settings" - /// ] - #[serde(default)] - pub file_scan_exclusions: Option>, - - /// Always include files that match these globs when scanning for files, even if they're - /// ignored by git. This setting is overridden by `file_scan_exclusions`. - /// Default: [ - /// ".env*", - /// "docker-compose.*.yml", - /// ] - #[serde(default)] - pub file_scan_inclusions: Option>, - - /// Treat the files matching these globs as `.env` files. - /// Default: [ "**/.env*" ] - pub private_files: Option>, -} - impl Settings for WorktreeSettings { - type FileContent = WorktreeSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { - let result: WorktreeSettingsContent = sources.json_merge()?; - let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default(); - let mut private_files = result.private_files.unwrap_or_default(); - let mut parsed_file_scan_inclusions: Vec = result - .file_scan_inclusions - .unwrap_or_default() + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let worktree = content.project.worktree.clone(); + let file_scan_exclusions = worktree.file_scan_exclusions.unwrap(); + let file_scan_inclusions = worktree.file_scan_inclusions.unwrap(); + let private_files = worktree.private_files.unwrap(); + let parsed_file_scan_inclusions: Vec = file_scan_inclusions .iter() .flat_map(|glob| { Path::new(glob) @@ -91,30 +45,71 @@ impl Settings for WorktreeSettings { }) .filter(|p: &String| !p.is_empty()) .collect(); - file_scan_exclusions.sort(); - private_files.sort(); - parsed_file_scan_inclusions.sort(); - Ok(Self { - file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?, - private_files: path_matchers(&private_files, "private_files")?, + + Self { + project_name: None, + file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions") + .unwrap(), file_scan_inclusions: path_matchers( - &parsed_file_scan_inclusions, + parsed_file_scan_inclusions, "file_scan_inclusions", - )?, - project_name: result.project_name, - }) + ) + .unwrap(), + private_files: path_matchers(private_files, "private_files").unwrap(), + } + } + + fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { + let worktree = &content.project.worktree; + + if let Some(project_name) = worktree.project_name.clone() { + self.project_name = Some(project_name); + } + + if let Some(mut private_files) = worktree.private_files.clone() { + let sources = self.private_files.sources(); + private_files.extend_from_slice(sources); + if let Some(matchers) = path_matchers(private_files, "private_files").log_err() { + self.private_files = matchers; + } + } + + if let Some(file_scan_exclusions) = worktree.file_scan_exclusions.clone() { + if let Some(matchers) = + path_matchers(file_scan_exclusions, "file_scan_exclusions").log_err() + { + self.file_scan_exclusions = matchers + } + } + + if let Some(file_scan_inclusions) = worktree.file_scan_inclusions.clone() { + let parsed_file_scan_inclusions: Vec = file_scan_inclusions + .iter() + .flat_map(|glob| { + Path::new(glob) + .ancestors() + .map(|a| a.to_string_lossy().into()) + }) + .filter(|p: &String| !p.is_empty()) + .collect(); + if let Some(matchers) = + path_matchers(parsed_file_scan_inclusions, "file_scan_inclusions").log_err() + { + self.file_scan_inclusions = matchers + } + } } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { + fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(inclusions) = vscode .read_value("files.watcherInclude") .and_then(|v| v.as_array()) .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect()) { - if let Some(old) = current.file_scan_inclusions.as_mut() { + if let Some(old) = current.project.worktree.file_scan_inclusions.as_mut() { old.extend(inclusions) } else { - current.file_scan_inclusions = Some(inclusions) + current.project.worktree.file_scan_inclusions = Some(inclusions) } } if let Some(exclusions) = vscode @@ -122,15 +117,16 @@ impl Settings for WorktreeSettings { .and_then(|v| v.as_array()) .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect()) { - if let Some(old) = current.file_scan_exclusions.as_mut() { + if let Some(old) = current.project.worktree.file_scan_exclusions.as_mut() { old.extend(exclusions) } else { - current.file_scan_exclusions = Some(exclusions) + current.project.worktree.file_scan_exclusions = Some(exclusions) } } } } -fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result { +fn path_matchers(mut values: Vec, context: &'static str) -> anyhow::Result { + values.sort(); PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context)) } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 5022c8965192a36b50e3b0a325a87b3988e28efd..d30d9bb450cdf19a74db18d2b6c2333f19a15b77 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -770,9 +770,9 @@ async fn test_file_scan_inclusions(cx: &mut TestAppContext) { })); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(vec![]); - project_settings.file_scan_inclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![]); + settings.project.worktree.file_scan_inclusions = Some(vec![ "node_modules/**/package.json".to_string(), "**/.DS_Store".to_string(), ]); @@ -836,9 +836,11 @@ async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext) cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(vec!["**/.DS_Store".to_string()]); - project_settings.file_scan_inclusions = Some(vec!["**/.DS_Store".to_string()]); + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = + Some(vec!["**/.DS_Store".to_string()]); + settings.project.worktree.file_scan_inclusions = + Some(vec!["**/.DS_Store".to_string()]); }); }); }); @@ -894,9 +896,10 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(vec![]); - project_settings.file_scan_inclusions = Some(vec!["node_modules/**".to_string()]); + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![]); + settings.project.worktree.file_scan_inclusions = + Some(vec!["node_modules/**".to_string()]); }); }); }); @@ -926,9 +929,9 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(vec![]); - project_settings.file_scan_inclusions = Some(vec![]); + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![]); + settings.project.worktree.file_scan_inclusions = Some(vec![]); }); }); }); @@ -978,8 +981,8 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { })); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]); }); }); @@ -1015,8 +1018,8 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec!["**/node_modules/**".to_string()]); }); }); @@ -1080,8 +1083,8 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { })); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(vec![ + store.update_user_settings(cx, |settings| { + settings.project.worktree.file_scan_exclusions = Some(vec![ "**/.git".to_string(), "node_modules/".to_string(), "build_output".to_string(), diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 1c5dc3ddddb11279ae0d79717aadc18f51350675..c85d3e70245ff1ee1ea1253492643b603b8ca70c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -126,7 +126,6 @@ serde.workspace = true serde_json.workspace = true session.workspace = true settings.workspace = true -settings_ui.workspace = true keymap_editor.workspace = true shellexpand.workspace = true smol.workspace = true @@ -185,7 +184,6 @@ itertools.workspace = true language = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true project = { workspace = true, features = ["test-support"] } -settings_ui = { workspace = true, features = ["test-support"] } terminal_view = { workspace = true, features = ["test-support"] } tree-sitter-md.workspace = true tree-sitter-rust.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 882c29e7b0f25c0f076e142bb7006ea4450c565e..5cce6a6e2974d2fad9638f00811273ee202ab7b6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -614,7 +614,6 @@ pub fn main() { markdown_preview::init(cx); svg_preview::init(cx); onboarding::init(cx); - settings_ui::init(cx); keymap_editor::init(cx); extensions_ui::init(cx); zeta::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 87664d20f0df08761ca6f5cc3a14ad59e978605f..6029ce6530a729ccd0b299bdcab2acbd620bbbdd 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -727,9 +727,10 @@ fn register_actions( let fs = app_state.fs.clone(); move |_, action: &zed_actions::IncreaseUiFontSize, _window, cx| { if action.persist { - update_settings_file::(fs.clone(), cx, move |settings, cx| { + update_settings_file(fs.clone(), cx, move |settings, cx| { let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx) + px(1.0); let _ = settings + .theme .ui_font_size .insert(theme::clamp_font_size(ui_font_size).0); }); @@ -742,9 +743,10 @@ fn register_actions( let fs = app_state.fs.clone(); move |_, action: &zed_actions::DecreaseUiFontSize, _window, cx| { if action.persist { - update_settings_file::(fs.clone(), cx, move |settings, cx| { + update_settings_file(fs.clone(), cx, move |settings, cx| { let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx) - px(1.0); let _ = settings + .theme .ui_font_size .insert(theme::clamp_font_size(ui_font_size).0); }); @@ -757,8 +759,8 @@ fn register_actions( let fs = app_state.fs.clone(); move |_, action: &zed_actions::ResetUiFontSize, _window, cx| { if action.persist { - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.ui_font_size = None; + update_settings_file(fs.clone(), cx, move |settings, _| { + settings.theme.ui_font_size = None; }); } else { theme::reset_ui_font_size(cx); @@ -769,10 +771,11 @@ fn register_actions( let fs = app_state.fs.clone(); move |_, action: &zed_actions::IncreaseBufferFontSize, _window, cx| { if action.persist { - update_settings_file::(fs.clone(), cx, move |settings, cx| { + update_settings_file(fs.clone(), cx, move |settings, cx| { let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size(cx) + px(1.0); let _ = settings + .theme .buffer_font_size .insert(theme::clamp_font_size(buffer_font_size).0); }); @@ -785,10 +788,11 @@ fn register_actions( let fs = app_state.fs.clone(); move |_, action: &zed_actions::DecreaseBufferFontSize, _window, cx| { if action.persist { - update_settings_file::(fs.clone(), cx, move |settings, cx| { + update_settings_file(fs.clone(), cx, move |settings, cx| { let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size(cx) - px(1.0); let _ = settings + .theme .buffer_font_size .insert(theme::clamp_font_size(buffer_font_size).0); }); @@ -801,8 +805,8 @@ fn register_actions( let fs = app_state.fs.clone(); move |_, action: &zed_actions::ResetBufferFontSize, _window, cx| { if action.persist { - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.buffer_font_size = None; + update_settings_file(fs.clone(), cx, move |settings, _| { + settings.theme.buffer_font_size = None; }); } else { theme::reset_buffer_font_size(cx); @@ -1944,7 +1948,7 @@ mod tests { }; use language::{LanguageMatcher, LanguageRegistry}; use pretty_assertions::{assert_eq, assert_ne}; - use project::{Project, ProjectPath, WorktreeSettings, project_settings::ProjectSettings}; + use project::{Project, ProjectPath}; use serde_json::json; use settings::{SettingsStore, watch_config_file}; use std::{ @@ -2249,8 +2253,11 @@ mod tests { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.session.restore_unsaved_buffers = false + store.update_user_settings(cx, |settings| { + settings + .session + .get_or_insert_default() + .restore_unsaved_buffers = Some(false) }); }); }); @@ -2965,8 +2972,8 @@ mod tests { let app_state = init_test(cx); cx.update(|cx| { cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = + store.update_user_settings(cx, |project_settings| { + project_settings.project.worktree.file_scan_exclusions = Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]); }); }); @@ -4795,8 +4802,9 @@ mod tests { // 3. Add .zed to file scan exclusions in user settings cx.update_global::(|store, cx| { - store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(vec![".zed".to_string()]); + store.update_user_settings(cx, |worktree_settings| { + worktree_settings.project.worktree.file_scan_exclusions = + Some(vec![".zed".to_string()]); }); }); @@ -4856,34 +4864,4 @@ mod tests { "BUG FOUND: Project settings were overwritten when opening via command - original custom content was lost" ); } - - #[gpui::test] - fn test_settings_defaults(cx: &mut TestAppContext) { - cx.update(|cx| { - settings::init(cx); - workspace::init_settings(cx); - title_bar::init(cx); - editor::init_settings(cx); - debugger_ui::init(cx); - }); - let default_json = - cx.read(|cx| cx.global::().raw_default_settings().clone()); - - let all_paths = cx.read(|cx| settings_ui::SettingsUiTree::new(cx).all_paths(cx)); - let mut failures = Vec::new(); - for path in all_paths { - if settings_ui::read_settings_value_from_path(&default_json, &path).is_none() { - failures.push(path); - } - } - if !failures.is_empty() { - panic!( - "No default value found for paths: {:#?}", - failures - .into_iter() - .map(|path| path.join(".")) - .collect::>() - ); - } - } } diff --git a/crates/zeta/src/init.rs b/crates/zeta/src/init.rs index f27667de6332bf4c3b8d2d705f281c9e3ba96a83..0167d878fa34976d7175a64269d9dfe29d18d8fe 100644 --- a/crates/zeta/src/init.rs +++ b/crates/zeta/src/init.rs @@ -3,7 +3,7 @@ use std::any::{Any, TypeId}; use command_palette_hooks::CommandPaletteFilter; use feature_flags::{FeatureFlagAppExt as _, PredictEditsRateCompletionsFeatureFlag}; use gpui::actions; -use language::language_settings::{AllLanguageSettings, EditPredictionProvider}; +use language::language_settings::EditPredictionProvider; use project::DisableAiSettings; use settings::{Settings, SettingsStore, update_settings_file}; use ui::App; @@ -44,15 +44,14 @@ pub fn init(cx: &mut App) { ); workspace.register_action(|workspace, _: &ResetOnboarding, _window, cx| { - update_settings_file::( - workspace.app_state().fs.clone(), - cx, - move |file, _| { - file.features - .get_or_insert(Default::default()) - .edit_prediction_provider = Some(EditPredictionProvider::None) - }, - ); + update_settings_file(workspace.app_state().fs.clone(), cx, move |settings, _| { + settings + .project + .all_languages + .features + .get_or_insert_default() + .edit_prediction_provider = Some(EditPredictionProvider::None) + }); }); }) .detach(); diff --git a/crates/zeta/src/onboarding_modal.rs b/crates/zeta/src/onboarding_modal.rs index 6b743d95f2d2765be0d332e1aa8a03a9647131aa..94480add3053bece5017cf478e9f74065491639b 100644 --- a/crates/zeta/src/onboarding_modal.rs +++ b/crates/zeta/src/onboarding_modal.rs @@ -9,7 +9,7 @@ use gpui::{ ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render, linear_color_stop, linear_gradient, }; -use language::language_settings::{AllLanguageSettings, EditPredictionProvider}; +use language::language_settings::EditPredictionProvider; use settings::update_settings_file; use ui::{Vector, VectorName, prelude::*}; use workspace::{ModalView, Workspace}; @@ -22,8 +22,10 @@ pub struct ZedPredictModal { pub(crate) fn set_edit_prediction_provider(provider: EditPredictionProvider, cx: &mut App) { let fs = ::global(cx); - update_settings_file::(fs, cx, move |settings, _| { + update_settings_file(fs, cx, move |settings, _| { settings + .project + .all_languages .features .get_or_insert(Default::default()) .edit_prediction_provider = Some(provider); diff --git a/crates/zeta_cli/src/headless.rs b/crates/zeta_cli/src/headless.rs index a11fd707591d0403409b5bc9e243d35c70fd65d3..bb4cb010cba6ea29a9bcd6d8cc0dc93475dbc2a0 100644 --- a/crates/zeta_cli/src/headless.rs +++ b/crates/zeta_cli/src/headless.rs @@ -31,10 +31,7 @@ pub fn init(cx: &mut App) -> ZetaCliAppState { release_channel::init(app_version, cx); gpui_tokio::init(cx); - let mut settings_store = SettingsStore::new(cx); - settings_store - .set_default_settings(settings::default_settings().as_ref(), cx) - .unwrap(); + let settings_store = SettingsStore::new(cx, &settings::default_settings()); cx.set_global(settings_store); client::init_settings(cx); diff --git a/crates/zlog/Cargo.toml b/crates/zlog/Cargo.toml index d0632d14f2d5b9ca6f26d7f9cbcbb8a341104860..4b758437d5e1608aaa68e86f90215f57b928e883 100644 --- a/crates/zlog/Cargo.toml +++ b/crates/zlog/Cargo.toml @@ -15,6 +15,7 @@ path = "src/zlog.rs" default = [] [dependencies] +collections.workspace = true chrono.workspace = true log.workspace = true workspace-hack.workspace = true diff --git a/crates/zlog/src/filter.rs b/crates/zlog/src/filter.rs index 31a58894774e6c0d08ea22b585350eb26ff09907..9a2de13cb3d33a1a6f4d17f7eddd4754cae40ea3 100644 --- a/crates/zlog/src/filter.rs +++ b/crates/zlog/src/filter.rs @@ -1,9 +1,8 @@ -use std::{ - collections::{HashMap, VecDeque}, - sync::{ - OnceLock, RwLock, - atomic::{AtomicU8, Ordering}, - }, +use collections::HashMap; +use std::collections::VecDeque; +use std::sync::{ + OnceLock, RwLock, + atomic::{AtomicU8, Ordering}, }; use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config, private}; diff --git a/crates/zlog_settings/Cargo.toml b/crates/zlog_settings/Cargo.toml index 2555d18aadc162161eb3aedf5c948385f347c6cc..8ec63cefe447d944b4556f1e72e481d5461391f1 100644 --- a/crates/zlog_settings/Cargo.toml +++ b/crates/zlog_settings/Cargo.toml @@ -15,10 +15,8 @@ path = "src/zlog_settings.rs" default = [] [dependencies] -anyhow.workspace = true gpui.workspace = true -schemars.workspace = true -serde.workspace = true +collections.workspace = true settings.workspace = true zlog.workspace = true workspace-hack.workspace = true diff --git a/crates/zlog_settings/src/zlog_settings.rs b/crates/zlog_settings/src/zlog_settings.rs index dd74fc574ff23dc78beca1feafeb34d874a68c22..0d464b872067683e7fad8db6f4049eff92d5ed8b 100644 --- a/crates/zlog_settings/src/zlog_settings.rs +++ b/crates/zlog_settings/src/zlog_settings.rs @@ -1,9 +1,8 @@ //! # zlog_settings -use anyhow::Result; +use collections::HashMap; + use gpui::App; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsStore, SettingsUi}; +use settings::{Settings, SettingsStore}; pub fn init(cx: &mut App) { ZlogSettings::register(cx); @@ -15,33 +14,27 @@ pub fn init(cx: &mut App) { .detach(); } -#[derive( - Clone, - Debug, - Default, - Serialize, - Deserialize, - PartialEq, - Eq, - JsonSchema, - SettingsUi, - SettingsKey, -)] -#[settings_key(key = "log")] +#[derive(Clone, Debug)] pub struct ZlogSettings { - #[serde(default, flatten)] - pub scopes: std::collections::HashMap, + /// A map of log scopes to the desired log level. + /// Useful for filtering out noisy logs or enabling more verbose logging. + /// + /// Example: {"log": {"client": "warn"}} + pub scopes: HashMap, } impl Settings for ZlogSettings { - type FileContent = Self; + fn from_defaults(content: &settings::SettingsContent, _: &mut App) -> Self { + ZlogSettings { + scopes: content.log.clone().unwrap(), + } + } - fn load(sources: settings::SettingsSources, _: &mut App) -> Result - where - Self: Sized, - { - sources.json_merge() + fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { + if let Some(log) = &content.log { + self.scopes.extend(log.clone()); + } } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} + fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} } From 55d130a166d43618d014408d3b2bd9d39ceb47d2 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:16:36 -0400 Subject: [PATCH 118/721] Fix chunks peek_with_bitmaps panic (#38430) This panic only happened in debug builds because of a left shift overflow. The slice range has bounds between 0 and 128. The 128 case caused the overflow. We now do an unbounded shift and a wrapped sub to get the correct bitmask. If the slice range is 128 left, it should make 1 zero. Then the wrapped sub would flip all bits, which is expected behavior. Release Notes: - N/A Co-authored-by: Nia --- crates/rope/src/rope.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 3f6addb7c2394503098a213f4139fedc9757ba86..9c98985989c2ac0fdcd5a39342dd9911d64dd01a 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -784,7 +784,10 @@ impl<'a> Chunks<'a> { slice_start..slice_end }; - let bitmask = (1u128 << slice_range.end as u128).saturating_sub(1); + // slice range has a bounds between 0 and 128 in non test builds + // We use a non wrapping sub because we want to overflow in the case where slice_range.end == 128 + // because that represents a full chunk and the bitmask shouldn't remove anything + let bitmask = (1u128.unbounded_shl(slice_range.end as u32)).wrapping_sub(1); let chars = (chunk.chars() & bitmask) >> slice_range.start; let tabs = (chunk.tabs & bitmask) >> slice_range.start; From df50b5c14a7cfae0d07c4e5d6a0ff91bd5587c89 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 18 Sep 2025 15:09:44 -0300 Subject: [PATCH 119/721] edit prediction: Context debug view (#38435) Adds a `dev: open edit prediction context` action that opens a new workspace pane that displays the excerpts and snippets that would be included in the edit prediction request. Release Notes: - N/A --------- Co-authored-by: Bennet --- Cargo.lock | 28 ++ Cargo.toml | 2 + .../src/declaration.rs | 6 +- .../src/declaration_scoring.rs | 6 +- .../src/edit_prediction_context.rs | 20 +- crates/edit_prediction_context/src/excerpt.rs | 2 +- .../edit_prediction_context/src/reference.rs | 3 +- .../src/syntax_index.rs | 4 +- crates/edit_prediction_tools/Cargo.toml | 41 ++ crates/edit_prediction_tools/LICENSE-GPL | 1 + .../src/edit_prediction_tools.rs | 457 ++++++++++++++++++ crates/language/src/language_registry.rs | 18 + crates/ui_input/src/ui_input.rs | 12 +- crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + 15 files changed, 583 insertions(+), 19 deletions(-) create mode 100644 crates/edit_prediction_tools/Cargo.toml create mode 120000 crates/edit_prediction_tools/LICENSE-GPL create mode 100644 crates/edit_prediction_tools/src/edit_prediction_tools.rs diff --git a/Cargo.lock b/Cargo.lock index b80e101f868dbe8dd7d6d939a6ab0c94951ed555..28247c7f4c6bcab64a8acce951e072e80abf751b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5197,6 +5197,33 @@ dependencies = [ "zlog", ] +[[package]] +name = "edit_prediction_tools" +version = "0.1.0" +dependencies = [ + "clap", + "collections", + "edit_prediction_context", + "editor", + "futures 0.3.31", + "gpui", + "indoc", + "language", + "log", + "pretty_assertions", + "project", + "serde", + "serde_json", + "settings", + "text", + "ui", + "ui_input", + "util", + "workspace", + "workspace-hack", + "zlog", +] + [[package]] name = "editor" version = "0.1.0" @@ -21217,6 +21244,7 @@ dependencies = [ "debugger_ui", "diagnostics", "edit_prediction_button", + "edit_prediction_tools", "editor", "env_logger 0.11.8", "extension", diff --git a/Cargo.toml b/Cargo.toml index 08a9b41315c36d7facd7b9d1751b949a2577395c..8a67b28b0e4a7b6e146f4c6a4b84c73d229ceb27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "crates/edit_prediction", "crates/edit_prediction_button", "crates/edit_prediction_context", + "crates/edit_prediction_tools", "crates/editor", "crates/eval", "crates/explorer_command_injector", @@ -314,6 +315,7 @@ image_viewer = { path = "crates/image_viewer" } edit_prediction = { path = "crates/edit_prediction" } edit_prediction_button = { path = "crates/edit_prediction_button" } edit_prediction_context = { path = "crates/edit_prediction_context" } +edit_prediction_tools = { path = "crates/edit_prediction_tools" } inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } jj = { path = "crates/jj" } diff --git a/crates/edit_prediction_context/src/declaration.rs b/crates/edit_prediction_context/src/declaration.rs index fcf54fead80194fe97a2719971f86318a57ad75c..8fba85367c70e2cb1211e343bba7a6675a5a5360 100644 --- a/crates/edit_prediction_context/src/declaration.rs +++ b/crates/edit_prediction_context/src/declaration.rs @@ -41,14 +41,14 @@ impl Declaration { } } - pub fn project_entry_id(&self) -> Option { + pub fn project_entry_id(&self) -> ProjectEntryId { match self { Declaration::File { project_entry_id, .. - } => Some(*project_entry_id), + } => *project_entry_id, Declaration::Buffer { project_entry_id, .. - } => Some(*project_entry_id), + } => *project_entry_id, } } diff --git a/crates/edit_prediction_context/src/declaration_scoring.rs b/crates/edit_prediction_context/src/declaration_scoring.rs index dc442710516a935a65e755393fdfc15026ff1f0e..4cbc4e83c02e0cae912813a261d99f8fe8c41b55 100644 --- a/crates/edit_prediction_context/src/declaration_scoring.rs +++ b/crates/edit_prediction_context/src/declaration_scoring.rs @@ -119,15 +119,13 @@ pub fn scored_snippets( ) }) } else { - // TODO should we prefer the current file instead? - Some((false, 0, declaration)) + Some((false, u32::MAX, declaration)) } } Declaration::File { .. } => { - // TODO should we prefer the current file instead? // We can assume that a file declaration is in a different file, // because the current one must be open - Some((false, 0, declaration)) + Some((false, u32::MAX, declaration)) } }) .sorted_by_key(|&(_, distance, _)| distance) diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs index 5d73dc7f7dcf2223ae1f23b22c2f104842206e12..aed2953777d82d65b7e9cb42229d78634d5e4a3d 100644 --- a/crates/edit_prediction_context/src/edit_prediction_context.rs +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -6,8 +6,12 @@ mod reference; mod syntax_index; mod text_similarity; +use std::time::Instant; + pub use declaration::{BufferDeclaration, Declaration, FileDeclaration, Identifier}; +pub use declaration_scoring::SnippetStyle; pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions, EditPredictionExcerptText}; + use gpui::{App, AppContext as _, Entity, Task}; use language::BufferSnapshot; pub use reference::references_in_excerpt; @@ -16,10 +20,12 @@ use text::{Point, ToOffset as _}; use crate::declaration_scoring::{ScoredSnippet, scored_snippets}; +#[derive(Debug)] pub struct EditPredictionContext { pub excerpt: EditPredictionExcerpt, pub excerpt_text: EditPredictionExcerptText, pub snippets: Vec, + pub retrieval_duration: std::time::Duration, } impl EditPredictionContext { @@ -29,14 +35,14 @@ impl EditPredictionContext { excerpt_options: EditPredictionExcerptOptions, syntax_index: Entity, cx: &mut App, - ) -> Task { + ) -> Task> { + let start = Instant::now(); let index_state = syntax_index.read_with(cx, |index, _cx| index.state().clone()); cx.background_spawn(async move { let index_state = index_state.lock().await; let excerpt = - EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &excerpt_options) - .unwrap(); + EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &excerpt_options)?; let excerpt_text = excerpt.text(&buffer); let references = references_in_excerpt(&excerpt, &excerpt_text, &buffer); let cursor_offset = cursor_point.to_offset(&buffer); @@ -50,11 +56,12 @@ impl EditPredictionContext { &buffer, ); - Self { + Some(Self { excerpt, excerpt_text, snippets, - } + retrieval_duration: start.elapsed(), + }) }) } } @@ -107,7 +114,8 @@ mod tests { cx, ) }) - .await; + .await + .unwrap(); assert_eq!(context.snippets.len(), 1); assert_eq!(context.snippets[0].identifier.name.as_ref(), "process_data"); diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index da1de042623167d17f078c1e85b461fb0ecc8c24..3fde142093efd095129f51a83f836953a76d20cb 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -38,7 +38,7 @@ pub struct EditPredictionExcerpt { pub size: usize, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EditPredictionExcerptText { pub body: String, pub parent_signatures: Vec, diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs index ee2fc7ba573c3909b5a650e3ca0ff20155272b9f..975f15c81f44dad9ee1d3105f6a5863a4685b164 100644 --- a/crates/edit_prediction_context/src/reference.rs +++ b/crates/edit_prediction_context/src/reference.rs @@ -89,7 +89,8 @@ pub fn identifiers_in_range( } let identifier_text = - &range_text[node_range.start - range.start..node_range.end - range.start]; + // TODO we changed this to saturating_sub for now, but we should fix the actually issue + &range_text[node_range.start.saturating_sub(range.start)..node_range.end.saturating_sub(range.start)]; references.push(Reference { identifier: Identifier { name: identifier_text.into(), diff --git a/crates/edit_prediction_context/src/syntax_index.rs b/crates/edit_prediction_context/src/syntax_index.rs index 852973dd7296647b0f868c3f9242ed59b81b6743..64982f5805f08a3ba791578e28778f0c8399fde8 100644 --- a/crates/edit_prediction_context/src/syntax_index.rs +++ b/crates/edit_prediction_context/src/syntax_index.rs @@ -9,7 +9,7 @@ use project::worktree_store::{WorktreeStore, WorktreeStoreEvent}; use project::{PathChange, Project, ProjectEntryId, ProjectPath}; use slotmap::SlotMap; use text::BufferId; -use util::{ResultExt as _, debug_panic, some_or_debug_panic}; +use util::{debug_panic, some_or_debug_panic}; use crate::declaration::{ BufferDeclaration, Declaration, DeclarationId, FileDeclaration, Identifier, @@ -332,7 +332,7 @@ impl SyntaxIndex { let language = language_registry .language_for_file_path(&project_path.path) .await - .log_err(); + .ok(); let buffer = cx.new(|cx| { let mut buffer = Buffer::local(loaded_file.text, cx); diff --git a/crates/edit_prediction_tools/Cargo.toml b/crates/edit_prediction_tools/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ffd34abb2537006dd914d9bf9d30b735de91c5ba --- /dev/null +++ b/crates/edit_prediction_tools/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "edit_prediction_tools" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/edit_prediction_tools.rs" + +[dependencies] +edit_prediction_context.workspace = true +collections.workspace = true +editor.workspace = true +gpui.workspace = true +language.workspace = true +log.workspace = true +project.workspace = true +serde.workspace = true +text.workspace = true +ui.workspace = true +ui_input.workspace = true +workspace-hack.workspace = true +workspace.workspace = true + +[dev-dependencies] +clap.workspace = true +futures.workspace = true +gpui = { workspace = true, features = ["test-support"] } +indoc.workspace = true +language = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true +project = {workspace= true, features = ["test-support"]} +serde_json.workspace = true +settings = {workspace= true, features = ["test-support"]} +text = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +zlog.workspace = true diff --git a/crates/edit_prediction_tools/LICENSE-GPL b/crates/edit_prediction_tools/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/edit_prediction_tools/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/edit_prediction_tools/src/edit_prediction_tools.rs b/crates/edit_prediction_tools/src/edit_prediction_tools.rs new file mode 100644 index 0000000000000000000000000000000000000000..f00a16e026704f1d1da318956f41128a9783a54c --- /dev/null +++ b/crates/edit_prediction_tools/src/edit_prediction_tools.rs @@ -0,0 +1,457 @@ +use std::{ + collections::hash_map::Entry, + ffi::OsStr, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, + time::Duration, +}; + +use collections::HashMap; +use editor::{Editor, EditorEvent, EditorMode, ExcerptRange, MultiBuffer}; +use gpui::{ + Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, actions, + prelude::*, +}; +use language::{Buffer, DiskState}; +use project::{Project, WorktreeId}; +use text::ToPoint; +use ui::prelude::*; +use ui_input::SingleLineInput; +use workspace::{Item, SplitDirection, Workspace}; + +use edit_prediction_context::{ + EditPredictionContext, EditPredictionExcerptOptions, SnippetStyle, SyntaxIndex, +}; + +actions!( + dev, + [ + /// Opens the language server protocol logs viewer. + OpenEditPredictionContext + ] +); + +pub fn init(cx: &mut App) { + cx.observe_new(move |workspace: &mut Workspace, _, _cx| { + workspace.register_action( + move |workspace, _: &OpenEditPredictionContext, window, cx| { + let workspace_entity = cx.entity(); + let project = workspace.project(); + let active_editor = workspace.active_item_as::(cx); + workspace.split_item( + SplitDirection::Right, + Box::new(cx.new(|cx| { + EditPredictionTools::new( + &workspace_entity, + &project, + active_editor, + window, + cx, + ) + })), + window, + cx, + ); + }, + ); + }) + .detach(); +} + +pub struct EditPredictionTools { + focus_handle: FocusHandle, + project: Entity, + last_context: Option, + max_bytes_input: Entity, + min_bytes_input: Entity, + cursor_context_ratio_input: Entity, + // TODO move to project or provider? + syntax_index: Entity, + last_editor: WeakEntity, + _active_editor_subscription: Option, + _edit_prediction_context_task: Task<()>, +} + +struct ContextState { + context_editor: Entity, + retrieval_duration: Duration, +} + +impl EditPredictionTools { + pub fn new( + workspace: &Entity, + project: &Entity, + active_editor: Option>, + window: &mut Window, + cx: &mut Context, + ) -> Self { + cx.subscribe_in(workspace, window, |this, workspace, event, window, cx| { + if let workspace::Event::ActiveItemChanged = event { + if let Some(editor) = workspace.read(cx).active_item_as::(cx) { + this._active_editor_subscription = Some(cx.subscribe_in( + &editor, + window, + |this, editor, event, window, cx| { + if let EditorEvent::SelectionsChanged { .. } = event { + this.update_context(editor, window, cx); + } + }, + )); + this.update_context(&editor, window, cx); + } else { + this._active_editor_subscription = None; + } + } + }) + .detach(); + let syntax_index = cx.new(|cx| SyntaxIndex::new(project, cx)); + + let number_input = |label: &'static str, + value: &'static str, + window: &mut Window, + cx: &mut Context| + -> Entity { + let input = cx.new(|cx| { + let input = SingleLineInput::new(window, cx, "") + .label(label) + .label_min_width(px(64.)); + input.set_text(value, window, cx); + input + }); + cx.subscribe_in( + &input.read(cx).editor().clone(), + window, + |this, _, event, window, cx| { + if let EditorEvent::BufferEdited = event + && let Some(editor) = this.last_editor.upgrade() + { + this.update_context(&editor, window, cx); + } + }, + ) + .detach(); + input + }; + + let mut this = Self { + focus_handle: cx.focus_handle(), + project: project.clone(), + last_context: None, + max_bytes_input: number_input("Max Bytes", "512", window, cx), + min_bytes_input: number_input("Min Bytes", "128", window, cx), + cursor_context_ratio_input: number_input("Cursor Context Ratio", "0.5", window, cx), + syntax_index, + last_editor: WeakEntity::new_invalid(), + _active_editor_subscription: None, + _edit_prediction_context_task: Task::ready(()), + }; + + if let Some(editor) = active_editor { + this.update_context(&editor, window, cx); + } + + this + } + + fn update_context( + &mut self, + editor: &Entity, + window: &mut Window, + cx: &mut Context, + ) { + self.last_editor = editor.downgrade(); + + let editor = editor.read(cx); + let buffer = editor.buffer().clone(); + let cursor_position = editor.selections.newest_anchor().start; + + let Some(buffer) = buffer.read(cx).buffer_for_anchor(cursor_position, cx) else { + self.last_context.take(); + return; + }; + let current_buffer_snapshot = buffer.read(cx).snapshot(); + let cursor_position = cursor_position + .text_anchor + .to_point(¤t_buffer_snapshot); + + let language = current_buffer_snapshot.language().cloned(); + let Some(worktree_id) = self + .project + .read(cx) + .worktrees(cx) + .next() + .map(|worktree| worktree.read(cx).id()) + else { + log::error!("Open a worktree to use edit prediction debug view"); + self.last_context.take(); + return; + }; + + self._edit_prediction_context_task = cx.spawn_in(window, { + let language_registry = self.project.read(cx).languages().clone(); + async move |this, cx| { + cx.background_executor() + .timer(Duration::from_millis(50)) + .await; + + let Ok(task) = this.update(cx, |this, cx| { + fn number_input_value( + input: &Entity, + cx: &App, + ) -> T { + input + .read(cx) + .editor() + .read(cx) + .text(cx) + .parse::() + .unwrap_or_default() + } + + let options = EditPredictionExcerptOptions { + max_bytes: number_input_value(&this.max_bytes_input, cx), + min_bytes: number_input_value(&this.min_bytes_input, cx), + target_before_cursor_over_total_bytes: number_input_value( + &this.cursor_context_ratio_input, + cx, + ), + // TODO Display and add to options + include_parent_signatures: false, + }; + + EditPredictionContext::gather( + cursor_position, + current_buffer_snapshot, + options, + this.syntax_index.clone(), + cx, + ) + }) else { + this.update(cx, |this, _cx| { + this.last_context.take(); + }) + .ok(); + return; + }; + + let Some(context) = task.await else { + // TODO: Display message + this.update(cx, |this, _cx| { + this.last_context.take(); + }) + .ok(); + return; + }; + + let mut languages = HashMap::default(); + for snippet in context.snippets.iter() { + let lang_id = snippet.declaration.identifier().language_id; + if let Entry::Vacant(entry) = languages.entry(lang_id) { + // Most snippets are gonna be the same language, + // so we think it's fine to do this sequentially for now + entry.insert(language_registry.language_for_id(lang_id).await.ok()); + } + } + + this.update_in(cx, |this, window, cx| { + let context_editor = cx.new(|cx| { + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(language::Capability::ReadOnly); + let excerpt_file = Arc::new(ExcerptMetadataFile { + title: PathBuf::from("Cursor Excerpt").into(), + worktree_id, + }); + + let excerpt_buffer = cx.new(|cx| { + let mut buffer = Buffer::local(context.excerpt_text.body, cx); + buffer.set_language(language, cx); + buffer.file_updated(excerpt_file, cx); + buffer + }); + + multibuffer.push_excerpts( + excerpt_buffer, + [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], + cx, + ); + + for snippet in context.snippets { + let path = this + .project + .read(cx) + .path_for_entry(snippet.declaration.project_entry_id(), cx); + + let snippet_file = Arc::new(ExcerptMetadataFile { + title: PathBuf::from(format!( + "{} (Score density: {})", + path.map(|p| p.path.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()), + snippet.score_density(SnippetStyle::Declaration) + )) + .into(), + worktree_id, + }); + + let excerpt_buffer = cx.new(|cx| { + let mut buffer = + Buffer::local(snippet.declaration.item_text().0, cx); + buffer.file_updated(snippet_file, cx); + if let Some(language) = + languages.get(&snippet.declaration.identifier().language_id) + { + buffer.set_language(language.clone(), cx); + } + buffer + }); + + multibuffer.push_excerpts( + excerpt_buffer, + [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], + cx, + ); + } + + multibuffer + }); + + Editor::new(EditorMode::full(), multibuffer, None, window, cx) + }); + + this.last_context = Some(ContextState { + context_editor, + retrieval_duration: context.retrieval_duration, + }); + cx.notify(); + }) + .ok(); + } + }); + } +} + +impl Focusable for EditPredictionTools { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Item for EditPredictionTools { + type Event = (); + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Edit Prediction Context Debug View".into() + } + + fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { + Some(Icon::new(IconName::ZedPredict)) + } +} + +impl EventEmitter<()> for EditPredictionTools {} + +impl Render for EditPredictionTools { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .size_full() + .bg(cx.theme().colors().editor_background) + .child( + h_flex() + .items_start() + .w_full() + .child( + v_flex() + .flex_1() + .p_4() + .gap_2() + .child(Headline::new("Excerpt Options").size(HeadlineSize::Small)) + .child( + h_flex() + .gap_2() + .child(self.max_bytes_input.clone()) + .child(self.min_bytes_input.clone()) + .child(self.cursor_context_ratio_input.clone()), + ), + ) + .child(ui::Divider::vertical()) + .when_some(self.last_context.as_ref(), |this, last_context| { + this.child( + v_flex() + .p_4() + .gap_2() + .min_w(px(160.)) + .child(Headline::new("Stats").size(HeadlineSize::Small)) + .child( + h_flex() + .gap_1() + .child( + Label::new("Time to retrieve") + .color(Color::Muted) + .size(LabelSize::Small), + ) + .child( + Label::new( + if last_context.retrieval_duration.as_micros() + > 1000 + { + format!( + "{} ms", + last_context.retrieval_duration.as_millis() + ) + } else { + format!( + "{} µs", + last_context.retrieval_duration.as_micros() + ) + }, + ) + .size(LabelSize::Small), + ), + ), + ) + }), + ) + .children(self.last_context.as_ref().map(|c| c.context_editor.clone())) + } +} + +// Using same approach as commit view + +struct ExcerptMetadataFile { + title: Arc, + worktree_id: WorktreeId, +} + +impl language::File for ExcerptMetadataFile { + fn as_local(&self) -> Option<&dyn language::LocalFile> { + None + } + + fn disk_state(&self) -> DiskState { + DiskState::New + } + + fn path(&self) -> &Arc { + &self.title + } + + fn full_path(&self, _: &App) -> PathBuf { + self.title.as_ref().into() + } + + fn file_name<'a>(&'a self, _: &'a App) -> &'a OsStr { + self.title.file_name().unwrap() + } + + fn worktree_id(&self, _: &App) -> WorktreeId { + self.worktree_id + } + + fn to_proto(&self, _: &App) -> language::proto::File { + unimplemented!() + } + + fn is_private(&self) -> bool { + false + } +} diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 9ec57b90b39f40b9530bef7ff02753ea8f37d2e5..92efe122aa5a13e0d4b1c196c019d9090ce3aa22 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -646,6 +646,24 @@ impl LanguageRegistry { async move { rx.await? } } + pub async fn language_for_id(self: &Arc, id: LanguageId) -> Result> { + let available_language = { + let state = self.state.read(); + + let Some(available_language) = state + .available_languages + .iter() + .find(|lang| lang.id == id) + .cloned() + else { + anyhow::bail!(LanguageNotFound); + }; + available_language + }; + + self.load_language(&available_language).await? + } + pub fn language_name_for_extension(self: &Arc, extension: &str) -> Option { self.state.try_read().and_then(|state| { state diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index 45c0deba4adfe71ea99d83c1bd081af1fc272671..79bddf6a182f1fefa495e635bd0dd348211fdc94 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/crates/ui_input/src/ui_input.rs @@ -7,7 +7,7 @@ use component::{example_group, single_example}; use editor::{Editor, EditorElement, EditorStyle}; -use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle}; +use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, Length, TextStyle}; use settings::Settings; use std::sync::Arc; use theme::ThemeSettings; @@ -42,6 +42,8 @@ pub struct SingleLineInput { start_icon: Option, /// Whether the text field is disabled. disabled: bool, + /// The minimum width of for the input + min_width: Length, } impl Focusable for SingleLineInput { @@ -67,6 +69,7 @@ impl SingleLineInput { editor, start_icon: None, disabled: false, + min_width: px(192.).into(), } } @@ -85,6 +88,11 @@ impl SingleLineInput { self } + pub fn label_min_width(mut self, width: impl Into) -> Self { + self.min_width = width.into(); + self + } + pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context) { self.disabled = disabled; self.editor @@ -167,7 +175,7 @@ impl Render for SingleLineInput { }) .child( h_flex() - .min_w_48() + .min_w(self.min_width) .min_h_8() .w_full() .px_2() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c85d3e70245ff1ee1ea1253492643b603b8ca70c..5b6cb3924610b89406a37230497fee8ffc511e34 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -52,6 +52,7 @@ debugger_tools.workspace = true debugger_ui.workspace = true diagnostics.workspace = true editor.workspace = true +edit_prediction_tools.workspace = true env_logger.workspace = true extension.workspace = true extension_host.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5cce6a6e2974d2fad9638f00811273ee202ab7b6..3dbbed0ce50ad84a1717f81afdf95c432b09259d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -549,6 +549,7 @@ pub fn main() { language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx); agent_settings::init(cx); acp_tools::init(cx); + edit_prediction_tools::init(cx); web_search::init(cx); web_search_providers::init(app_state.client.clone(), cx); snippet_provider::init(cx); From 439d31e2d412c14210685ce75cbae9e9e3bef361 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 18 Sep 2025 14:17:13 -0400 Subject: [PATCH 120/721] Add branch rename action to Git panel (#38273) Reopening #35136, cc @launay12u Release Notes: - git: added `git: rename branch` action to rename a branch (`git branch -m`) --------- Co-authored-by: Guillaume Launay Co-authored-by: Peter Tripp --- crates/fs/src/fake_git_repo.rs | 15 +++- crates/git/src/git.rs | 13 ++++ crates/git/src/repository.rs | 21 ++++- crates/git_ui/src/git_ui.rs | 133 +++++++++++++++++++++++++++++++- crates/project/src/git_store.rs | 50 ++++++++++++ crates/proto/proto/git.proto | 7 ++ crates/proto/proto/zed.proto | 4 +- crates/proto/src/proto.rs | 3 + 8 files changed, 238 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index 549c788dfac6acbb69fec8c715fb2a31b3674040..b608d0fec65a80057445fb3598102297f445ad4f 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -1,5 +1,5 @@ use crate::{FakeFs, FakeFsEntry, Fs}; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, Result, bail}; use collections::{HashMap, HashSet}; use futures::future::{self, BoxFuture, join_all}; use git::{ @@ -354,6 +354,19 @@ impl GitRepository for FakeGitRepository { }) } + fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>> { + self.with_state_async(true, move |state| { + if !state.branches.remove(&branch) { + bail!("no such branch: {branch}"); + } + state.branches.insert(new_name.clone()); + if state.current_branch_name == Some(branch) { + state.current_branch_name = Some(new_name); + } + Ok(()) + }) + } + fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<'_, Result> { self.with_state_async(false, move |state| { state diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index 73d32ac9e468b57e13fc9bf714bc96d55549167c..2028a0f374578d0c0f35bdc8c80ec09462ab0875 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -11,6 +11,7 @@ pub use crate::remote::*; use anyhow::{Context as _, Result}; pub use git2 as libgit; use gpui::{Action, actions}; +pub use repository::RemoteCommandOutput; pub use repository::WORK_DIRECTORY_REPO_PATH; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -101,6 +102,18 @@ actions!( ] ); +/// Renames a git branch. +#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = git)] +#[serde(deny_unknown_fields)] +pub struct RenameBranch { + /// The branch to rename. + /// + /// Default: the current branch. + #[serde(default)] + pub branch: Option, +} + /// Restores a file to its last committed state, discarding local changes. #[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] #[action(namespace = git, deprecated_aliases = ["editor::RevertFile"])] diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 10aaca38bbb3f7326e9bae27d4e6b1e9c20bb59a..29e2dab240e83da8d4343a370970ec0cc2256601 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -346,6 +346,7 @@ pub trait GitRepository: Send + Sync { fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>; fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>>; + fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>; fn reset( &self, @@ -1095,11 +1096,11 @@ 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 { - anyhow::bail!("Branch not found"); + anyhow::bail!("Branch '{}' not found", name); }; Ok(branch @@ -1115,7 +1116,6 @@ impl GitRepository for RealGitRepository { GitBinary::new(git_binary_path, working_directory?, executor) .run(&["checkout", &branch]) .await?; - anyhow::Ok(()) }) .boxed() @@ -1133,6 +1133,21 @@ impl GitRepository for RealGitRepository { .boxed() } + fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>> { + let git_binary_path = self.git_binary_path.clone(); + let working_directory = self.working_directory(); + let executor = self.executor.clone(); + + self.executor + .spawn(async move { + GitBinary::new(git_binary_path, working_directory?, executor) + .run(&["branch", "-m", &branch, &new_name]) + .await?; + anyhow::Ok(()) + }) + .boxed() + } + fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index 000b6639b440914f117e30cc3272bf4cc38d8be6..cede717d53b257be2570c4b0c067fb46341c0fc5 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -4,20 +4,28 @@ use ::settings::Settings; use command_palette_hooks::CommandPaletteFilter; use commit_modal::CommitModal; use editor::{Editor, actions::DiffClipboardWithSelectionData}; +use ui::{ + Headline, HeadlineSize, Icon, IconName, IconSize, IntoElement, ParentElement, Render, Styled, + StyledExt, div, h_flex, rems, v_flex, +}; + mod blame_ui; + use git::{ repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus}, status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode}, }; use git_panel_settings::GitPanelSettings; use gpui::{ - Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Window, - actions, + Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, SharedString, + Window, actions, }; +use menu::{Cancel, Confirm}; use onboarding::GitOnboardingModal; +use project::git_store::Repository; use project_diff::ProjectDiff; use ui::prelude::*; -use workspace::{ModalView, Workspace}; +use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr}; use zed_actions; use crate::{git_panel::GitPanel, text_diff_view::TextDiffView}; @@ -202,6 +210,9 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &git::OpenModifiedFiles, window, cx| { open_modified_files(workspace, window, cx); }); + workspace.register_action(|workspace, _: &git::RenameBranch, window, cx| { + rename_current_branch(workspace, window, cx); + }); workspace.register_action( |workspace, action: &DiffClipboardWithSelectionData, window, cx| { if let Some(task) = TextDiffView::open(action, workspace, window, cx) { @@ -245,6 +256,122 @@ pub fn git_status_icon(status: FileStatus) -> impl IntoElement { GitStatusIcon::new(status) } +struct RenameBranchModal { + current_branch: SharedString, + editor: Entity, + repo: Entity, +} + +impl RenameBranchModal { + fn new( + current_branch: String, + repo: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let editor = cx.new(|cx| { + let mut editor = Editor::single_line(window, cx); + editor.set_text(current_branch.clone(), window, cx); + editor + }); + Self { + current_branch: current_branch.into(), + editor, + repo, + } + } + + fn cancel(&mut self, _: &Cancel, _window: &mut Window, cx: &mut Context) { + cx.emit(DismissEvent); + } + + fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { + let new_name = self.editor.read(cx).text(cx); + if new_name.is_empty() || new_name == self.current_branch.as_ref() { + cx.emit(DismissEvent); + return; + } + + let repo = self.repo.clone(); + let current_branch = self.current_branch.to_string(); + cx.spawn(async move |_, cx| { + match repo + .update(cx, |repo, _| { + repo.rename_branch(current_branch, new_name.clone()) + })? + .await + { + Ok(Ok(_)) => Ok(()), + Ok(Err(error)) => Err(error), + Err(_) => Err(anyhow::anyhow!("Operation was canceled")), + } + }) + .detach_and_prompt_err("Failed to rename branch", window, cx, |_, _, _| None); + cx.emit(DismissEvent); + } +} + +impl EventEmitter for RenameBranchModal {} +impl ModalView for RenameBranchModal {} +impl Focusable for RenameBranchModal { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.editor.focus_handle(cx) + } +} + +impl Render for RenameBranchModal { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .key_context("RenameBranchModal") + .on_action(cx.listener(Self::cancel)) + .on_action(cx.listener(Self::confirm)) + .elevation_2(cx) + .w(rems(34.)) + .child( + h_flex() + .px_3() + .pt_2() + .pb_1() + .w_full() + .gap_1p5() + .child(Icon::new(IconName::GitBranch).size(IconSize::XSmall)) + .child( + Headline::new(format!("Rename Branch ({})", self.current_branch)) + .size(HeadlineSize::XSmall), + ), + ) + .child(div().px_3().pb_3().w_full().child(self.editor.clone())) + } +} + +fn rename_current_branch( + workspace: &mut Workspace, + window: &mut Window, + cx: &mut Context, +) { + let Some(panel) = workspace.panel::(cx) else { + return; + }; + let current_branch: Option = panel.update(cx, |panel, cx| { + let repo = panel.active_repository.as_ref()?; + let repo = repo.read(cx); + repo.branch.as_ref().map(|branch| branch.name().to_string()) + }); + + let Some(current_branch_name) = current_branch else { + return; + }; + + let repo = panel.read(cx).active_repository.clone(); + let Some(repo) = repo else { + return; + }; + + workspace.toggle_modal(window, cx, |window, cx| { + RenameBranchModal::new(current_branch_name, repo, window, cx) + }); +} + fn render_remote_button( id: impl Into, branch: &Branch, diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 4b9ee462529e980c782c555157e0f1ff34029fb7..a3d777ac774216967b2a5ffab03c72cf51dd9e7d 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -398,6 +398,7 @@ impl GitStore { client.add_entity_request_handler(Self::handle_get_default_branch); client.add_entity_request_handler(Self::handle_change_branch); client.add_entity_request_handler(Self::handle_create_branch); + client.add_entity_request_handler(Self::handle_rename_branch); client.add_entity_request_handler(Self::handle_git_init); client.add_entity_request_handler(Self::handle_push); client.add_entity_request_handler(Self::handle_pull); @@ -1944,6 +1945,25 @@ impl GitStore { Ok(proto::Ack {}) } + async fn handle_rename_branch( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + let repository_id = RepositoryId::from_proto(envelope.payload.repository_id); + let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?; + let branch = envelope.payload.branch; + let new_name = envelope.payload.new_name; + + repository_handle + .update(&mut cx, |repository_handle, _| { + repository_handle.rename_branch(branch, new_name) + })? + .await??; + + Ok(proto::Ack {}) + } + async fn handle_show( this: Entity, envelope: TypedEnvelope, @@ -4331,6 +4351,36 @@ impl Repository { ) } + pub fn rename_branch( + &mut self, + branch: String, + new_name: String, + ) -> oneshot::Receiver> { + let id = self.id; + self.send_job( + Some(format!("git branch -m {branch} {new_name}").into()), + move |repo, _cx| async move { + match repo { + RepositoryState::Local { backend, .. } => { + backend.rename_branch(branch, new_name).await + } + RepositoryState::Remote { project_id, client } => { + client + .request(proto::GitRenameBranch { + project_id: project_id.0, + repository_id: id.to_proto(), + branch, + new_name, + }) + .await?; + + Ok(()) + } + } + }, + ) + } + pub fn check_for_pushed_commits(&mut self) -> oneshot::Receiver>> { let id = self.id; self.send_job(None, move |repo, _cx| async move { diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index 3f17f0d0c3483ade36b73e26c7207f6cf667bb63..7004b0c9a0b4aff54434fac6b1f6ecc9be773ed4 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -183,6 +183,13 @@ message GitChangeBranch { string branch_name = 4; } +message GitRenameBranch { + uint64 project_id = 1; + uint64 repository_id = 2; + string branch = 3; + string new_name = 4; +} + message GitDiff { uint64 project_id = 1; reserved 2; diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index b20979081187b3dc7350b08b5c07ae700d86e02e..d9cc166c9b77fdd6cb4c876c4b118598b50895b2 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -414,7 +414,9 @@ message Envelope { NewExternalAgentVersionAvailable new_external_agent_version_available = 377; StashDrop stash_drop = 378; - StashApply stash_apply = 379; // current max + StashApply stash_apply = 379; + + GitRenameBranch git_rename_branch = 380; // current max } reserved 87 to 88; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 2985fde4d3ff4357628534f0ca3a5daf5476f813..5359ee983d9ceb01cd11e14c4d6dd491e097ea11 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -300,6 +300,7 @@ messages!( (AskPassResponse, Background), (GitCreateBranch, Background), (GitChangeBranch, Background), + (GitRenameBranch, Background), (CheckForPushedCommits, Background), (CheckForPushedCommitsResponse, Background), (GitDiff, Background), @@ -483,6 +484,7 @@ request_messages!( (AskPassRequest, AskPassResponse), (GitCreateBranch, Ack), (GitChangeBranch, Ack), + (GitRenameBranch, Ack), (CheckForPushedCommits, CheckForPushedCommitsResponse), (GitDiff, GitDiffResponse), (GitInit, Ack), @@ -637,6 +639,7 @@ entity_messages!( Pull, AskPassRequest, GitChangeBranch, + GitRenameBranch, GitCreateBranch, CheckForPushedCommits, GitDiff, From a6a2465954822be296f1b45f7732eed059dddc1f Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 18 Sep 2025 15:28:41 -0300 Subject: [PATCH 121/721] edit prediction: Fix sub overflow in identifiers_in_range (#38438) Release Notes: - N/A Co-authored-by: Bennet --- crates/edit_prediction_context/src/excerpt.rs | 8 +-- .../edit_prediction_context/src/reference.rs | 71 +++++++++++++++++-- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index 3fde142093efd095129f51a83f836953a76d20cb..d27e75bd129147986d2359585d5e048704d8828f 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -114,13 +114,13 @@ impl EditPredictionExcerpt { options, }; - if let Some(excerpt_ranges) = excerpt_selector.select_tree_sitter_nodes() { - if excerpt_ranges.size >= options.min_bytes { - return Some(excerpt_ranges); + if let Some(excerpt) = excerpt_selector.select_tree_sitter_nodes() { + if excerpt.size >= options.min_bytes { + return Some(excerpt); } log::debug!( "tree-sitter excerpt was {} bytes, smaller than min of {}, falling back on line-based selection", - excerpt_ranges.size, + excerpt.size, options.min_bytes ); } else { diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs index 975f15c81f44dad9ee1d3105f6a5863a4685b164..abb7dd75dd569f7a14bea807bdc64441d0e64871 100644 --- a/crates/edit_prediction_context/src/reference.rs +++ b/crates/edit_prediction_context/src/reference.rs @@ -1,6 +1,7 @@ use language::BufferSnapshot; use std::collections::HashMap; use std::ops::Range; +use util::RangeExt; use crate::{ declaration::Identifier, @@ -78,8 +79,8 @@ pub fn identifiers_in_range( .highlights_config .as_ref(); - for capture in mat.captures { - if let Some(config) = config { + if let Some(config) = config { + for capture in mat.captures { if config.identifier_capture_indices.contains(&capture.index) { let node_range = capture.node.byte_range(); @@ -88,9 +89,13 @@ pub fn identifiers_in_range( continue; } + if !range.contains_inclusive(&node_range) { + continue; + } + let identifier_text = - // TODO we changed this to saturating_sub for now, but we should fix the actually issue - &range_text[node_range.start.saturating_sub(range.start)..node_range.end.saturating_sub(range.start)]; + &range_text[node_range.start - range.start..node_range.end - range.start]; + references.push(Reference { identifier: Identifier { name: identifier_text.into(), @@ -108,3 +113,61 @@ pub fn identifiers_in_range( } references } + +#[cfg(test)] +mod test { + use gpui::{TestAppContext, prelude::*}; + use indoc::indoc; + use language::{BufferSnapshot, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust}; + + use crate::reference::{ReferenceRegion, identifiers_in_range}; + + #[gpui::test] + fn test_identifier_node_truncated(cx: &mut TestAppContext) { + let code = indoc! { r#" + fn main() { + add(1, 2); + } + + fn add(a: i32, b: i32) -> i32 { + a + b + } + "# }; + let buffer = create_buffer(code, cx); + + let range = 0..35; + let references = identifiers_in_range( + range.clone(), + &code[range], + ReferenceRegion::Breadcrumb, + &buffer, + ); + assert_eq!(references.len(), 2); + assert_eq!(references[0].identifier.name.as_ref(), "main"); + assert_eq!(references[1].identifier.name.as_ref(), "add"); + } + + fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot { + let buffer = + cx.new(|cx| language::Buffer::local(text, cx).with_language(rust_lang().into(), cx)); + buffer.read_with(cx, |buffer, _| buffer.snapshot()) + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) + .with_highlights_query(include_str!("../../languages/src/rust/highlights.scm")) + .unwrap() + .with_outline_query(include_str!("../../languages/src/rust/outline.scm")) + .unwrap() + } +} From c58763a5267228fd71e70dd9fed3b716f6d3c009 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Thu, 18 Sep 2025 20:34:13 +0200 Subject: [PATCH 122/721] git_ui: Reduce spacing between action icon and label (#38445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Opinionated change: A bit uneven spacing between Git action icon and label, in comparison to the border on the right in the segmented action button was triggering my UI OCD a bit. 😅 # How Remove the right margin from icon and icon + counter children of the segmented Git action button in Git Panel. The default spacing from the button layout seems to be enough to separate them from the left-side label. # Release Notes - Reduced spacing between Git action icon and label in Git Panel # Test plan I have tested few cases, and made sure that the spacing is still present, but icon (or icon and counter) does not feel too separated/detached from the label. ### Before Screenshot 2025-09-18 at 20 11 16 Screenshot 2025-09-18 at 20 13 19 ### After Screenshot 2025-09-18 at 19 53 14 Screenshot 2025-09-18 at 19 53 34 Screenshot 2025-09-18 at 19 56 23 --- crates/git_ui/src/git_ui.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index cede717d53b257be2570c4b0c067fb46341c0fc5..da2e2ca032aa005ad619eabf094ae6981975b050 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -645,7 +645,6 @@ mod remote_button { this.child( h_flex() .ml_neg_0p5() - .mr_1() .when(behind_count > 0, |this| { this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall)) .child(count(behind_count)) @@ -660,7 +659,6 @@ mod remote_button { this.child( h_flex() .ml_neg_0p5() - .mr_1() .child(Icon::new(left_icon).size(IconSize::XSmall)), ) }) From 5fccde9b1bf539dacbf892279b84f8b008438868 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 18 Sep 2025 14:45:02 -0400 Subject: [PATCH 123/721] python: Install basedpyright if the basedpyright-langserver binary is missing (#38426) Potential fix for #38377 Release Notes: - N/A --------- Co-authored-by: Peter Tripp --- crates/language/src/language.rs | 5 +--- crates/languages/src/python.rs | 48 ++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 291b5d3957cc7274bdd07b5260890c23b7a253d1..2af5657ea776ddd85bf9495d3c1f32c2d0c69ac2 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -55,7 +55,7 @@ use std::{ str, sync::{ Arc, LazyLock, - atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst}, + atomic::{AtomicUsize, Ordering::SeqCst}, }, }; use syntax_map::{QueryCursorHandle, SyntaxSnapshot}; @@ -168,7 +168,6 @@ pub struct CachedLspAdapter { pub disk_based_diagnostics_progress_token: Option, language_ids: HashMap, pub adapter: Arc, - pub reinstall_attempt_count: AtomicU64, cached_binary: ServerBinaryCache, } @@ -185,7 +184,6 @@ impl Debug for CachedLspAdapter { &self.disk_based_diagnostics_progress_token, ) .field("language_ids", &self.language_ids) - .field("reinstall_attempt_count", &self.reinstall_attempt_count) .finish_non_exhaustive() } } @@ -204,7 +202,6 @@ impl CachedLspAdapter { language_ids, adapter, cached_binary: Default::default(), - reinstall_attempt_count: AtomicU64::new(0), }) } diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 4905cd22cd64488a0f0da9ede6834602fc5834bc..4451054cfd21be5d07c9df64b55a37ffc190670f 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1559,7 +1559,7 @@ impl LspInstaller for PyLspAdapter { util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server") - .arg("-U") + .arg("--upgrade") .output() .await? .status @@ -1570,7 +1570,7 @@ impl LspInstaller for PyLspAdapter { util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server[all]") - .arg("-U") + .arg("--upgrade") .output() .await? .status @@ -1581,7 +1581,7 @@ impl LspInstaller for PyLspAdapter { util::command::new_smol_command(pip_path) .arg("install") .arg("pylsp-mypy") - .arg("-U") + .arg("--upgrade") .output() .await? .status @@ -1589,6 +1589,10 @@ impl LspInstaller for PyLspAdapter { "pylsp-mypy installation failed" ); let pylsp = venv.join(BINARY_DIR).join("pylsp"); + ensure!( + delegate.which(pylsp.as_os_str()).await.is_some(), + "pylsp installation was incomplete" + ); Ok(LanguageServerBinary { path: pylsp, env: None, @@ -1603,6 +1607,7 @@ impl LspInstaller for PyLspAdapter { ) -> Option { let venv = self.base_venv(delegate).await.ok()?; let pylsp = venv.join(BINARY_DIR).join("pylsp"); + delegate.which(pylsp.as_os_str()).await?; Some(LanguageServerBinary { path: pylsp, env: None, @@ -1641,9 +1646,11 @@ impl BasedPyrightLspAdapter { .arg("venv") .arg("basedpyright-venv") .current_dir(work_dir) - .spawn()? + .spawn() + .context("spawning child")? .output() - .await?; + .await + .context("getting child output")?; } Ok(path.into()) @@ -1885,11 +1892,14 @@ impl LspInstaller for BasedPyrightLspAdapter { let path = Path::new(toolchain?.path.as_ref()) .parent()? .join(Self::BINARY_NAME); - path.exists().then(|| LanguageServerBinary { - path, - arguments: vec!["--stdio".into()], - env: None, - }) + delegate + .which(path.as_os_str()) + .await + .map(|_| LanguageServerBinary { + path, + arguments: vec!["--stdio".into()], + env: None, + }) } } @@ -1905,16 +1915,21 @@ impl LspInstaller for BasedPyrightLspAdapter { util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("basedpyright") - .arg("-U") + .arg("--upgrade") .output() - .await? + .await + .context("getting pip install output")? .status .success(), "basedpyright installation failed" ); - let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + ensure!( + delegate.which(path.as_os_str()).await.is_some(), + "basedpyright installation was incomplete" + ); Ok(LanguageServerBinary { - path: pylsp, + path, env: None, arguments: vec!["--stdio".into()], }) @@ -1926,9 +1941,10 @@ impl LspInstaller for BasedPyrightLspAdapter { delegate: &dyn LspAdapterDelegate, ) -> Option { let venv = self.base_venv(delegate).await.ok()?; - let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + delegate.which(path.as_os_str()).await?; Some(LanguageServerBinary { - path: pylsp, + path, env: None, arguments: vec!["--stdio".into()], }) From 6b8ed5bf28e1cacd0f72319adbc589065849ac93 Mon Sep 17 00:00:00 2001 From: Jaeyong Sung Date: Fri, 19 Sep 2025 03:46:26 +0900 Subject: [PATCH 124/721] docs: Fix typo in Python configuration example (#38434) Release Notes: - N/A --------- Co-authored-by: Marshall Bowers --- docs/src/languages/python.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/src/languages/python.md b/docs/src/languages/python.md index f4960bc8e8d3643bab6310020ab7d7834a6a85b3..faca1185768d09b5dcb6e485c146b0e1973cf870 100644 --- a/docs/src/languages/python.md +++ b/docs/src/languages/python.md @@ -81,11 +81,13 @@ These are disabled by default, but can be enabled in your settings. For example: { "languages": { "Python": { - "language_servers": { + "language_servers": [ // Disable basedpyright and enable Ty, and otherwise // use the default configuration. - "ty", "!basedpyright", ".." - } + "ty", + "!basedpyright", + "..." + ] } } } From fc0eb882f74e19b578d8943087abbfe413e3a2b6 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 18 Sep 2025 15:05:55 -0500 Subject: [PATCH 125/721] debugger_ui: Update new process modal to include more context about its source (#36650) Closes #36280 Release Notes: - Added additional context to debug task selection Adding additional context when selecting a debug task to help with projects that have multiple config files with similar names for tasks. I think there is room for improvement, especially adding context for a LanguageTask type. I started but it looked like it would need to add a path value to that and wanted to make sure this was a good idea before working on that. Also any thoughts on the wording if you do like this format? --- image --------- Co-authored-by: Anthony Co-authored-by: Anthony --- crates/debugger_ui/src/debugger_panel.rs | 4 + crates/debugger_ui/src/new_process_modal.rs | 188 +++++++++++++----- .../src/tests/new_process_modal.rs | 78 +++++++- crates/project/src/task_inventory.rs | 1 + crates/tasks_ui/src/modal.rs | 2 +- 5 files changed, 217 insertions(+), 56 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 12a31a7360bb6e7fb1ff6fdcaf18f73d679693a3..f1a1b4dc738f82f729832c60648392af8b9921ed 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -134,6 +134,10 @@ impl DebugPanel { .map(|session| session.read(cx).running_state().clone()) } + pub fn project(&self) -> &Entity { + &self.project + } + pub fn load( workspace: WeakEntity, cx: &mut AsyncWindowContext, diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index eeed36ac1df18d5a0d1b33d6c3567c7df6a4b9c0..f1fa4738e30e5ed24e7815b61571b03e5a16252e 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, bail}; use collections::{FxHashMap, HashMap, HashSet}; -use language::LanguageRegistry; +use language::{LanguageName, LanguageRegistry}; use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -22,7 +22,7 @@ use itertools::Itertools as _; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; use project::{DebugScenarioContext, Project, TaskContexts, TaskSourceKind, task_store::TaskStore}; use settings::Settings; -use task::{DebugScenario, RevealTarget, ZedDebugConfig}; +use task::{DebugScenario, RevealTarget, VariableName, ZedDebugConfig}; use theme::ThemeSettings; use ui::{ ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context, @@ -978,6 +978,7 @@ pub(super) struct DebugDelegate { task_store: Entity, candidates: Vec<( Option, + Option, DebugScenario, Option, )>, @@ -1005,28 +1006,89 @@ impl DebugDelegate { } } - fn get_scenario_kind( + fn get_task_subtitle( + &self, + task_kind: &Option, + context: &Option, + cx: &mut App, + ) -> Option { + match task_kind { + Some(TaskSourceKind::Worktree { + id: worktree_id, + directory_in_worktree, + .. + }) => self + .debug_panel + .update(cx, |debug_panel, cx| { + let project = debug_panel.project().read(cx); + let worktrees: Vec<_> = project.visible_worktrees(cx).collect(); + + let mut path = if worktrees.len() > 1 + && let Some(worktree) = project.worktree_for_id(*worktree_id, cx) + { + let worktree_path = worktree.read(cx).abs_path(); + let full_path = worktree_path.join(directory_in_worktree); + full_path + } else { + directory_in_worktree.clone() + }; + + match path + .components() + .next_back() + .and_then(|component| component.as_os_str().to_str()) + { + Some(".zed") => { + path.push("debug.json"); + } + Some(".vscode") => { + path.push("launch.json"); + } + _ => {} + } + Some(path.display().to_string()) + }) + .unwrap_or_else(|_| Some(directory_in_worktree.display().to_string())), + Some(TaskSourceKind::AbsPath { abs_path, .. }) => { + Some(abs_path.to_string_lossy().into_owned()) + } + Some(TaskSourceKind::Lsp { language_name, .. }) => { + Some(format!("LSP: {language_name}")) + } + Some(TaskSourceKind::Language { .. }) => None, + _ => context.clone().and_then(|ctx| { + ctx.task_context + .task_variables + .get(&VariableName::RelativeFile) + .map(|f| format!("in {f}")) + .or_else(|| { + ctx.task_context + .task_variables + .get(&VariableName::Dirname) + .map(|d| format!("in {d}/")) + }) + }), + } + } + + fn get_scenario_language( languages: &Arc, dap_registry: &DapRegistry, scenario: DebugScenario, - ) -> (Option, DebugScenario) { + ) -> (Option, DebugScenario) { let language_names = languages.language_names(); - let language = dap_registry - .adapter_language(&scenario.adapter) - .map(|language| TaskSourceKind::Language { name: language.0 }); + let language_name = dap_registry.adapter_language(&scenario.adapter); - let language = language.or_else(|| { + let language_name = language_name.or_else(|| { scenario.label.split_whitespace().find_map(|word| { language_names .iter() .find(|name| name.as_ref().eq_ignore_ascii_case(word)) - .map(|name| TaskSourceKind::Language { - name: name.to_owned().into(), - }) + .cloned() }) }); - (language, scenario) + (language_name, scenario) } pub fn tasks_loaded( @@ -1080,9 +1142,9 @@ impl DebugDelegate { this.delegate.candidates = recent .into_iter() .map(|(scenario, context)| { - let (kind, scenario) = - Self::get_scenario_kind(&languages, dap_registry, scenario); - (kind, scenario, Some(context)) + let (language_name, scenario) = + Self::get_scenario_language(&languages, dap_registry, scenario); + (None, language_name, scenario, Some(context)) }) .chain( scenarios @@ -1097,9 +1159,9 @@ impl DebugDelegate { }) .filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter)) .map(|(kind, scenario)| { - let (language, scenario) = - Self::get_scenario_kind(&languages, dap_registry, scenario); - (language.or(Some(kind)), scenario, None) + let (language_name, scenario) = + Self::get_scenario_language(&languages, dap_registry, scenario); + (Some(kind), language_name, scenario, None) }), ) .collect(); @@ -1145,7 +1207,7 @@ impl PickerDelegate for DebugDelegate { let candidates: Vec<_> = candidates .into_iter() .enumerate() - .map(|(index, (_, candidate, _))| { + .map(|(index, (_, _, candidate, _))| { StringMatchCandidate::new(index, candidate.label.as_ref()) }) .collect(); @@ -1314,7 +1376,7 @@ impl PickerDelegate for DebugDelegate { .get(self.selected_index()) .and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned()); - let Some((kind, debug_scenario, context)) = debug_scenario else { + let Some((kind, _, debug_scenario, context)) = debug_scenario else { return; }; @@ -1447,6 +1509,7 @@ impl PickerDelegate for DebugDelegate { cx: &mut Context>, ) -> Option { let hit = &self.matches.get(ix)?; + let (task_kind, language_name, _scenario, context) = &self.candidates[hit.candidate_id]; let highlighted_location = HighlightedMatch { text: hit.string.clone(), @@ -1454,33 +1517,40 @@ impl PickerDelegate for DebugDelegate { char_count: hit.string.chars().count(), color: Color::Default, }; - let task_kind = &self.candidates[hit.candidate_id].0; - - let icon = match task_kind { - Some(TaskSourceKind::UserInput) => Some(Icon::new(IconName::Terminal)), - Some(TaskSourceKind::AbsPath { .. }) => Some(Icon::new(IconName::Settings)), - Some(TaskSourceKind::Worktree { .. }) => Some(Icon::new(IconName::FileTree)), - Some(TaskSourceKind::Lsp { - language_name: name, - .. - }) - | Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx) - .get_icon_for_type(&name.to_lowercase(), cx) - .map(Icon::from_path), - None => Some(Icon::new(IconName::HistoryRerun)), - } - .map(|icon| icon.color(Color::Muted).size(IconSize::Small)); - let indicator = if matches!(task_kind, Some(TaskSourceKind::Lsp { .. })) { - Some(Indicator::icon( - Icon::new(IconName::BoltFilled) - .color(Color::Muted) - .size(IconSize::Small), - )) - } else { - None + + let subtitle = self.get_task_subtitle(task_kind, context, cx); + + let language_icon = language_name.as_ref().and_then(|lang| { + file_icons::FileIcons::get(cx) + .get_icon_for_type(&lang.0.to_lowercase(), cx) + .map(Icon::from_path) + }); + + let (icon, indicator) = match task_kind { + Some(TaskSourceKind::UserInput) => (Some(Icon::new(IconName::Terminal)), None), + Some(TaskSourceKind::AbsPath { .. }) => (Some(Icon::new(IconName::Settings)), None), + Some(TaskSourceKind::Worktree { .. }) => (Some(Icon::new(IconName::FileTree)), None), + Some(TaskSourceKind::Lsp { language_name, .. }) => ( + file_icons::FileIcons::get(cx) + .get_icon_for_type(&language_name.to_lowercase(), cx) + .map(Icon::from_path), + Some(Indicator::icon( + Icon::new(IconName::BoltFilled) + .color(Color::Muted) + .size(IconSize::Small), + )), + ), + Some(TaskSourceKind::Language { name }) => ( + file_icons::FileIcons::get(cx) + .get_icon_for_type(&name.to_lowercase(), cx) + .map(Icon::from_path), + None, + ), + None => (Some(Icon::new(IconName::HistoryRerun)), None), }; - let icon = icon.map(|icon| { - IconWithIndicator::new(icon, indicator) + + let icon = language_icon.or(icon).map(|icon| { + IconWithIndicator::new(icon.color(Color::Muted).size(IconSize::Small), indicator) .indicator_border_color(Some(cx.theme().colors().border_transparent)) }); @@ -1490,7 +1560,18 @@ impl PickerDelegate for DebugDelegate { .start_slot::(icon) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) - .child(highlighted_location.render(window, cx)), + .child( + v_flex() + .items_start() + .child(highlighted_location.render(window, cx)) + .when_some(subtitle, |this, subtitle_text| { + this.child( + Label::new(subtitle_text) + .size(LabelSize::Small) + .color(Color::Muted), + ) + }), + ), ) } } @@ -1539,4 +1620,17 @@ impl NewProcessModal { } }) } + + pub(crate) fn debug_picker_candidate_subtitles(&self, cx: &mut App) -> Vec { + self.debug_picker.update(cx, |picker, cx| { + picker + .delegate + .candidates + .iter() + .filter_map(|(task_kind, _, _, context)| { + picker.delegate.get_task_subtitle(task_kind, context, cx) + }) + .collect() + }) + } } diff --git a/crates/debugger_ui/src/tests/new_process_modal.rs b/crates/debugger_ui/src/tests/new_process_modal.rs index bfc445cf67329b7190f8e5b8d353415fb53fcd74..80e27ee6bdeb1d1a2627ad7aa46bf68c38464510 100644 --- a/crates/debugger_ui/src/tests/new_process_modal.rs +++ b/crates/debugger_ui/src/tests/new_process_modal.rs @@ -10,6 +10,7 @@ use text::Point; use util::path; use crate::NewProcessMode; +use crate::new_process_modal::NewProcessModal; use crate::tests::{init_test, init_test_workspace}; #[gpui::test] @@ -178,13 +179,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut workspace .update(cx, |workspace, window, cx| { - crate::new_process_modal::NewProcessModal::show( - workspace, - window, - NewProcessMode::Debug, - None, - cx, - ); + NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); }) .unwrap(); @@ -192,7 +187,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut let modal = workspace .update(cx, |workspace, _, cx| { - workspace.active_modal::(cx) + workspace.active_modal::(cx) }) .unwrap() .expect("Modal should be active"); @@ -281,6 +276,73 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut pretty_assertions::assert_eq!(expected_content, debug_json_content); } +#[gpui::test] +async fn test_debug_modal_subtitles_with_multiple_worktrees( + executor: BackgroundExecutor, + cx: &mut TestAppContext, +) { + init_test(cx); + + let fs = FakeFs::new(executor.clone()); + + fs.insert_tree( + path!("/workspace1"), + json!({ + ".zed": { + "debug.json": r#"[ + { + "adapter": "fake-adapter", + "label": "Debug App 1", + "request": "launch", + "program": "./app1", + "cwd": "." + }, + { + "adapter": "fake-adapter", + "label": "Debug Tests 1", + "request": "launch", + "program": "./test1", + "cwd": "." + } + ]"# + }, + "main.rs": "fn main() {}" + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/workspace1").as_ref()], cx).await; + + let workspace = init_test_workspace(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + + workspace + .update(cx, |workspace, window, cx| { + NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); + }) + .unwrap(); + + cx.run_until_parked(); + + let modal = workspace + .update(cx, |workspace, _, cx| { + workspace.active_modal::(cx) + }) + .unwrap() + .expect("Modal should be active"); + + cx.executor().run_until_parked(); + + let subtitles = modal.update_in(cx, |modal, _, cx| { + modal.debug_picker_candidate_subtitles(cx) + }); + + assert_eq!( + subtitles.as_slice(), + [path!(".zed/debug.json"), path!(".zed/debug.json")] + ); +} + #[gpui::test] async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 818a5c58cff7a57705a6498581e62f010ff6dcf9..4d85c8f2622c7e287c41f28089845b99d7b2ec4d 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -388,6 +388,7 @@ impl Inventory { .into_iter() .flat_map(|worktree| self.worktree_templates_from_settings(worktree)) .collect::>(); + let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index e14b42af2b673ae161b5434e0c4f9b3f096e316a..3522e9522a6d32d729e7f0dca6731b2052f63f94 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -493,7 +493,7 @@ impl PickerDelegate for TasksModalDelegate { language_name: name, .. } - | TaskSourceKind::Language { name } => file_icons::FileIcons::get(cx) + | TaskSourceKind::Language { name, .. } => file_icons::FileIcons::get(cx) .get_icon_for_type(&name.to_lowercase(), cx) .map(Icon::from_path), } From e3e0522e32d5b3ddec85f18c3638fdb388f66145 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:15:25 -0400 Subject: [PATCH 126/721] debugger: Fix debug scenario picker showing history in reverse order (#38452) Closes #37859 Release Notes: - debugger: Fix sort order of pasted launched debug sessions in debugger launch modal --- crates/project/src/task_inventory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 4d85c8f2622c7e287c41f28089845b99d7b2ec4d..da6d87ac702b6e0e9b1ec459e810dc1ca37eeb7e 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -258,7 +258,7 @@ impl Inventory { ) { self.last_scheduled_scenarios .retain(|(s, _)| s.label != scenario.label); - self.last_scheduled_scenarios.push_back(( + self.last_scheduled_scenarios.push_front(( scenario, DebugScenarioContext { task_context, From 11212b80f95235ae37491b3a8d615a825ad756a2 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Thu, 18 Sep 2025 16:39:50 -0400 Subject: [PATCH 127/721] docs: Improve Elixir HEEX language server documentation (#38363) Closes: https://github.com/zed-industries/zed/issues/38009 Release Notes: - N/A --- docs/src/languages/elixir.md | 49 +++++++++++++----------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/docs/src/languages/elixir.md b/docs/src/languages/elixir.md index c7b7e2287a0d772871bee331035944a5e7bab8a1..b09653b7ff6972a0c9a92d496648302efa7db593 100644 --- a/docs/src/languages/elixir.md +++ b/docs/src/languages/elixir.md @@ -22,19 +22,14 @@ The Elixir extension offers language server support for `expert`, `elixir-ls`, ` To switch to `expert`, add the following to your `settings.json`: ```json -{ "languages": { "Elixir": { - "language_servers": [ - "expert", - "!elixir-ls", - "!next-ls", - "!lexical", - "..." - ] + "language_servers": ["expert", "!elixir-ls", "!next-ls", "!lexical", "..."] + }, + "HEEX": { + "language_servers": ["expert", "!elixir-ls", "!next-ls", "!lexical", "..."] } } -} ``` ### Next LS @@ -42,19 +37,14 @@ To switch to `expert`, add the following to your `settings.json`: To switch to `next-ls`, add the following to your `settings.json`: ```json -{ "languages": { "Elixir": { - "language_servers": [ - "next-ls", - "!expert", - "!elixir-ls", - "!lexical", - "..." - ] + "language_servers": ["next-ls", "!expert", "!elixir-ls", "!lexical", "..."] + }, + "HEEX": { + "language_servers": ["next-ls", "!expert", "!elixir-ls", "!lexical", "..."] } } -} ``` ### Lexical @@ -62,19 +52,14 @@ To switch to `next-ls`, add the following to your `settings.json`: To switch to `lexical`, add the following to your `settings.json`: ```json -{ "languages": { "Elixir": { - "language_servers": [ - "lexical", - "!expert", - "!elixir-ls", - "!next-ls", - "..." - ] + "language_servers": ["lexical", "!expert", "!elixir-ls", "!next-ls", "..."] + }, + "HEEX": { + "language_servers": ["lexical", "!expert", "!elixir-ls", "!next-ls", "..."] } } -} ``` ## Setting up `elixir-ls` @@ -121,13 +106,13 @@ You can pass additional elixir-ls workspace configuration options via lsp settin The following example disables dialyzer: ```json -"lsp": { - "elixir-ls": { - "settings": { - "dialyzerEnabled": false + "lsp": { + "elixir-ls": { + "settings": { + "dialyzerEnabled": false + } } } -} ``` See [ElixirLS configuration settings](https://github.com/elixir-lsp/elixir-ls#elixirls-configuration-settings) for more options. From 530225a06a4a4ddff95972ee52441901b5f6ad56 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Thu, 18 Sep 2025 16:40:06 -0400 Subject: [PATCH 128/721] python: Remove a redundant pip install call (#38449) I confirmed that the pip packages match for: ```sh pip install python-lsp-server && pip install 'python-lsp-server[all]' pip install 'python-lsp-server[all]' ``` Originally introduced here: - https://github.com/zed-industries/zed/pull/20358 Release Notes: - N/A --- crates/languages/src/python.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 4451054cfd21be5d07c9df64b55a37ffc190670f..91c1c675f8090d9d0161b6d3733d34ed386cfb50 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1555,17 +1555,6 @@ impl LspInstaller for PyLspAdapter { ) -> Result { let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; let pip_path = venv.join(BINARY_DIR).join("pip3"); - ensure!( - util::command::new_smol_command(pip_path.as_path()) - .arg("install") - .arg("python-lsp-server") - .arg("--upgrade") - .output() - .await? - .status - .success(), - "python-lsp-server installation failed" - ); ensure!( util::command::new_smol_command(pip_path.as_path()) .arg("install") From 82e1e5b7ac6a54b416c474c691c4a0c74cce9674 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Sep 2025 14:58:07 -0600 Subject: [PATCH 129/721] Fix panic in vim mode (#38437) Release Notes: - vim: Fixed a rare panic in search --- crates/vim/src/normal/search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 6da18164e42e42b3c1932b094fab5cecf048c155..1023375cacc21152b27e8bf49177dc1d92f8ca91 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -222,7 +222,7 @@ impl Vim { let mut count = self.search.count; let direction = self.search.direction; search_bar.has_active_match(); - let new_head = new_selections.last().unwrap().start; + let new_head = new_selections.last()?.start; let is_different_head = self .search .prior_selections From 5f4f0a873e02c3a95c27aa18d5e562d26e4ed9f9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Sep 2025 14:58:15 -0600 Subject: [PATCH 130/721] Fix wierd rust-analyzer error (#38431) Release Notes: - N/A --- crates/collab/src/tests/editor_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index cc84ddc800d7c4c4979dc824d25fa705405a0aa7..2169b7123263305e45f11403796f8d7c395c2cdf 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -1895,13 +1895,13 @@ async fn test_mutual_editor_inlay_hint_cache_update( let closure_edits_made = Arc::clone(&edits_made); fake_language_server .set_request_handler::(move |params, _| { - let task_edits_made = Arc::clone(&closure_edits_made); + let edits_made_2 = Arc::clone(&closure_edits_made); async move { assert_eq!( params.text_document.uri, lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(), ); - let edits_made = task_edits_made.load(atomic::Ordering::Acquire); + let edits_made = AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire); Ok(Some(vec![lsp::InlayHint { position: lsp::Position::new(0, edits_made as u32), label: lsp::InlayHintLabel::String(edits_made.to_string()), From b09764c54a92bfb567aa0ca6d00203cc641e5a3d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Sep 2025 15:13:49 -0600 Subject: [PATCH 131/721] settings: Use a derive macro for refine (#38451) When we refactored settings to not pass JSON blobs around, we ended up needing to write *a lot* of code that just merged things (like json merge used to do). Use a derive macro to prevent typos in this logic. Release Notes: - N/A --- Cargo.lock | 28 +- Cargo.toml | 3 +- crates/agent2/src/tools/grep_tool.rs | 16 +- .../agent2/src/tools/list_directory_tool.rs | 16 +- crates/agent2/src/tools/read_file_tool.rs | 16 +- crates/agent_settings/src/agent_settings.rs | 63 +- crates/agent_ui/src/agent_panel.rs | 2 +- crates/assistant_tools/src/grep_tool.rs | 16 +- .../src/list_directory_tool.rs | 16 +- crates/assistant_tools/src/read_file_tool.rs | 16 +- crates/audio/src/audio_settings.rs | 14 +- crates/auto_update/src/auto_update.rs | 16 +- crates/call/src/call_settings.rs | 10 +- crates/client/Cargo.toml | 1 - crates/client/src/client.rs | 42 +- crates/collab_ui/src/panel_settings.rs | 35 +- crates/collections/src/collections.rs | 1 + crates/dap/src/debugger_settings.rs | 24 +- crates/editor/src/editor_settings.rs | 161 +---- .../extension_host/src/extension_settings.rs | 18 +- .../file_finder/src/file_finder_settings.rs | 19 +- crates/git_hosting_providers/src/settings.rs | 17 +- crates/git_ui/src/git_panel_settings.rs | 22 +- crates/go_to_line/src/cursor_position.rs | 8 +- .../image_viewer/src/image_viewer_settings.rs | 12 +- crates/journal/Cargo.toml | 1 - crates/journal/src/journal.rs | 11 +- crates/language/Cargo.toml | 1 - crates/language/src/buffer_tests.rs | 8 +- crates/language/src/language_settings.rs | 486 +++----------- crates/language_models/src/settings.rs | 112 +--- crates/languages/Cargo.toml | 1 + crates/languages/src/json.rs | 20 +- crates/onboarding/src/ai_setup_page.rs | 2 +- .../src/outline_panel_settings.rs | 33 +- crates/project/src/agent_server_store.rs | 22 +- crates/project/src/project.rs | 11 +- crates/project/src/project_settings.rs | 133 +--- .../src/project_panel_settings.rs | 51 +- .../recent_projects/src/remote_connections.rs | 28 +- crates/recent_projects/src/remote_servers.rs | 4 +- crates/repl/src/jupyter_settings.rs | 11 +- crates/repl/src/repl_settings.rs | 12 +- crates/settings/Cargo.toml | 2 +- crates/settings/src/base_keymap_setting.rs | 36 +- crates/settings/src/merge_from.rs | 164 +++++ crates/settings/src/settings.rs | 4 +- crates/settings/src/settings_content.rs | 136 ++-- crates/settings/src/settings_content/agent.rs | 25 +- .../settings/src/settings_content/editor.rs | 73 ++- .../settings/src/settings_content/language.rs | 128 ++-- .../src/settings_content/language_model.rs | 75 +-- .../settings/src/settings_content/project.rs | 69 +- .../settings/src/settings_content/terminal.rs | 31 +- crates/settings/src/settings_content/theme.rs | 50 +- .../src/settings_content/workspace.rs | 47 +- crates/settings/src/settings_json.rs | 12 +- crates/settings/src/settings_store.rs | 265 ++++---- .../Cargo.toml | 6 +- .../LICENSE-GPL | 0 crates/settings_macros/src/settings_macros.rs | 132 ++++ .../src/settings_ui_macros.rs | 612 ------------------ crates/terminal/src/terminal_settings.rs | 73 +-- crates/theme/Cargo.toml | 1 - crates/theme/src/settings.rs | 172 +---- crates/title_bar/src/title_bar_settings.rs | 23 +- crates/util/src/schemars.rs | 5 +- crates/util/src/util.rs | 18 - crates/vim/src/vim.rs | 28 +- .../vim_mode_setting/src/vim_mode_setting.rs | 16 +- crates/workspace/src/item.rs | 30 +- crates/workspace/src/workspace_settings.rs | 75 +-- crates/worktree/src/worktree_settings.rs | 52 +- crates/zlog_settings/src/zlog_settings.rs | 8 +- 74 files changed, 1089 insertions(+), 2818 deletions(-) create mode 100644 crates/settings/src/merge_from.rs rename crates/{settings_ui_macros => settings_macros}/Cargo.toml (73%) rename crates/{settings_ui_macros => settings_macros}/LICENSE-GPL (100%) create mode 100644 crates/settings_macros/src/settings_macros.rs delete mode 100644 crates/settings_ui_macros/src/settings_ui_macros.rs diff --git a/Cargo.lock b/Cargo.lock index 28247c7f4c6bcab64a8acce951e072e80abf751b..5654d206701f5efd5d91cd33c9ab456701b1e667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3144,7 +3144,6 @@ dependencies = [ "release_channel", "rpc", "rustls-pki-types", - "schemars 1.0.1", "serde", "serde_json", "serde_urlencoded", @@ -9302,7 +9301,6 @@ dependencies = [ "serde", "settings", "shellexpand 2.1.2", - "util", "workspace", "workspace-hack", ] @@ -9523,7 +9521,6 @@ dependencies = [ "http_client", "imara-diff", "indoc", - "inventory", "itertools 0.14.0", "log", "lsp", @@ -15499,7 +15496,7 @@ dependencies = [ "serde_path_to_error", "serde_repr", "serde_with", - "settings_ui_macros", + "settings_macros", "smallvec", "tree-sitter", "tree-sitter-json", @@ -15509,6 +15506,16 @@ dependencies = [ "zlog", ] +[[package]] +name = "settings_macros" +version = "0.1.0" +dependencies = [ + "quote", + "settings", + "syn 2.0.101", + "workspace-hack", +] + [[package]] name = "settings_profile_selector" version = "0.1.0" @@ -15530,18 +15537,6 @@ dependencies = [ "zed_actions", ] -[[package]] -name = "settings_ui_macros" -version = "0.1.0" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "settings", - "syn 2.0.101", - "workspace-hack", -] - [[package]] name = "sha1" version = "0.10.6" @@ -17190,7 +17185,6 @@ dependencies = [ "futures 0.3.31", "gpui", "indexmap 2.9.0", - "inventory", "log", "palette", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index 8a67b28b0e4a7b6e146f4c6a4b84c73d229ceb27..6939fb4dd60cd443e07d16988f187d7074535de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,8 +150,8 @@ members = [ "crates/semantic_version", "crates/session", "crates/settings", + "crates/settings_macros", "crates/settings_profile_selector", - "crates/settings_ui_macros", "crates/snippet", "crates/snippet_provider", "crates/snippets_ui", @@ -383,7 +383,6 @@ semantic_version = { path = "crates/semantic_version" } session = { path = "crates/session" } settings = { path = "crates/settings" } settings_ui = { path = "crates/settings_ui" } -settings_ui_macros = { path = "crates/settings_ui_macros" } snippet = { path = "crates/snippet" } snippet_provider = { path = "crates/snippet_provider" } snippets_ui = { path = "crates/snippets_ui" } diff --git a/crates/agent2/src/tools/grep_tool.rs b/crates/agent2/src/tools/grep_tool.rs index a1cd088c858683429103670c604ed3e08d179483..7a2fa4db4009d94644455710429a44d51fdf20dc 100644 --- a/crates/agent2/src/tools/grep_tool.rs +++ b/crates/agent2/src/tools/grep_tool.rs @@ -834,11 +834,14 @@ mod tests { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -1064,7 +1067,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/agent2/src/tools/list_directory_tool.rs b/crates/agent2/src/tools/list_directory_tool.rs index 41198269d69c17e028cca250069c2e1000ac8dfe..fe7e2a4d85d115d7b9b87be9a2b90f3f7bd19028 100644 --- a/crates/agent2/src/tools/list_directory_tool.rs +++ b/crates/agent2/src/tools/list_directory_tool.rs @@ -427,11 +427,14 @@ mod tests { "**/.mymetadata".to_string(), "**/.hidden_subdir".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -568,7 +571,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index 3a9ddb88fe1a44677e43248184902c12724f6936..6fa157630d487c517a536126d6b8dd4d4d53a4e1 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -593,11 +593,14 @@ mod test { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -804,7 +807,8 @@ mod test { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 176e8b1a1aa85214f79be1b8ec2696ab387434c1..e416ce73e5451e840af8c36e8ffee301bacc79b3 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -11,7 +11,6 @@ use settings::{ DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection, NotifyWhenAgentWaiting, Settings, SettingsContent, }; -use util::MergeFrom; pub use crate::agent_profile::*; @@ -147,7 +146,7 @@ impl Default for AgentProfileId { } impl Settings for AgentSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let agent = content.agent.clone().unwrap(); Self { enabled: agent.enabled.unwrap(), @@ -183,66 +182,6 @@ impl Settings for AgentSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - let Some(value) = &content.agent else { return }; - self.enabled.merge_from(&value.enabled); - self.button.merge_from(&value.button); - self.dock.merge_from(&value.dock); - self.default_width - .merge_from(&value.default_width.map(Into::into)); - self.default_height - .merge_from(&value.default_height.map(Into::into)); - self.default_model = value.default_model.clone().or(self.default_model.take()); - - self.inline_assistant_model = value - .inline_assistant_model - .clone() - .or(self.inline_assistant_model.take()); - self.commit_message_model = value - .clone() - .commit_message_model - .or(self.commit_message_model.take()); - self.thread_summary_model = value - .clone() - .thread_summary_model - .or(self.thread_summary_model.take()); - self.inline_alternatives - .merge_from(&value.inline_alternatives.clone()); - self.default_profile - .merge_from(&value.default_profile.clone().map(AgentProfileId)); - self.default_view.merge_from(&value.default_view); - self.always_allow_tool_actions - .merge_from(&value.always_allow_tool_actions); - self.notify_when_agent_waiting - .merge_from(&value.notify_when_agent_waiting); - self.play_sound_when_agent_done - .merge_from(&value.play_sound_when_agent_done); - self.stream_edits.merge_from(&value.stream_edits); - self.single_file_review - .merge_from(&value.single_file_review); - self.preferred_completion_mode - .merge_from(&value.preferred_completion_mode.map(Into::into)); - self.enable_feedback.merge_from(&value.enable_feedback); - self.expand_edit_card.merge_from(&value.expand_edit_card); - self.expand_terminal_card - .merge_from(&value.expand_terminal_card); - self.use_modifier_to_send - .merge_from(&value.use_modifier_to_send); - - self.model_parameters - .extend_from_slice(&value.model_parameters); - self.message_editor_min_lines - .merge_from(&value.message_editor_min_lines); - - if let Some(profiles) = value.profiles.as_ref() { - self.profiles.extend( - profiles - .into_iter() - .map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())), - ); - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(b) = vscode .read_value("chat.agent.enabled") diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 976dbb411990b4eb647bdec9af83f97c3a9bda84..ee9ac73b2ee23a8b4326ddbc4e60c345ef4a3526 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -1067,7 +1067,7 @@ impl AgentPanel { let _ = settings .theme .agent_font_size - .insert(Some(theme::clamp_font_size(agent_font_size).into())); + .insert(theme::clamp_font_size(agent_font_size).into()); }); } else { theme::adjust_agent_font_size(cx, |size| size + delta); diff --git a/crates/assistant_tools/src/grep_tool.rs b/crates/assistant_tools/src/grep_tool.rs index 9f536df995cdfc5860cc3377dce65871386d50e0..c0de8a8d7e1d5552455656e5a76f3201898e5b67 100644 --- a/crates/assistant_tools/src/grep_tool.rs +++ b/crates/assistant_tools/src/grep_tool.rs @@ -856,11 +856,14 @@ mod tests { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -1160,7 +1163,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 9b4b501bfa55c3037bf6658686d92668cac966a6..9303a50468c428ddd4e603c69d75030dc860e876 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -513,11 +513,14 @@ mod tests { "**/.mymetadata".to_string(), "**/.hidden_subdir".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -701,7 +704,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 4ac2ec14ab9dde97b0ff89a40db356fef42d3741..7006cc690375b904be2128e16d254cc6acbaac01 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -684,11 +684,14 @@ mod test { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -970,7 +973,8 @@ mod test { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index 28f97c4b4df6f8741c3fd0552ed883cc7a8838a3..2c9db4989efa5edcf4ef84c4e3031b53980fad51 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -2,7 +2,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use gpui::App; use settings::{Settings, SettingsStore}; -use util::MergeFrom as _; #[derive(Clone, Debug)] pub struct AudioSettings { @@ -23,7 +22,7 @@ pub struct AudioSettings { /// Configuration of audio in Zed impl Settings for AudioSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let audio = &content.audio.as_ref().unwrap(); AudioSettings { control_input_volume: audio.control_input_volume.unwrap(), @@ -32,17 +31,6 @@ impl Settings for AudioSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(audio) = content.audio.as_ref() else { - return; - }; - self.control_input_volume - .merge_from(&audio.control_input_volume); - self.control_output_volume - .merge_from(&audio.control_output_volume); - self.rodio_audio.merge_from(&audio.rodio_audio); - } - fn import_from_vscode( _vscode: &settings::VsCodeSettings, _current: &mut settings::SettingsContent, diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 4e0348575e687c2b4e36fcde7df83b8f329733d0..6c46c145c15cbcb3a6c6790f21a9958c109b846c 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -9,7 +9,7 @@ use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use paths::remote_servers_dir; use release_channel::{AppCommitSha, ReleaseChannel}; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsContent, SettingsStore}; +use settings::{Settings, SettingsStore}; use smol::{fs, io::AsyncReadExt}; use smol::{fs::File, process::Command}; use std::{ @@ -119,21 +119,9 @@ struct AutoUpdateSetting(bool); /// /// Default: true impl Settings for AutoUpdateSetting { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { - debug_assert_eq!(content.auto_update.unwrap(), true); + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self(content.auto_update.unwrap()) } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - if let Some(auto_update) = content.auto_update { - self.0 = auto_update; - } - } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) { - // We could match on vscode's update.mode here, but - // I think it's more important to have more people updating zed by default. - } } #[derive(Default)] diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index cce8142903c17ec41244d346a79feb8f4cad70e6..a97ac682022ef30c603ca94fe60fe78064726f42 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -1,6 +1,5 @@ use gpui::App; use settings::Settings; -use util::MergeFrom; #[derive(Debug)] pub struct CallSettings { @@ -9,7 +8,7 @@ pub struct CallSettings { } impl Settings for CallSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let call = content.calls.clone().unwrap(); CallSettings { mute_on_join: call.mute_on_join.unwrap(), @@ -17,13 +16,6 @@ impl Settings for CallSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - if let Some(call) = content.calls.clone() { - self.mute_on_join.merge_from(&call.mute_on_join); - self.share_on_join.merge_from(&call.share_on_join); - } - } - fn import_from_vscode( _vscode: &settings::VsCodeSettings, _current: &mut settings::SettingsContent, diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 01007cdc6618996735c859284e3860b936f540e8..86ecb1b34e323289b542d3bd6f48520c50867ad6 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -41,7 +41,6 @@ rand.workspace = true regex.workspace = true release_channel.workspace = true rpc = { workspace = true, features = ["gpui"] } -schemars.workspace = true serde.workspace = true serde_json.workspace = true serde_urlencoded.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 237eaa11d954db7c95eaa513e8e921a1000faac6..e098e7aed52281605c2882514b23c81d2041c6db 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -29,9 +29,8 @@ use proxy::connect_proxy_stream; use rand::prelude::*; use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage}; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsContent, SettingsKey, SettingsUi}; +use settings::{Settings, SettingsContent}; use std::{ any::TypeId, convert::TryFrom, @@ -50,7 +49,7 @@ use telemetry::Telemetry; use thiserror::Error; use tokio::net::TcpStream; use url::Url; -use util::{ConnectionResult, MergeFrom, ResultExt}; +use util::{ConnectionResult, ResultExt}; pub use rpc::*; pub use telemetry_events::Event; @@ -102,7 +101,7 @@ pub struct ClientSettings { } impl Settings for ClientSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { if let Some(server_url) = &*ZED_SERVER_URL { return Self { server_url: server_url.clone(), @@ -112,23 +111,6 @@ impl Settings for ClientSettings { server_url: content.server_url.clone().unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if ZED_SERVER_URL.is_some() { - return; - } - if let Some(server_url) = content.server_url.clone() { - self.server_url = server_url; - } - } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} -} - -#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct ProxySettingsContent { - proxy: Option, } #[derive(Deserialize, Default)] @@ -151,18 +133,12 @@ impl ProxySettings { } impl Settings for ProxySettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { proxy: content.proxy.clone(), } } - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if let Some(proxy) = content.proxy.clone() { - self.proxy = Some(proxy) - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { vscode.string_setting("http.proxy", &mut current.proxy); } @@ -543,21 +519,13 @@ pub struct TelemetrySettings { } impl settings::Settings for TelemetrySettings { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { Self { diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(), metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(), } } - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let Some(telemetry) = &content.telemetry else { - return; - }; - self.diagnostics.merge_from(&telemetry.diagnostics); - self.metrics.merge_from(&telemetry.metrics); - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { let mut telemetry = settings::TelemetrySettingsContent::default(); vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| { diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index f1cd24c820a631f86c215f810011cc5870ae1ff9..58be0c358b2626426bc050b2eb7940f35690b37b 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -1,7 +1,6 @@ use gpui::Pixels; use settings::Settings; use ui::px; -use util::MergeFrom as _; use workspace::dock::DockPosition; #[derive(Debug)] @@ -19,7 +18,7 @@ pub struct NotificationPanelSettings { } impl Settings for CollaborationPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let panel = content.collaboration_panel.as_ref().unwrap(); Self { @@ -28,25 +27,10 @@ impl Settings for CollaborationPanelSettings { default_width: panel.default_width.map(px).unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - if let Some(panel) = content.collaboration_panel.as_ref() { - self.button.merge_from(&panel.button); - self.default_width - .merge_from(&panel.default_width.map(Pixels::from)); - self.dock.merge_from(&panel.dock.map(Into::into)); - } - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _content: &mut settings::SettingsContent, - ) { - } } impl Settings for NotificationPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let panel = content.notification_panel.as_ref().unwrap(); return Self { button: panel.button.unwrap(), @@ -54,19 +38,4 @@ impl Settings for NotificationPanelSettings { default_width: panel.default_width.map(px).unwrap(), }; } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - let Some(panel) = content.notification_panel.as_ref() else { - return; - }; - self.button.merge_from(&panel.button); - self.dock.merge_from(&panel.dock.map(Into::into)); - self.default_width.merge_from(&panel.default_width.map(px)); - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - } } diff --git a/crates/collections/src/collections.rs b/crates/collections/src/collections.rs index be7bbdb59f646682e0eb84ddc40d3e260ef96d94..6544d90b917899764795ee516e8082d179628e7f 100644 --- a/crates/collections/src/collections.rs +++ b/crates/collections/src/collections.rs @@ -22,6 +22,7 @@ pub type IndexMap = indexmap::IndexMap; #[cfg(not(feature = "test-support"))] pub type IndexSet = indexmap::IndexSet; +pub use indexmap::Equivalent; pub use rustc_hash::FxHasher; pub use rustc_hash::{FxHashMap, FxHashSet}; pub use std::collections::*; diff --git a/crates/dap/src/debugger_settings.rs b/crates/dap/src/debugger_settings.rs index 5fa3d60dfc08cd847dd961c322555b5ef54c78f7..114f858eec5a5660e74b1cf8a80aecf812f17f93 100644 --- a/crates/dap/src/debugger_settings.rs +++ b/crates/dap/src/debugger_settings.rs @@ -1,7 +1,6 @@ use dap_types::SteppingGranularity; use gpui::App; use settings::{Settings, SettingsContent}; -use util::MergeFrom; pub struct DebuggerSettings { /// Determines the stepping granularity. @@ -35,7 +34,7 @@ pub struct DebuggerSettings { } impl Settings for DebuggerSettings { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { let content = content.debugger.clone().unwrap(); Self { stepping_granularity: dap_granularity_from_settings( @@ -49,27 +48,6 @@ impl Settings for DebuggerSettings { dock: content.dock.unwrap(), } } - - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let Some(content) = &content.debugger else { - return; - }; - self.stepping_granularity.merge_from( - &content - .stepping_granularity - .map(dap_granularity_from_settings), - ); - self.save_breakpoints.merge_from(&content.save_breakpoints); - self.button.merge_from(&content.button); - self.timeout.merge_from(&content.timeout); - self.log_dap_communications - .merge_from(&content.log_dap_communications); - self.format_dap_log_messages - .merge_from(&content.format_dap_log_messages); - self.dock.merge_from(&content.dock); - } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } fn dap_granularity_from_settings( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index a6893a10791b1e5ce19d4ad484327d6f16539a74..6ad8304f911a54fc0817f963aa593dc6e908637b 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -12,7 +12,6 @@ pub use settings::{ }; use settings::{Settings, SettingsContent}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; -use util::MergeFrom; /// Imports from the VSCode settings at /// https://code.visualstudio.com/docs/reference/default-settings @@ -190,7 +189,7 @@ impl ScrollbarVisibility for EditorSettings { } impl Settings for EditorSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let editor = content.editor.clone(); let scrollbar = editor.scrollbar.unwrap(); let minimap = editor.minimap.unwrap(); @@ -238,7 +237,7 @@ impl Settings for EditorSettings { display_in: minimap.display_in.unwrap(), thumb: minimap.thumb.unwrap(), thumb_border: minimap.thumb_border.unwrap(), - current_line_highlight: minimap.current_line_highlight.flatten(), + current_line_highlight: minimap.current_line_highlight, max_width_columns: minimap.max_width_columns.unwrap(), }, gutter: Gutter { @@ -290,162 +289,6 @@ impl Settings for EditorSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let editor = &content.editor; - self.cursor_blink.merge_from(&editor.cursor_blink); - if let Some(cursor_shape) = editor.cursor_shape { - self.cursor_shape = Some(cursor_shape.into()) - } - self.current_line_highlight - .merge_from(&editor.current_line_highlight); - self.selection_highlight - .merge_from(&editor.selection_highlight); - self.rounded_selection.merge_from(&editor.rounded_selection); - self.lsp_highlight_debounce - .merge_from(&editor.lsp_highlight_debounce); - self.hover_popover_enabled - .merge_from(&editor.hover_popover_enabled); - self.hover_popover_delay - .merge_from(&editor.hover_popover_delay); - self.scroll_beyond_last_line - .merge_from(&editor.scroll_beyond_last_line); - self.vertical_scroll_margin - .merge_from(&editor.vertical_scroll_margin); - self.autoscroll_on_clicks - .merge_from(&editor.autoscroll_on_clicks); - self.horizontal_scroll_margin - .merge_from(&editor.horizontal_scroll_margin); - self.scroll_sensitivity - .merge_from(&editor.scroll_sensitivity); - self.fast_scroll_sensitivity - .merge_from(&editor.fast_scroll_sensitivity); - self.relative_line_numbers - .merge_from(&editor.relative_line_numbers); - self.seed_search_query_from_cursor - .merge_from(&editor.seed_search_query_from_cursor); - self.use_smartcase_search - .merge_from(&editor.use_smartcase_search); - self.multi_cursor_modifier - .merge_from(&editor.multi_cursor_modifier); - self.redact_private_values - .merge_from(&editor.redact_private_values); - self.expand_excerpt_lines - .merge_from(&editor.expand_excerpt_lines); - self.excerpt_context_lines - .merge_from(&editor.excerpt_context_lines); - self.middle_click_paste - .merge_from(&editor.middle_click_paste); - self.double_click_in_multibuffer - .merge_from(&editor.double_click_in_multibuffer); - self.search_wrap.merge_from(&editor.search_wrap); - self.auto_signature_help - .merge_from(&editor.auto_signature_help); - self.show_signature_help_after_edits - .merge_from(&editor.show_signature_help_after_edits); - self.go_to_definition_fallback - .merge_from(&editor.go_to_definition_fallback); - if let Some(hide_mouse) = editor.hide_mouse { - self.hide_mouse = Some(hide_mouse) - } - self.snippet_sort_order - .merge_from(&editor.snippet_sort_order); - if let Some(diagnostics_max_severity) = editor.diagnostics_max_severity { - self.diagnostics_max_severity = Some(diagnostics_max_severity.into()); - } - self.inline_code_actions - .merge_from(&editor.inline_code_actions); - self.lsp_document_colors - .merge_from(&editor.lsp_document_colors); - self.minimum_contrast_for_highlights - .merge_from(&editor.minimum_contrast_for_highlights); - - if let Some(status_bar) = &editor.status_bar { - self.status_bar - .active_language_button - .merge_from(&status_bar.active_language_button); - self.status_bar - .cursor_position_button - .merge_from(&status_bar.cursor_position_button); - } - if let Some(toolbar) = &editor.toolbar { - self.toolbar.breadcrumbs.merge_from(&toolbar.breadcrumbs); - self.toolbar - .quick_actions - .merge_from(&toolbar.quick_actions); - self.toolbar - .selections_menu - .merge_from(&toolbar.selections_menu); - self.toolbar.agent_review.merge_from(&toolbar.agent_review); - self.toolbar.code_actions.merge_from(&toolbar.code_actions); - } - if let Some(scrollbar) = &editor.scrollbar { - self.scrollbar - .show - .merge_from(&scrollbar.show.map(Into::into)); - self.scrollbar.git_diff.merge_from(&scrollbar.git_diff); - self.scrollbar - .selected_text - .merge_from(&scrollbar.selected_text); - self.scrollbar - .selected_symbol - .merge_from(&scrollbar.selected_symbol); - self.scrollbar - .search_results - .merge_from(&scrollbar.search_results); - self.scrollbar - .diagnostics - .merge_from(&scrollbar.diagnostics); - self.scrollbar.cursors.merge_from(&scrollbar.cursors); - if let Some(axes) = &scrollbar.axes { - self.scrollbar.axes.horizontal.merge_from(&axes.horizontal); - self.scrollbar.axes.vertical.merge_from(&axes.vertical); - } - } - if let Some(minimap) = &editor.minimap { - self.minimap.show.merge_from(&minimap.show); - self.minimap.display_in.merge_from(&minimap.display_in); - self.minimap.thumb.merge_from(&minimap.thumb); - self.minimap.thumb_border.merge_from(&minimap.thumb_border); - self.minimap - .current_line_highlight - .merge_from(&minimap.current_line_highlight); - self.minimap - .max_width_columns - .merge_from(&minimap.max_width_columns); - } - if let Some(gutter) = &editor.gutter { - self.gutter - .min_line_number_digits - .merge_from(&gutter.min_line_number_digits); - self.gutter.line_numbers.merge_from(&gutter.line_numbers); - self.gutter.runnables.merge_from(&gutter.runnables); - self.gutter.breakpoints.merge_from(&gutter.breakpoints); - self.gutter.folds.merge_from(&gutter.folds); - } - if let Some(search) = &editor.search { - self.search.button.merge_from(&search.button); - self.search.whole_word.merge_from(&search.whole_word); - self.search - .case_sensitive - .merge_from(&search.case_sensitive); - self.search - .include_ignored - .merge_from(&search.include_ignored); - self.search.regex.merge_from(&search.regex); - } - if let Some(enabled) = editor.jupyter.as_ref().and_then(|jupyter| jupyter.enabled) { - self.jupyter.enabled = enabled; - } - if let Some(drag_and_drop_selection) = &editor.drag_and_drop_selection { - self.drag_and_drop_selection - .enabled - .merge_from(&drag_and_drop_selection.enabled); - self.drag_and_drop_selection - .delay - .merge_from(&drag_and_drop_selection.delay); - } - } - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) { vscode.enum_setting( "editor.cursorBlinking", diff --git a/crates/extension_host/src/extension_settings.rs b/crates/extension_host/src/extension_settings.rs index b9e1609b42d6e7fa2f4dab63486a77a13f29824f..0f15add8cbf7e422b2d90c684a46c464672ad1d6 100644 --- a/crates/extension_host/src/extension_settings.rs +++ b/crates/extension_host/src/extension_settings.rs @@ -33,26 +33,10 @@ impl ExtensionSettings { } impl Settings for ExtensionSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { auto_install_extensions: content.extension.auto_install_extensions.clone(), auto_update_extensions: content.extension.auto_update_extensions.clone(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - self.auto_install_extensions - .extend(content.extension.auto_install_extensions.clone()); - self.auto_update_extensions - .extend(content.extension.auto_update_extensions.clone()); - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - // settingsSync.ignoredExtensions controls autoupdate for vscode extensions, but we - // don't have a mapping to zed-extensions. there's also extensions.autoCheckUpdates - // and extensions.autoUpdate which are global switches, we don't support those yet - } } diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 3f974b7412d740dd30e96fdd61487ac8774daa0e..cf2b4f4bfb87f7a71c2dcc2a1d0a2218131c988a 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -1,7 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; -use util::MergeFrom; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct FileFinderSettings { @@ -12,30 +11,16 @@ pub struct FileFinderSettings { } impl Settings for FileFinderSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let file_finder = content.file_finder.as_ref().unwrap(); Self { file_icons: file_finder.file_icons.unwrap(), modal_max_width: file_finder.modal_max_width.unwrap().into(), skip_focus_for_active_in_search: file_finder.skip_focus_for_active_in_search.unwrap(), - include_ignored: file_finder.include_ignored.flatten(), + include_ignored: file_finder.include_ignored, } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - let Some(file_finder) = content.file_finder.as_ref() else { - return; - }; - - self.file_icons.merge_from(&file_finder.file_icons); - self.modal_max_width - .merge_from(&file_finder.modal_max_width.map(Into::into)); - self.skip_focus_for_active_in_search - .merge_from(&file_finder.skip_focus_for_active_in_search); - self.include_ignored - .merge_from(&file_finder.include_ignored); - } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] diff --git a/crates/git_hosting_providers/src/settings.rs b/crates/git_hosting_providers/src/settings.rs index b6aabc47f3fba9fca6c9b908de87ca7319afa616..e045fae08b7a4dc019177361d7365f286c95518c 100644 --- a/crates/git_hosting_providers/src/settings.rs +++ b/crates/git_hosting_providers/src/settings.rs @@ -58,17 +58,14 @@ pub struct GitHostingProviderSettings { } impl Settings for GitHostingProviderSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { - git_hosting_providers: content.project.git_hosting_providers.clone().unwrap(), + git_hosting_providers: content + .project + .git_hosting_providers + .clone() + .unwrap() + .into(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if let Some(more) = &content.project.git_hosting_providers { - self.git_hosting_providers.extend_from_slice(&more.clone()); - } - } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} } diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index c82ff469857f084ce59201b359a79883056fee43..b137988539510a3d3242656bd1f6cc6d85a07703 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -7,7 +7,6 @@ use ui::{ px, scrollbars::{ScrollbarVisibility, ShowScrollbar}, }; -use util::MergeFrom; use workspace::dock::DockPosition; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -52,7 +51,7 @@ impl ScrollbarVisibility for GitPanelSettings { } impl Settings for GitPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let git_panel = content.git_panel.clone().unwrap(); Self { button: git_panel.button.unwrap(), @@ -68,25 +67,6 @@ impl Settings for GitPanelSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - let Some(git_panel) = &content.git_panel else { - return; - }; - self.button.merge_from(&git_panel.button); - self.dock.merge_from(&git_panel.dock.map(Into::into)); - self.default_width - .merge_from(&git_panel.default_width.map(px)); - self.status_style.merge_from(&git_panel.status_style); - self.fallback_branch_name - .merge_from(&git_panel.fallback_branch_name); - self.sort_by_path.merge_from(&git_panel.sort_by_path); - self.collapse_untracked_diff - .merge_from(&git_panel.collapse_untracked_diff); - if let Some(show) = git_panel.scrollbar.as_ref().and_then(|s| s.show) { - self.scrollbar.show = Some(show.into()) - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(git_enabled) = vscode.read_bool("git.enabled") { current.git_panel.get_or_insert_default().button = Some(git_enabled); diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 9c109db8de6e092da5513317d9bc5b686146edf8..9387a5359ef3013574876602b4c67a44497c65cc 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -7,7 +7,7 @@ use ui::{ Button, ButtonCommon, Clickable, Context, FluentBuilder, IntoElement, LabelSize, ParentElement, Render, Tooltip, Window, div, }; -use util::{MergeFrom, paths::FILE_ROW_COLUMN_DELIMITER}; +use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{StatusItemView, Workspace, item::ItemHandle}; #[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)] @@ -307,11 +307,7 @@ impl From for LineIndicatorFormat { } impl Settings for LineIndicatorFormat { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { content.line_indicator_format.unwrap().into() } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - self.merge_from(&content.line_indicator_format.map(Into::into)); - } } diff --git a/crates/image_viewer/src/image_viewer_settings.rs b/crates/image_viewer/src/image_viewer_settings.rs index e093a5fc826fa23c9894ecdb56466dcb71e03465..64f2e4948284265c1f348cb90be85d18d8c22d8e 100644 --- a/crates/image_viewer/src/image_viewer_settings.rs +++ b/crates/image_viewer/src/image_viewer_settings.rs @@ -1,7 +1,6 @@ use gpui::App; pub use settings::ImageFileSizeUnit; use settings::Settings; -use util::MergeFrom; /// The settings for the image viewer. #[derive(Clone, Debug, Default)] @@ -13,18 +12,9 @@ pub struct ImageViewerSettings { } impl Settings for ImageViewerSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { unit: content.image_viewer.clone().unwrap().unit.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - self.unit.merge_from( - &content - .image_viewer - .as_ref() - .and_then(|image_viewer| image_viewer.unit), - ); - } } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 3c98e4a712a4e53358d2d31e7df981dccb86fa3c..1b32c9cdbb7a20a13e39a2d61554e4dd7018d81b 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -23,7 +23,6 @@ settings.workspace = true shellexpand.workspace = true workspace.workspace = true workspace-hack.workspace = true -util.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 24c3fecf78c2ea3ce1cd036e8b90f88a5d889ccd..52d93ba21a828b076141dd8d21a1b8f88bc20be8 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -9,7 +9,6 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::MergeFrom; use workspace::{AppState, OpenVisible, Workspace}; actions!( @@ -34,7 +33,7 @@ pub struct JournalSettings { } impl settings::Settings for JournalSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let journal = content.journal.clone().unwrap(); Self { @@ -42,14 +41,6 @@ impl settings::Settings for JournalSettings { hour_format: journal.hour_format.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(journal) = content.journal.as_ref() else { - return; - }; - self.path.merge_from(&journal.path); - self.hour_format.merge_from(&journal.hour_format); - } } pub fn init(_: Arc, cx: &mut App) { diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 4ab56d6647db5246bf0af7343c8485d946c8b156..4a4f51a58be7e4ffc61617dd74c2ed0ad7b49d34 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -39,7 +39,6 @@ globset.workspace = true gpui.workspace = true http_client.workspace = true imara-diff.workspace = true -inventory.workspace = true itertools.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index e079029b478af1c70ff626acea0791cdb204421f..a60399538e840d18d42d3ed2b6d6f62927d4e046 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -269,15 +269,15 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) cx.update(|cx| { init_settings(cx, |settings| { settings.file_types.extend([ - ("TypeScript".into(), vec!["js".into()]), + ("TypeScript".into(), vec!["js".into()].into()), ( "JavaScript".into(), - vec!["*longer.ts".into(), "ecmascript".into()], + vec!["*longer.ts".into(), "ecmascript".into()].into(), ), - ("C++".into(), vec!["c".into(), "*.dev".into()]), + ("C++".into(), vec!["c".into(), "*.dev".into()].into()), ( "Dockerfile".into(), - vec!["Dockerfile".into(), "Dockerfile.*".into()], + vec!["Dockerfile".into(), "Dockerfile.*".into()].into(), ), ]); }) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index ff7b432c5f26043799d94458621aa35f969ade06..64744ee99d24a56abb357e0c034e11afa4dae9d0 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -9,22 +9,15 @@ use ec4rs::{ use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use gpui::{App, Modifiers}; use itertools::{Either, Itertools}; -use schemars::json_schema; pub use settings::{ CompletionSettingsContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave, Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode, RewrapBehavior, SelectedFormatter, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode, }; -use settings::{ - IndentGuideSettingsContent, LanguageTaskSettingsContent, ParameterizedJsonSchema, - PrettierSettingsContent, Settings, SettingsContent, SettingsLocation, SettingsStore, - SettingsUi, -}; +use settings::{ExtendingVec, Settings, SettingsContent, SettingsLocation, SettingsStore}; use shellexpand; use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; -use util::MergeFrom; -use util::{ResultExt, schemars::replace_subschema}; /// Initializes the language settings. pub fn init(cx: &mut App) { @@ -64,7 +57,6 @@ pub struct AllLanguageSettings { pub defaults: LanguageSettings, languages: HashMap, pub(crate) file_types: FxHashMap, GlobSet>, - pub(crate) file_globs: FxHashMap, Vec>, } /// The settings for a particular language. @@ -189,18 +181,6 @@ pub struct CompletionSettings { pub lsp_insert_mode: LspInsertMode, } -impl CompletionSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src else { return }; - self.words.merge_from(&src.words); - self.words_min_length.merge_from(&src.words_min_length); - self.lsp.merge_from(&src.lsp); - self.lsp_fetch_timeout_ms - .merge_from(&src.lsp_fetch_timeout_ms); - self.lsp_insert_mode.merge_from(&src.lsp_insert_mode); - } -} - /// The settings for indent guides. #[derive(Debug, Clone, PartialEq)] pub struct IndentGuideSettings { @@ -226,19 +206,6 @@ pub struct IndentGuideSettings { pub background_coloring: settings::IndentGuideBackgroundColoring, } -impl IndentGuideSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src else { return }; - - self.enabled.merge_from(&src.enabled); - self.line_width.merge_from(&src.line_width); - self.active_line_width.merge_from(&src.active_line_width); - self.coloring.merge_from(&src.coloring); - self.background_coloring - .merge_from(&src.background_coloring); - } -} - #[derive(Debug, Clone)] pub struct LanguageTaskSettings { /// Extra task variables to set for a particular language. @@ -254,17 +221,6 @@ pub struct LanguageTaskSettings { pub prefer_lsp: bool, } -impl LanguageTaskSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src.clone() else { - return; - }; - self.variables.extend(src.variables); - self.enabled.merge_from(&src.enabled); - self.prefer_lsp.merge_from(&src.prefer_lsp); - } -} - /// Allows to enable/disable formatting with Prettier /// and configure default Prettier, used when no project-level Prettier installation is found. /// Prettier formatting is disabled by default. @@ -285,16 +241,6 @@ pub struct PrettierSettings { pub options: HashMap, } -impl PrettierSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src.clone() else { return }; - self.allowed.merge_from(&src.allowed); - self.parser = src.parser.clone(); - self.plugins.extend(src.plugins); - self.options.extend(src.options); - } -} - impl LanguageSettings { /// A token representing the rest of the available language servers. const REST_OF_LANGUAGE_SERVERS: &'static str = "..."; @@ -413,14 +359,13 @@ impl InlayHintSettings { /// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot) /// or [Supermaven](https://supermaven.com). -#[derive(Clone, Debug, Default, SettingsUi)] +#[derive(Clone, Debug, Default)] pub struct EditPredictionSettings { /// The provider that supplies edit predictions. pub provider: settings::EditPredictionProvider, /// A list of globs representing files that edit predictions should be disabled for. /// This list adds to a pre-existing, sensible default set of globs. /// Any additional ones you add are combined with them. - #[settings_ui(skip)] pub disabled_globs: Vec, /// Configures how edit predictions are displayed in the buffer. pub mode: settings::EditPredictionsMode, @@ -451,41 +396,16 @@ pub struct DisabledGlob { is_absolute: bool, } -#[derive(Clone, Debug, Default, SettingsUi)] +#[derive(Clone, Debug, Default)] pub struct CopilotSettings { /// HTTP/HTTPS proxy to use for Copilot. - #[settings_ui(skip)] pub proxy: Option, /// Disable certificate verification for proxy (not recommended). pub proxy_no_verify: Option, /// Enterprise URI for Copilot. - #[settings_ui(skip)] pub enterprise_uri: Option, } -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - let language_settings_content_ref = generator - .subschema_for::() - .to_value(); - replace_subschema::(generator, || json_schema!({ - "type": "object", - "properties": params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - language_settings_content_ref.clone(), - ) - }) - .collect::>() - })) - } - } -} - impl AllLanguageSettings { /// Returns the [`LanguageSettings`] for the language with the specified name. pub fn language<'a>( @@ -574,93 +494,99 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr } impl settings::Settings for AllLanguageSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let all_languages = &content.project.all_languages; - let defaults = all_languages.defaults.clone(); - let inlay_hints = defaults.inlay_hints.unwrap(); - let completions = defaults.completions.unwrap(); - let prettier = defaults.prettier.unwrap(); - let indent_guides = defaults.indent_guides.unwrap(); - let tasks = defaults.tasks.unwrap(); - - let default_language_settings = LanguageSettings { - tab_size: defaults.tab_size.unwrap(), - hard_tabs: defaults.hard_tabs.unwrap(), - soft_wrap: defaults.soft_wrap.unwrap(), - preferred_line_length: defaults.preferred_line_length.unwrap(), - show_wrap_guides: defaults.show_wrap_guides.unwrap(), - wrap_guides: defaults.wrap_guides.unwrap(), - indent_guides: IndentGuideSettings { - enabled: indent_guides.enabled.unwrap(), - line_width: indent_guides.line_width.unwrap(), - active_line_width: indent_guides.active_line_width.unwrap(), - coloring: indent_guides.coloring.unwrap(), - background_coloring: indent_guides.background_coloring.unwrap(), - }, - format_on_save: defaults.format_on_save.unwrap(), - remove_trailing_whitespace_on_save: defaults - .remove_trailing_whitespace_on_save - .unwrap(), - ensure_final_newline_on_save: defaults.ensure_final_newline_on_save.unwrap(), - formatter: defaults.formatter.unwrap(), - prettier: PrettierSettings { - allowed: prettier.allowed.unwrap(), - parser: prettier.parser, - plugins: prettier.plugins, - options: prettier.options, - }, - jsx_tag_auto_close: defaults.jsx_tag_auto_close.unwrap().enabled.unwrap(), - enable_language_server: defaults.enable_language_server.unwrap(), - language_servers: defaults.language_servers.unwrap(), - allow_rewrap: defaults.allow_rewrap.unwrap(), - show_edit_predictions: defaults.show_edit_predictions.unwrap(), - edit_predictions_disabled_in: defaults.edit_predictions_disabled_in.unwrap(), - show_whitespaces: defaults.show_whitespaces.unwrap(), - whitespace_map: defaults.whitespace_map.unwrap(), - extend_comment_on_newline: defaults.extend_comment_on_newline.unwrap(), - inlay_hints: InlayHintSettings { - enabled: inlay_hints.enabled.unwrap(), - show_value_hints: inlay_hints.show_value_hints.unwrap(), - show_type_hints: inlay_hints.show_type_hints.unwrap(), - show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(), - show_other_hints: inlay_hints.show_other_hints.unwrap(), - show_background: inlay_hints.show_background.unwrap(), - edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(), - scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(), - toggle_on_modifiers_press: inlay_hints.toggle_on_modifiers_press, - }, - use_autoclose: defaults.use_autoclose.unwrap(), - use_auto_surround: defaults.use_auto_surround.unwrap(), - use_on_type_format: defaults.use_on_type_format.unwrap(), - auto_indent: defaults.auto_indent.unwrap(), - auto_indent_on_paste: defaults.auto_indent_on_paste.unwrap(), - always_treat_brackets_as_autoclosed: defaults - .always_treat_brackets_as_autoclosed - .unwrap(), - code_actions_on_format: defaults.code_actions_on_format.unwrap(), - linked_edits: defaults.linked_edits.unwrap(), - tasks: LanguageTaskSettings { - variables: tasks.variables, - enabled: tasks.enabled.unwrap(), - prefer_lsp: tasks.prefer_lsp.unwrap(), - }, - show_completions_on_input: defaults.show_completions_on_input.unwrap(), - show_completion_documentation: defaults.show_completion_documentation.unwrap(), - completions: CompletionSettings { - words: completions.words.unwrap(), - words_min_length: completions.words_min_length.unwrap(), - lsp: completions.lsp.unwrap(), - lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(), - lsp_insert_mode: completions.lsp_insert_mode.unwrap(), - }, - debuggers: defaults.debuggers.unwrap(), - }; + + fn load_from_content(settings: LanguageSettingsContent) -> LanguageSettings { + let inlay_hints = settings.inlay_hints.unwrap(); + let completions = settings.completions.unwrap(); + let prettier = settings.prettier.unwrap(); + let indent_guides = settings.indent_guides.unwrap(); + let tasks = settings.tasks.unwrap(); + LanguageSettings { + tab_size: settings.tab_size.unwrap(), + hard_tabs: settings.hard_tabs.unwrap(), + soft_wrap: settings.soft_wrap.unwrap(), + preferred_line_length: settings.preferred_line_length.unwrap(), + show_wrap_guides: settings.show_wrap_guides.unwrap(), + wrap_guides: settings.wrap_guides.unwrap(), + indent_guides: IndentGuideSettings { + enabled: indent_guides.enabled.unwrap(), + line_width: indent_guides.line_width.unwrap(), + active_line_width: indent_guides.active_line_width.unwrap(), + coloring: indent_guides.coloring.unwrap(), + background_coloring: indent_guides.background_coloring.unwrap(), + }, + format_on_save: settings.format_on_save.unwrap(), + remove_trailing_whitespace_on_save: settings + .remove_trailing_whitespace_on_save + .unwrap(), + ensure_final_newline_on_save: settings.ensure_final_newline_on_save.unwrap(), + formatter: settings.formatter.unwrap(), + prettier: PrettierSettings { + allowed: prettier.allowed.unwrap(), + parser: prettier.parser, + plugins: prettier.plugins, + options: prettier.options, + }, + jsx_tag_auto_close: settings.jsx_tag_auto_close.unwrap().enabled.unwrap(), + enable_language_server: settings.enable_language_server.unwrap(), + language_servers: settings.language_servers.unwrap(), + allow_rewrap: settings.allow_rewrap.unwrap(), + show_edit_predictions: settings.show_edit_predictions.unwrap(), + edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(), + show_whitespaces: settings.show_whitespaces.unwrap(), + whitespace_map: settings.whitespace_map.unwrap(), + extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(), + inlay_hints: InlayHintSettings { + enabled: inlay_hints.enabled.unwrap(), + show_value_hints: inlay_hints.show_value_hints.unwrap(), + show_type_hints: inlay_hints.show_type_hints.unwrap(), + show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(), + show_other_hints: inlay_hints.show_other_hints.unwrap(), + show_background: inlay_hints.show_background.unwrap(), + edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(), + scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(), + toggle_on_modifiers_press: inlay_hints.toggle_on_modifiers_press, + }, + use_autoclose: settings.use_autoclose.unwrap(), + use_auto_surround: settings.use_auto_surround.unwrap(), + use_on_type_format: settings.use_on_type_format.unwrap(), + auto_indent: settings.auto_indent.unwrap(), + auto_indent_on_paste: settings.auto_indent_on_paste.unwrap(), + always_treat_brackets_as_autoclosed: settings + .always_treat_brackets_as_autoclosed + .unwrap(), + code_actions_on_format: settings.code_actions_on_format.unwrap(), + linked_edits: settings.linked_edits.unwrap(), + tasks: LanguageTaskSettings { + variables: tasks.variables, + enabled: tasks.enabled.unwrap(), + prefer_lsp: tasks.prefer_lsp.unwrap(), + }, + show_completions_on_input: settings.show_completions_on_input.unwrap(), + show_completion_documentation: settings.show_completion_documentation.unwrap(), + completions: CompletionSettings { + words: completions.words.unwrap(), + words_min_length: completions.words_min_length.unwrap(), + lsp: completions.lsp.unwrap(), + lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(), + lsp_insert_mode: completions.lsp_insert_mode.unwrap(), + }, + debuggers: settings.debuggers.unwrap(), + } + } + + let default_language_settings = load_from_content(all_languages.defaults.clone()); let mut languages = HashMap::default(); for (language_name, settings) in &all_languages.languages.0 { - let mut language_settings = default_language_settings.clone(); - merge_settings(&mut language_settings, settings); - languages.insert(LanguageName(language_name.clone()), language_settings); + let mut language_settings = all_languages.defaults.clone(); + settings::merge_from::MergeFrom::merge_from(&mut language_settings, Some(settings)); + languages.insert( + LanguageName(language_name.clone()), + load_from_content(language_settings), + ); } let edit_prediction_provider = all_languages @@ -688,17 +614,15 @@ impl settings::Settings for AllLanguageSettings { let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap(); let mut file_types: FxHashMap, GlobSet> = FxHashMap::default(); - let mut file_globs: FxHashMap, Vec> = FxHashMap::default(); for (language, patterns) in &all_languages.file_types { let mut builder = GlobSetBuilder::new(); - for pattern in patterns { + for pattern in &patterns.0 { builder.add(Glob::new(pattern).unwrap()); } file_types.insert(language.clone(), builder.build().unwrap()); - file_globs.insert(language.clone(), patterns.clone()); } Self { @@ -725,110 +649,6 @@ impl settings::Settings for AllLanguageSettings { defaults: default_language_settings, languages, file_types, - file_globs, - } - } - - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let all_languages = &content.project.all_languages; - if let Some(provider) = all_languages - .features - .as_ref() - .and_then(|f| f.edit_prediction_provider) - { - self.edit_predictions.provider = provider; - } - - if let Some(edit_predictions) = all_languages.edit_predictions.as_ref() { - self.edit_predictions - .mode - .merge_from(&edit_predictions.mode); - self.edit_predictions - .enabled_in_text_threads - .merge_from(&edit_predictions.enabled_in_text_threads); - - if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() { - self.edit_predictions - .disabled_globs - .extend(disabled_globs.iter().filter_map(|g| { - let expanded_g = shellexpand::tilde(g).into_owned(); - Some(DisabledGlob { - matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(), - is_absolute: Path::new(&expanded_g).is_absolute(), - }) - })); - } - } - - if let Some(proxy) = all_languages - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.as_ref()?.proxy.clone()) - { - self.edit_predictions.copilot.proxy = Some(proxy); - } - - if let Some(proxy_no_verify) = all_languages - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.as_ref()?.proxy_no_verify) - { - self.edit_predictions.copilot.proxy_no_verify = Some(proxy_no_verify); - } - - if let Some(enterprise_uri) = all_languages - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.as_ref()?.enterprise_uri.clone()) - { - self.edit_predictions.copilot.enterprise_uri = Some(enterprise_uri); - } - - // A user's global settings override the default global settings and - // all default language-specific settings. - merge_settings(&mut self.defaults, &all_languages.defaults); - for language_settings in self.languages.values_mut() { - merge_settings(language_settings, &all_languages.defaults); - } - - // A user's language-specific settings override default language-specific settings. - for (language_name, user_language_settings) in &all_languages.languages.0 { - merge_settings( - self.languages - .entry(LanguageName(language_name.clone())) - .or_insert_with(|| self.defaults.clone()), - user_language_settings, - ); - } - - for (language, patterns) in &all_languages.file_types { - let mut builder = GlobSetBuilder::new(); - - let default_value = self.file_globs.get(&language.clone()); - - // Merge the default value with the user's value. - if let Some(patterns) = default_value { - for pattern in patterns { - if let Some(glob) = Glob::new(pattern).log_err() { - builder.add(glob); - } - } - } - - for pattern in patterns { - if let Some(glob) = Glob::new(pattern).log_err() { - builder.add(glob); - } - } - - self.file_globs - .entry(language.clone()) - .or_default() - .extend(patterns.clone()); - - if let Some(matcher) = builder.build().log_err() { - self.file_types.insert(language.clone(), matcher); - } } } @@ -917,14 +737,14 @@ impl settings::Settings for AllLanguageSettings { // TODO: pull ^ out into helper and reuse for per-language settings // vscodes file association map is inverted from ours, so we flip the mapping before merging - let mut associations: HashMap, Vec> = HashMap::default(); + let mut associations: HashMap, ExtendingVec> = HashMap::default(); if let Some(map) = vscode .read_value("files.associations") .and_then(|v| v.as_object()) { for (k, v) in map { let Some(v) = v.as_str() else { continue }; - associations.entry(v.into()).or_default().push(k.clone()); + associations.entry(v.into()).or_default().0.push(k.clone()); } } @@ -957,121 +777,7 @@ impl settings::Settings for AllLanguageSettings { } } -fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { - settings.tab_size.merge_from(&src.tab_size); - settings.tab_size = settings - .tab_size - .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap()); - - settings.hard_tabs.merge_from(&src.hard_tabs); - settings.soft_wrap.merge_from(&src.soft_wrap); - settings.use_autoclose.merge_from(&src.use_autoclose); - settings - .use_auto_surround - .merge_from(&src.use_auto_surround); - settings - .use_on_type_format - .merge_from(&src.use_on_type_format); - settings.auto_indent.merge_from(&src.auto_indent); - settings - .auto_indent_on_paste - .merge_from(&src.auto_indent_on_paste); - settings - .always_treat_brackets_as_autoclosed - .merge_from(&src.always_treat_brackets_as_autoclosed); - settings.show_wrap_guides.merge_from(&src.show_wrap_guides); - settings.wrap_guides.merge_from(&src.wrap_guides); - settings.indent_guides.merge_from(&src.indent_guides); - settings - .code_actions_on_format - .merge_from(&src.code_actions_on_format.clone()); - settings.linked_edits.merge_from(&src.linked_edits); - settings.tasks.merge_from(&src.tasks); - - settings - .preferred_line_length - .merge_from(&src.preferred_line_length); - settings.formatter.merge_from(&src.formatter.clone()); - settings.prettier.merge_from(&src.prettier.clone()); - settings - .jsx_tag_auto_close - .merge_from(&src.jsx_tag_auto_close.as_ref().and_then(|v| v.enabled)); - settings - .format_on_save - .merge_from(&src.format_on_save.clone()); - settings - .remove_trailing_whitespace_on_save - .merge_from(&src.remove_trailing_whitespace_on_save); - settings - .ensure_final_newline_on_save - .merge_from(&src.ensure_final_newline_on_save); - settings - .enable_language_server - .merge_from(&src.enable_language_server); - settings - .language_servers - .merge_from(&src.language_servers.clone()); - settings.allow_rewrap.merge_from(&src.allow_rewrap); - settings - .show_edit_predictions - .merge_from(&src.show_edit_predictions); - settings - .edit_predictions_disabled_in - .merge_from(&src.edit_predictions_disabled_in.clone()); - settings.show_whitespaces.merge_from(&src.show_whitespaces); - settings - .whitespace_map - .merge_from(&src.whitespace_map.clone()); - settings - .extend_comment_on_newline - .merge_from(&src.extend_comment_on_newline); - if let Some(inlay_hints) = &src.inlay_hints { - settings - .inlay_hints - .enabled - .merge_from(&inlay_hints.enabled); - settings - .inlay_hints - .show_value_hints - .merge_from(&inlay_hints.show_value_hints); - settings - .inlay_hints - .show_type_hints - .merge_from(&inlay_hints.show_type_hints); - settings - .inlay_hints - .show_parameter_hints - .merge_from(&inlay_hints.show_parameter_hints); - settings - .inlay_hints - .show_other_hints - .merge_from(&inlay_hints.show_other_hints); - settings - .inlay_hints - .show_background - .merge_from(&inlay_hints.show_background); - settings - .inlay_hints - .edit_debounce_ms - .merge_from(&inlay_hints.edit_debounce_ms); - settings - .inlay_hints - .scroll_debounce_ms - .merge_from(&inlay_hints.scroll_debounce_ms); - if let Some(toggle_on_modifiers_press) = &inlay_hints.toggle_on_modifiers_press { - settings.inlay_hints.toggle_on_modifiers_press = Some(*toggle_on_modifiers_press); - } - } - settings - .show_completions_on_input - .merge_from(&src.show_completions_on_input); - settings - .show_completion_documentation - .merge_from(&src.show_completion_documentation); - settings.completions.merge_from(&src.completions); -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, SettingsUi)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct JsxTagAutoCloseSettings { /// Enables or disables auto-closing of JSX tags. pub enabled: bool, diff --git a/crates/language_models/src/settings.rs b/crates/language_models/src/settings.rs index 30b208a170794a0d4018e8ebce39f1020b4c9b29..178703bd93d0d2cf0ece2e82ed26cedb49a38196 100644 --- a/crates/language_models/src/settings.rs +++ b/crates/language_models/src/settings.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use collections::HashMap; use gpui::App; use settings::Settings; -use util::MergeFrom; use crate::provider::{ anthropic::AnthropicSettings, bedrock::AmazonBedrockSettings, cloud::ZedDotDevSettings, @@ -37,7 +36,7 @@ pub struct AllLanguageModelSettings { impl settings::Settings for AllLanguageModelSettings { const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]); - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let language_models = content.language_models.clone().unwrap(); let anthropic = language_models.anthropic.unwrap(); let bedrock = language_models.bedrock.unwrap(); @@ -118,113 +117,4 @@ impl settings::Settings for AllLanguageModelSettings { }, } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(models) = content.language_models.as_ref() else { - return; - }; - - if let Some(anthropic) = models.anthropic.as_ref() { - self.anthropic - .available_models - .merge_from(&anthropic.available_models); - self.anthropic.api_url.merge_from(&anthropic.api_url); - } - - if let Some(bedrock) = models.bedrock.clone() { - self.bedrock - .available_models - .merge_from(&bedrock.available_models); - - if let Some(endpoint_url) = bedrock.endpoint_url { - self.bedrock.endpoint = Some(endpoint_url) - } - - if let Some(region) = bedrock.region { - self.bedrock.region = Some(region) - } - - if let Some(profile_name) = bedrock.profile { - self.bedrock.profile_name = Some(profile_name); - } - - if let Some(auth_method) = bedrock.authentication_method { - self.bedrock.authentication_method = Some(auth_method.into()); - } - } - - if let Some(deepseek) = models.deepseek.as_ref() { - self.deepseek - .available_models - .merge_from(&deepseek.available_models); - self.deepseek.api_url.merge_from(&deepseek.api_url); - } - - if let Some(google) = models.google.as_ref() { - self.google - .available_models - .merge_from(&google.available_models); - self.google.api_url.merge_from(&google.api_url); - } - - if let Some(lmstudio) = models.lmstudio.as_ref() { - self.lmstudio - .available_models - .merge_from(&lmstudio.available_models); - self.lmstudio.api_url.merge_from(&lmstudio.api_url); - } - - if let Some(mistral) = models.mistral.as_ref() { - self.mistral - .available_models - .merge_from(&mistral.available_models); - self.mistral.api_url.merge_from(&mistral.api_url); - } - if let Some(ollama) = models.ollama.as_ref() { - self.ollama - .available_models - .merge_from(&ollama.available_models); - self.ollama.api_url.merge_from(&ollama.api_url); - } - if let Some(open_router) = models.open_router.as_ref() { - self.open_router - .available_models - .merge_from(&open_router.available_models); - self.open_router.api_url.merge_from(&open_router.api_url); - } - if let Some(openai) = models.openai.as_ref() { - self.openai - .available_models - .merge_from(&openai.available_models); - self.openai.api_url.merge_from(&openai.api_url); - } - if let Some(openai_compatible) = models.openai_compatible.clone() { - for (name, value) in openai_compatible { - self.openai_compatible.insert( - name, - OpenAiCompatibleSettings { - api_url: value.api_url, - available_models: value.available_models, - }, - ); - } - } - if let Some(vercel) = models.vercel.as_ref() { - self.vercel - .available_models - .merge_from(&vercel.available_models); - self.vercel.api_url.merge_from(&vercel.api_url); - } - if let Some(x_ai) = models.x_ai.as_ref() { - self.x_ai - .available_models - .merge_from(&x_ai.available_models); - self.x_ai.api_url.merge_from(&x_ai.api_url); - } - if let Some(zed_dot_dev) = models.zed_dot_dev.as_ref() { - self.zed_dot_dev - .available_models - .merge_from(&zed_dot_dev.available_models); - } - } } diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index f08c548ddcb10a311e1d5b29a9bf50a7b5bc4fb1..efcbaa57ef88b36820150d9634eea6b27d0969cf 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -74,6 +74,7 @@ snippet_provider.workspace = true url.workspace = true task.workspace = true tempfile.workspace = true +theme.workspace = true toml.workspace = true tree-sitter = { workspace = true, optional = true } tree-sitter-bash = { workspace = true, optional = true } diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index df4a0d114611a2bab7a49417bb0a0bac79eaf734..496b0389e6e331f5c1d694d3ad30b5abffbee106 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use collections::HashMap; use dap::DapRegistry; use futures::StreamExt; -use gpui::{App, AsyncApp, Task}; +use gpui::{App, AsyncApp, SharedString, Task}; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; use language::{ ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter, @@ -29,6 +29,7 @@ use std::{ sync::Arc, }; use task::{AdapterSchemas, TaskTemplate, TaskTemplates, VariableName}; +use theme::ThemeRegistry; use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into}; use crate::PackageJsonData; @@ -156,13 +157,20 @@ impl JsonLspAdapter { ) -> Value { let keymap_schema = KeymapFile::generate_json_schema_for_registered_actions(cx); let font_names = &cx.text_system().all_font_names(); - let settings_schema = cx.global::().json_schema( - &SettingsJsonSchemaParams { + let theme_names = &ThemeRegistry::global(cx).list_names(); + let icon_theme_names = &ThemeRegistry::global(cx) + .list_icon_themes() + .into_iter() + .map(|icon_theme| icon_theme.name) + .collect::>(); + let settings_schema = cx + .global::() + .json_schema(&SettingsJsonSchemaParams { language_names: &language_names, font_names, - }, - cx, - ); + theme_names, + icon_theme_names, + }); let tasks_schema = task::TaskTemplates::generate_json_schema(); let debug_schema = task::DebugTaskFile::generate_json_schema(&adapter_schemas); diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index 6ed5e5e240201752938ea7e589c9f98b674b919c..64c97863c0ee720f3f9ddd1d466b553b043086e0 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -265,7 +265,7 @@ pub(crate) fn render_ai_setup_page( let fs = ::global(cx); update_settings_file(fs, cx, move |settings, _| { - settings.disable_ai = Some(enabled); + settings.disable_ai = Some(enabled.into()); }); }, ) diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index 7652c633f1e1166f2515e0338cba3323d6017fb3..7e09e21c2311780ec23f24b9616270c4a1f24854 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -2,7 +2,6 @@ use editor::EditorSettings; use gpui::{App, Pixels}; pub use settings::{DockSide, Settings, ShowIndentGuides}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; -use util::MergeFrom; #[derive(Debug, Clone, Copy, PartialEq)] pub struct OutlinePanelSettings { @@ -42,7 +41,7 @@ impl ScrollbarVisibility for OutlinePanelSettings { } impl Settings for OutlinePanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let panel = content.outline_panel.as_ref().unwrap(); Self { button: panel.button.unwrap(), @@ -58,40 +57,12 @@ impl Settings for OutlinePanelSettings { auto_reveal_entries: panel.auto_reveal_entries.unwrap(), auto_fold_dirs: panel.auto_fold_dirs.unwrap(), scrollbar: ScrollbarSettings { - show: panel.scrollbar.unwrap().show.flatten().map(Into::into), + show: panel.scrollbar.unwrap().show.map(Into::into), }, expand_outlines_with_depth: panel.expand_outlines_with_depth.unwrap(), } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(panel) = content.outline_panel.as_ref() else { - return; - }; - - self.button.merge_from(&panel.button); - self.default_width - .merge_from(&panel.default_width.map(Pixels::from)); - self.dock.merge_from(&panel.dock); - self.file_icons.merge_from(&panel.file_icons); - self.folder_icons.merge_from(&panel.folder_icons); - self.git_status.merge_from(&panel.git_status); - self.indent_size.merge_from(&panel.indent_size); - - if let Some(indent_guides) = panel.indent_guides.as_ref() { - self.indent_guides.show.merge_from(&indent_guides.show); - } - - self.auto_reveal_entries - .merge_from(&panel.auto_reveal_entries); - self.auto_fold_dirs.merge_from(&panel.auto_fold_dirs); - - if let Some(scrollbar) = panel.scrollbar.as_ref() - && let Some(show) = scrollbar.show.flatten() - { - self.scrollbar.show = Some(show.into()) - } - } fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index b7d1a75521480353433cf4953960b505201ab3c1..3d270bcb0db13e9c616687ce5d40cbc48bd4cbb9 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -22,7 +22,7 @@ use rpc::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{SettingsContent, SettingsKey, SettingsStore, SettingsUi}; +use settings::{SettingsContent, SettingsStore}; use util::{ResultExt as _, debug_panic}; use crate::ProjectEnvironment; @@ -989,8 +989,7 @@ impl ExternalAgentServer for LocalCustomAgent { pub const GEMINI_NAME: &'static str = "gemini"; pub const CLAUDE_CODE_NAME: &'static str = "claude"; -#[derive(Default, Clone, JsonSchema, Debug, SettingsUi, SettingsKey, PartialEq)] -#[settings_key(key = "agent_servers")] +#[derive(Default, Clone, JsonSchema, Debug, PartialEq)] pub struct AllAgentServersSettings { pub gemini: Option, pub claude: Option, @@ -1063,7 +1062,7 @@ impl From for CustomAgentServerSettings { } impl settings::Settings for AllAgentServersSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let agent_settings = content.agent_servers.clone().unwrap(); Self { gemini: agent_settings.gemini.map(Into::into), @@ -1076,20 +1075,5 @@ impl settings::Settings for AllAgentServersSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(content) = &content.agent_servers else { - return; - }; - if let Some(gemini) = content.gemini.clone() { - self.gemini = Some(gemini.into()) - }; - if let Some(claude) = content.claude.clone() { - self.claude = Some(claude.into()); - } - for (name, config) in content.custom.clone() { - self.custom.insert(name, config.into()); - } - } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 571a7b5db48eaf3660aadf268d61bf1864140066..27fb02f00bd383c0cd55c68cfcd19605d5ab33ae 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -971,23 +971,18 @@ pub enum PulledDiagnostics { /// Whether to disable all AI features in Zed. /// /// Default: false -#[derive(Copy, Clone, Debug, settings::SettingsUi)] +#[derive(Copy, Clone, Debug)] pub struct DisableAiSettings { pub disable_ai: bool, } impl settings::Settings for DisableAiSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { - disable_ai: content.disable_ai.unwrap(), + disable_ai: content.disable_ai.unwrap().0, } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - // If disable_ai is true *in any file*, it is disabled. - self.disable_ai = self.disable_ai || content.disable_ai.unwrap_or(false); - } - fn import_from_vscode( _vscode: &settings::VsCodeSettings, _current: &mut settings::SettingsContent, diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 097224e02cd9c1cbbb51ccc6d3d373d3b5a71f85..c95c20a3352bb067e492874f6f650d38f04671b2 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -21,7 +21,7 @@ pub use settings::DirenvSettings; pub use settings::LspSettings; use settings::{ DapSettingsContent, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, - SettingsStore, SettingsUi, parse_json_with_comments, watch_config_file, + SettingsStore, parse_json_with_comments, watch_config_file, }; use std::{ path::{Path, PathBuf}, @@ -29,7 +29,7 @@ use std::{ time::Duration, }; use task::{DebugTaskFile, TaskTemplates, VsCodeDebugTaskFile, VsCodeTaskFile}; -use util::{MergeFrom as _, ResultExt, serde::default_true}; +use util::{ResultExt, serde::default_true}; use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId}; use crate::{ @@ -189,20 +189,7 @@ impl ContextServerSettings { } } -#[derive( - Clone, - Copy, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Serialize, - Deserialize, - JsonSchema, - SettingsUi, -)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum DiagnosticSeverity { // No diagnostics are shown. Off, @@ -444,7 +431,7 @@ pub struct LspPullDiagnosticsSettings { } impl Settings for ProjectSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let project = &content.project.clone(); let diagnostics = content.diagnostics.as_ref().unwrap(); let lsp_pull_diagnostics = diagnostics.lsp_pull_diagnostics.as_ref().unwrap(); @@ -523,118 +510,6 @@ impl Settings for ProjectSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let project = &content.project; - self.context_servers.extend( - project - .context_servers - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())), - ); - self.dap.extend( - project - .dap - .clone() - .into_iter() - .map(|(key, value)| (DebugAdapterName(key.into()), DapSettings::from(value))), - ); - if let Some(diagnostics) = content.diagnostics.as_ref() { - if let Some(inline) = &diagnostics.inline { - self.diagnostics.inline.enabled.merge_from(&inline.enabled); - self.diagnostics - .inline - .update_debounce_ms - .merge_from(&inline.update_debounce_ms); - self.diagnostics.inline.padding.merge_from(&inline.padding); - self.diagnostics - .inline - .min_column - .merge_from(&inline.min_column); - if let Some(max_severity) = inline.max_severity { - self.diagnostics.inline.max_severity = Some(max_severity.into()) - } - } - - self.diagnostics.button.merge_from(&diagnostics.button); - self.diagnostics - .include_warnings - .merge_from(&diagnostics.include_warnings); - if let Some(pull_diagnostics) = &diagnostics.lsp_pull_diagnostics { - self.diagnostics - .lsp_pull_diagnostics - .enabled - .merge_from(&pull_diagnostics.enabled); - self.diagnostics - .lsp_pull_diagnostics - .debounce_ms - .merge_from(&pull_diagnostics.debounce_ms); - } - } - if let Some(git) = content.git.as_ref() { - if let Some(branch_picker) = git.branch_picker.as_ref() { - self.git - .branch_picker - .show_author_name - .merge_from(&branch_picker.show_author_name); - } - if let Some(inline_blame) = git.inline_blame.as_ref() { - self.git - .inline_blame - .enabled - .merge_from(&inline_blame.enabled); - self.git - .inline_blame - .delay_ms - .merge_from(&inline_blame.delay_ms.map(std::time::Duration::from_millis)); - self.git - .inline_blame - .padding - .merge_from(&inline_blame.padding); - self.git - .inline_blame - .min_column - .merge_from(&inline_blame.min_column); - self.git - .inline_blame - .show_commit_summary - .merge_from(&inline_blame.show_commit_summary); - } - self.git.git_gutter.merge_from(&git.git_gutter); - self.git.hunk_style.merge_from(&git.hunk_style); - if let Some(debounce) = git.gutter_debounce { - self.git.gutter_debounce = Some(debounce); - } - } - self.global_lsp_settings.button.merge_from( - &content - .global_lsp_settings - .as_ref() - .and_then(|settings| settings.button), - ); - self.load_direnv - .merge_from(&content.project.load_direnv.clone()); - - for (key, value) in content.project.lsp.clone() { - self.lsp.insert(LanguageServerName(key.into()), value); - } - - if let Some(node) = content.node.as_ref() { - self.node - .ignore_system_version - .merge_from(&node.ignore_system_version); - if let Some(path) = node.path.clone() { - self.node.path = Some(path); - } - if let Some(npm_path) = node.npm_path.clone() { - self.node.npm_path = Some(npm_path); - } - } - self.session - .restore_unsaved_buffers - .merge_from(&content.session.and_then(|s| s.restore_unsaved_buffers)); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index b2646477697118e14abbbe6647a284d5a19a8866..0f5f2e5b53a7a856b9e6bff136a1b1cc5af3e293 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -10,7 +10,6 @@ use ui::{ px, scrollbars::{ScrollbarVisibility, ShowScrollbar}, }; -use util::MergeFrom; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct ProjectPanelSettings { @@ -56,7 +55,7 @@ impl ScrollbarVisibility for ProjectPanelSettings { } impl Settings for ProjectPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let project_panel = content.project_panel.clone().unwrap(); Self { button: project_panel.button.unwrap(), @@ -76,12 +75,7 @@ impl Settings for ProjectPanelSettings { auto_fold_dirs: project_panel.auto_fold_dirs.unwrap(), starts_open: project_panel.starts_open.unwrap(), scrollbar: ScrollbarSettings { - show: project_panel - .scrollbar - .unwrap() - .show - .flatten() - .map(Into::into), + show: project_panel.scrollbar.unwrap().show.map(Into::into), }, show_diagnostics: project_panel.show_diagnostics.unwrap(), hide_root: project_panel.hide_root.unwrap(), @@ -89,47 +83,6 @@ impl Settings for ProjectPanelSettings { } } - fn refine(&mut self, content: &SettingsContent, _cx: &mut ui::App) { - let Some(project_panel) = content.project_panel.as_ref() else { - return; - }; - self.button.merge_from(&project_panel.button); - self.hide_gitignore - .merge_from(&project_panel.hide_gitignore); - self.default_width - .merge_from(&project_panel.default_width.map(px)); - self.dock.merge_from(&project_panel.dock); - self.entry_spacing.merge_from(&project_panel.entry_spacing); - self.file_icons.merge_from(&project_panel.file_icons); - self.folder_icons.merge_from(&project_panel.folder_icons); - self.git_status.merge_from(&project_panel.git_status); - self.indent_size.merge_from(&project_panel.indent_size); - self.sticky_scroll.merge_from(&project_panel.sticky_scroll); - self.auto_reveal_entries - .merge_from(&project_panel.auto_reveal_entries); - self.auto_fold_dirs - .merge_from(&project_panel.auto_fold_dirs); - self.starts_open.merge_from(&project_panel.starts_open); - self.show_diagnostics - .merge_from(&project_panel.show_diagnostics); - self.hide_root.merge_from(&project_panel.hide_root); - self.drag_and_drop.merge_from(&project_panel.drag_and_drop); - if let Some(show) = project_panel - .indent_guides - .as_ref() - .and_then(|indent| indent.show) - { - self.indent_guides.show = show; - } - if let Some(show) = project_panel - .scrollbar - .as_ref() - .and_then(|scrollbar| scrollbar.show) - { - self.scrollbar.show = show.map(Into::into) - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(hide_gitignore) = vscode.read_bool("explorer.excludeGitIgnore") { current.project_panel.get_or_insert_default().hide_gitignore = Some(hide_gitignore); diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index d3888c9878840d78f43f77e8437311a237822a81..fba7b122dd7842edfcb5f66054040e378acbfc70 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -19,29 +19,28 @@ use remote::{ SshConnectionOptions, }; pub use settings::SshConnection; -use settings::{Settings, WslConnection}; +use settings::{ExtendingVec, Settings, WslConnection}; use theme::ThemeSettings; use ui::{ ActiveTheme, Color, CommonAnimationExt, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, Styled, Window, prelude::*, }; -use util::MergeFrom; use workspace::{AppState, ModalView, Workspace}; pub struct SshSettings { - pub ssh_connections: Vec, - pub wsl_connections: Vec, + pub ssh_connections: ExtendingVec, + pub wsl_connections: ExtendingVec, /// Whether to read ~/.ssh/config for ssh connection sources. pub read_ssh_config: bool, } impl SshSettings { pub fn ssh_connections(&self) -> impl Iterator + use<> { - self.ssh_connections.clone().into_iter() + self.ssh_connections.clone().0.into_iter() } pub fn wsl_connections(&self) -> impl Iterator + use<> { - self.wsl_connections.clone().into_iter() + self.wsl_connections.clone().0.into_iter() } pub fn fill_connection_options_from_settings(&self, options: &mut SshConnectionOptions) { @@ -104,25 +103,14 @@ impl From for Connection { } impl Settings for SshSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let remote = &content.remote; Self { - ssh_connections: remote.ssh_connections.clone().unwrap_or_default(), - wsl_connections: remote.wsl_connections.clone().unwrap_or_default(), + ssh_connections: remote.ssh_connections.clone().unwrap_or_default().into(), + wsl_connections: remote.wsl_connections.clone().unwrap_or_default().into(), read_ssh_config: remote.read_ssh_config.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - if let Some(ssh_connections) = content.remote.ssh_connections.clone() { - self.ssh_connections.extend(ssh_connections) - } - if let Some(wsl_connections) = content.remote.wsl_connections.clone() { - self.wsl_connections.extend(wsl_connections) - } - self.read_ssh_config - .merge_from(&content.remote.read_ssh_config); - } } pub struct RemoteConnectionPrompt { diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 7b19c887b0c36490a83ef7da35f7ecbd533fcb16..989a054fcad948c3acdea2ffcd462eee9bf2fcc6 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1885,7 +1885,7 @@ impl RemoteServerProjects { let ssh_settings = SshSettings::get_global(cx); let mut should_rebuild = false; - let ssh_connections_changed = ssh_settings.ssh_connections.iter().ne(state + let ssh_connections_changed = ssh_settings.ssh_connections.0.iter().ne(state .servers .iter() .filter_map(|server| match server { @@ -1896,7 +1896,7 @@ impl RemoteServerProjects { _ => None, })); - let wsl_connections_changed = ssh_settings.wsl_connections.iter().ne(state + let wsl_connections_changed = ssh_settings.wsl_connections.0.iter().ne(state .servers .iter() .filter_map(|server| match server { diff --git a/crates/repl/src/jupyter_settings.rs b/crates/repl/src/jupyter_settings.rs index 8a468e9ec65feb8431222bd6b9a33c8bbd9efaf3..830e6032147cbc96bb46b240651167402db427f1 100644 --- a/crates/repl/src/jupyter_settings.rs +++ b/crates/repl/src/jupyter_settings.rs @@ -19,19 +19,10 @@ impl JupyterSettings { } impl Settings for JupyterSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let jupyter = content.editor.jupyter.clone().unwrap(); Self { kernel_selections: jupyter.kernel_selections.unwrap_or_default(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(jupyter) = content.editor.jupyter.as_ref() else { - return; - }; - if let Some(kernel_selections) = jupyter.kernel_selections.clone() { - self.kernel_selections.extend(kernel_selections) - } - } } diff --git a/crates/repl/src/repl_settings.rs b/crates/repl/src/repl_settings.rs index 1c9ceeecfc19bec40243193e9d4ea21a2e266e45..ee18c89d67a0f1da55acaee89cfaa0b03dc80f87 100644 --- a/crates/repl/src/repl_settings.rs +++ b/crates/repl/src/repl_settings.rs @@ -1,6 +1,5 @@ use gpui::App; use settings::Settings; -use util::MergeFrom; /// Settings for configuring REPL display and behavior. #[derive(Clone, Debug)] @@ -18,7 +17,7 @@ pub struct ReplSettings { } impl Settings for ReplSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let repl = content.repl.as_ref().unwrap(); Self { @@ -26,13 +25,4 @@ impl Settings for ReplSettings { max_columns: repl.max_columns.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(repl) = content.repl.as_ref() else { - return; - }; - - self.max_columns.merge_from(&repl.max_columns); - self.max_lines.merge_from(&repl.max_lines); - } } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 062af3d23926b5bfdc0e1f7239859b6bbfd7efdd..4790df4d95f676b7e7170e86b6d05976cbd3c69a 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -30,7 +30,7 @@ rust-embed.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true -settings_ui_macros.workspace = true +settings_macros = { path = "../settings_macros" } serde_json_lenient.workspace = true serde_repr.workspace = true serde_path_to_error.workspace = true diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index 858a3e758ff7d0aa6ec045197316f027913b49d6..1b41dc0d4f7db6a907de9586d5c4ceb796d00165 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -2,21 +2,17 @@ use std::fmt::{Display, Formatter}; use crate::{ self as settings, - settings_content::{self, BaseKeymapContent, SettingsContent}, + settings_content::{BaseKeymapContent, SettingsContent}, }; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; use settings::{Settings, VsCodeSettings}; -use settings_ui_macros::{SettingsKey, SettingsUi}; /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// /// Default: VSCode -#[derive( - Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default, SettingsUi, -)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] pub enum BaseKeymap { #[default] VSCode, @@ -134,37 +130,11 @@ impl BaseKeymap { } } -#[derive( - Copy, - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialEq, - Eq, - Default, - SettingsUi, - SettingsKey, -)] -// extracted so that it can be an option, and still work with derive(SettingsUi) -#[settings_key(None)] -#[skip_serializing_none] -pub struct BaseKeymapSetting { - pub base_keymap: Option, -} - impl Settings for BaseKeymap { - fn from_defaults(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Self { s.base_keymap.unwrap().into() } - fn refine(&mut self, s: &settings_content::SettingsContent, _cx: &mut App) { - if let Some(base_keymap) = s.base_keymap { - *self = base_keymap.into(); - }; - } - fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut SettingsContent) { current.base_keymap = Some(BaseKeymapContent::VSCode); } diff --git a/crates/settings/src/merge_from.rs b/crates/settings/src/merge_from.rs new file mode 100644 index 0000000000000000000000000000000000000000..11c0785bcb466e26de956475fb5bd4f9821c2790 --- /dev/null +++ b/crates/settings/src/merge_from.rs @@ -0,0 +1,164 @@ +use std::rc::Rc; + +/// Trait for recursively merging settings structures. +/// +/// This trait allows settings objects to be merged from optional sources, +/// where `None` values are ignored and `Some` values override existing values. +/// +/// HashMaps, structs and similar types are merged by combining their contents key-wise, +/// but all other types (including Vecs) are last-write-wins. +/// (Though see also ExtendingVec and SaturatingBool) +#[allow(unused)] +pub trait MergeFrom { + /// Merge from an optional source of the same type. + /// If `other` is `None`, no changes are made. + /// If `other` is `Some(value)`, fields from `value` are merged into `self`. + fn merge_from(&mut self, other: Option<&Self>); +} + +macro_rules! merge_from_overwrites { + ($($type:ty),+) => { + $( + impl MergeFrom for $type { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(value) = other { + *self = value.clone(); + } + } + } + )+ + } +} + +merge_from_overwrites!( + u16, + u32, + u64, + usize, + i16, + i32, + i64, + bool, + f64, + f32, + std::num::NonZeroUsize, + std::num::NonZeroU32, + String, + std::sync::Arc, + gpui::SharedString, + std::path::PathBuf, + gpui::Modifiers, + gpui::FontFeatures +); + +impl MergeFrom for Vec { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(other) = other { + *self = other.clone() + } + } +} + +// Implementations for collections that extend/merge their contents +impl MergeFrom for collections::HashMap +where + K: Clone + std::hash::Hash + Eq, + V: Clone + MergeFrom, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for (k, v) in other { + if let Some(existing) = self.get_mut(k) { + existing.merge_from(Some(v)); + } else { + self.insert(k.clone(), v.clone()); + } + } + } +} + +impl MergeFrom for collections::BTreeMap +where + K: Clone + std::hash::Hash + Eq + Ord, + V: Clone + MergeFrom, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for (k, v) in other { + if let Some(existing) = self.get_mut(k) { + existing.merge_from(Some(v)); + } else { + self.insert(k.clone(), v.clone()); + } + } + } +} + +impl MergeFrom for collections::IndexMap +where + K: std::hash::Hash + Eq + Clone, + // Q: ?Sized + std::hash::Hash + collections::Equivalent + Eq, + V: Clone + MergeFrom, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for (k, v) in other { + if let Some(existing) = self.get_mut(k) { + existing.merge_from(Some(v)); + } else { + self.insert(k.clone(), v.clone()); + } + } + } +} + +impl MergeFrom for collections::BTreeSet +where + T: Clone + Ord, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for item in other { + self.insert(item.clone()); + } + } +} + +impl MergeFrom for collections::HashSet +where + T: Clone + std::hash::Hash + Eq, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for item in other { + self.insert(item.clone()); + } + } +} + +impl MergeFrom for serde_json::Value { + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + match (self, other) { + (serde_json::Value::Object(this), serde_json::Value::Object(other)) => { + for (k, v) in other { + if let Some(existing) = this.get_mut(k) { + existing.merge_from(other.get(k)); + } else { + this.insert(k.clone(), v.clone()); + } + } + } + (this, other) => *this = other.clone(), + } + } +} + +impl MergeFrom for Rc { + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + let mut this: T = self.as_ref().clone(); + this.merge_from(Some(other.as_ref())); + *self = Rc::new(this) + } +} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index bb242aaac28e2910d72982eeb7e92c0ea8f6c896..4b53b8b6ea49bc45a059776e3f863d2dd2961651 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,6 +1,7 @@ mod base_keymap_setting; mod editable_setting_control; mod keymap_file; +pub mod merge_from; mod settings_content; mod settings_file; mod settings_json; @@ -27,8 +28,7 @@ pub use settings_store::{ InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, SettingsStore, }; pub use settings_ui_core::*; -// Re-export the derive macro -pub use settings_ui_macros::{SettingsKey, SettingsUi}; + pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; #[derive(Clone, Debug, PartialEq)] diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 542cc6c57b08389f85c71ab0ef8dfcdd1f78c189..2ef42d8ebd730343f749d3e2e48055a2d02819ad 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -22,15 +22,16 @@ use release_channel::ReleaseChannel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::collections::BTreeSet; use std::env; use std::sync::Arc; pub use util::serde::default_true; -use crate::ActiveSettingsProfileName; +use crate::{ActiveSettingsProfileName, merge_from}; #[skip_serializing_none] -#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct SettingsContent { #[serde(flatten)] pub project: ProjectSettingsContent, @@ -153,7 +154,7 @@ pub struct SettingsContent { /// Whether to disable all AI features in Zed. /// /// Default: false - pub disable_ai: Option, + pub disable_ai: Option, /// Settings related to Vim mode in Zed. pub vim: Option, @@ -166,14 +167,14 @@ impl SettingsContent { } #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ServerSettingsContent { #[serde(flatten)] pub project: ProjectSettingsContent, } #[skip_serializing_none] -#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct UserSettingsContent { #[serde(flatten)] pub content: Box, @@ -225,7 +226,9 @@ impl UserSettingsContent { /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// /// Default: VSCode -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default, +)] pub enum BaseKeymapContent { #[default] VSCode, @@ -239,7 +242,7 @@ pub enum BaseKeymapContent { } #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct TitleBarSettingsContent { /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen". /// @@ -275,7 +278,7 @@ pub struct TitleBarSettingsContent { pub show_menus: Option, } -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] #[serde(rename_all = "snake_case")] pub enum TitleBarVisibility { Always, @@ -285,7 +288,7 @@ pub enum TitleBarVisibility { /// Configuration of audio in Zed. #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct AudioSettingsContent { /// Opt into the new audio system. #[serde(rename = "experimental.rodio_audio", default)] @@ -307,7 +310,7 @@ pub struct AudioSettingsContent { /// Control what info is collected by Zed. #[skip_serializing_none] -#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug, MergeFrom)] pub struct TelemetrySettingsContent { /// Send debug info like crash reports. /// @@ -320,7 +323,7 @@ pub struct TelemetrySettingsContent { } #[skip_serializing_none] -#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone)] +#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone, MergeFrom)] pub struct DebuggerSettingsContent { /// Determines the stepping granularity. /// @@ -353,7 +356,9 @@ pub struct DebuggerSettingsContent { } /// The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`. -#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Deserialize, Serialize, JsonSchema)] +#[derive( + PartialEq, Eq, Debug, Hash, Clone, Copy, Deserialize, Serialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum SteppingGranularity { /// The step should allow the program to run until the current statement has finished executing. @@ -366,7 +371,7 @@ pub enum SteppingGranularity { Instruction, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DockPosition { Left, @@ -376,7 +381,7 @@ pub enum DockPosition { /// Settings for slash commands. #[skip_serializing_none] -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct SlashCommandSettings { /// Settings for the `/cargo-workspace` slash command. pub cargo_workspace: Option, @@ -384,7 +389,7 @@ pub struct SlashCommandSettings { /// Settings for the `/cargo-workspace` slash command. #[skip_serializing_none] -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct CargoWorkspaceCommandSettings { /// Whether `/cargo-workspace` is enabled. pub enabled: Option, @@ -392,7 +397,7 @@ pub struct CargoWorkspaceCommandSettings { /// Configuration of voice calls in Zed. #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. /// @@ -406,7 +411,7 @@ pub struct CallSettingsContent { } #[skip_serializing_none] -#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone, JsonSchema, MergeFrom)] pub struct ExtensionSettingsContent { /// The extensions that should be automatically installed by Zed. /// @@ -421,7 +426,7 @@ pub struct ExtensionSettingsContent { } #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct GitPanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -462,7 +467,9 @@ pub struct GitPanelSettingsContent { pub collapse_untracked_diff: Option, } -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum StatusStyle { #[default] @@ -471,13 +478,13 @@ pub enum StatusStyle { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ScrollbarSettings { pub show: Option, } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct NotificationPanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -494,7 +501,7 @@ pub struct NotificationPanelSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct PanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -511,7 +518,7 @@ pub struct PanelSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct MessageEditorSettings { /// Whether to automatically replace emoji shortcodes with emoji characters. /// For example: typing `:wave:` gets replaced with `👋`. @@ -521,7 +528,7 @@ pub struct MessageEditorSettings { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct FileFinderSettingsContent { /// Whether to show file icons in the file finder. /// @@ -549,10 +556,12 @@ pub struct FileFinderSettingsContent { /// /// Default: None /// todo() -> Change this type to an enum - pub include_ignored: Option>, + pub include_ignored: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "lowercase")] pub enum FileFinderWidthContent { #[default] @@ -564,7 +573,7 @@ pub enum FileFinderWidthContent { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)] pub struct VimSettingsContent { pub default_mode: Option, pub toggle_relative_line_numbers: Option, @@ -575,7 +584,7 @@ pub struct VimSettingsContent { pub cursor_shape: Option, } -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)] +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)] #[serde(rename_all = "snake_case")] pub enum ModeContent { #[default] @@ -585,7 +594,7 @@ pub enum ModeContent { } /// Controls when to use system clipboard. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum UseSystemClipboard { /// Don't use system clipboard. @@ -598,7 +607,7 @@ pub enum UseSystemClipboard { /// The settings for cursor shape. #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] pub struct CursorShapeSettings { /// Cursor shape for the normal mode. /// @@ -620,7 +629,7 @@ pub struct CursorShapeSettings { /// Settings specific to journaling #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct JournalSettingsContent { /// The path of the directory where journal entries are stored. /// @@ -632,7 +641,7 @@ pub struct JournalSettingsContent { pub hour_format: Option, } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub enum HourFormat { #[default] @@ -641,7 +650,7 @@ pub enum HourFormat { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct OutlinePanelSettingsContent { /// Whether to show the outline panel button in the status bar. /// @@ -695,14 +704,14 @@ pub struct OutlinePanelSettingsContent { pub expand_outlines_with_depth: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Copy, PartialEq)] #[serde(rename_all = "snake_case")] pub enum DockSide { Left, Right, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ShowIndentGuides { Always, @@ -710,13 +719,13 @@ pub enum ShowIndentGuides { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct IndentGuidesSettingsContent { /// When to show the scrollbar in the outline panel. pub show: Option, } -#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, Deserialize, Serialize)] +#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, MergeFrom, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum LineIndicatorFormat { Short, @@ -726,7 +735,7 @@ pub enum LineIndicatorFormat { /// The settings for the image viewer. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] pub struct ImageViewerSettingsContent { /// The unit to use for displaying image file sizes. /// @@ -735,7 +744,7 @@ pub struct ImageViewerSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ImageFileSizeUnit { /// Displays file size in binary units (e.g., KiB, MiB). @@ -746,7 +755,7 @@ pub enum ImageFileSizeUnit { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct RemoteSettingsContent { pub ssh_connections: Option>, pub wsl_connections: Option>, @@ -754,7 +763,7 @@ pub struct RemoteSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct SshConnection { pub host: SharedString, pub username: Option, @@ -774,7 +783,7 @@ pub struct SshConnection { pub port_forwards: Option>, } -#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Debug)] pub struct WslConnection { pub distro_name: SharedString, pub user: Option, @@ -791,7 +800,7 @@ pub struct SshProject { } #[skip_serializing_none] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)] pub struct SshPortForwardOption { #[serde(skip_serializing_if = "Option::is_none")] pub local_host: Option, @@ -803,7 +812,7 @@ pub struct SshPortForwardOption { /// Settings for configuring REPL display and behavior. #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ReplSettingsContent { /// Maximum number of lines to keep in REPL's scrollback buffer. /// Clamped with [4, 256] range. @@ -816,3 +825,42 @@ pub struct ReplSettingsContent { /// Default: 128 pub max_columns: Option, } + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct ExtendingVec(pub Vec); + +impl Into> for ExtendingVec { + fn into(self) -> Vec { + self.0 + } +} +impl From> for ExtendingVec { + fn from(vec: Vec) -> Self { + ExtendingVec(vec) + } +} + +impl merge_from::MergeFrom for ExtendingVec { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(other) = other { + self.0.extend_from_slice(other.0.as_slice()); + } + } +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct SaturatingBool(pub bool); + +impl From for SaturatingBool { + fn from(value: bool) -> Self { + SaturatingBool(value) + } +} + +impl merge_from::MergeFrom for SaturatingBool { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(other) = other { + self.0 |= other.0 + } + } +} diff --git a/crates/settings/src/settings_content/agent.rs b/crates/settings/src/settings_content/agent.rs index 0dd9a78343ec6737a7b98a8ef9c755783c1e6f33..88b27615aab5b2ddef1a55678897fff0beb114a1 100644 --- a/crates/settings/src/settings_content/agent.rs +++ b/crates/settings/src/settings_content/agent.rs @@ -3,12 +3,13 @@ use gpui::SharedString; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::{borrow::Cow, path::PathBuf, sync::Arc}; use crate::DockPosition; #[skip_serializing_none] -#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug, Default)] +#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)] pub struct AgentSettingsContent { /// Whether the Agent is enabled. /// @@ -168,7 +169,7 @@ impl AgentSettingsContent { } #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct AgentProfileContent { pub name: Arc, #[serde(default)] @@ -180,12 +181,12 @@ pub struct AgentProfileContent { } #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ContextServerPresetContent { pub tools: IndexMap, bool>, } -#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum DefaultAgentView { #[default] @@ -193,7 +194,7 @@ pub enum DefaultAgentView { TextThread, } -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub enum NotifyWhenAgentWaiting { #[default] @@ -203,13 +204,13 @@ pub enum NotifyWhenAgentWaiting { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct LanguageModelSelection { pub provider: LanguageModelProviderSetting, pub model: String, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] #[serde(rename_all = "snake_case")] pub enum CompletionMode { #[default] @@ -219,14 +220,14 @@ pub enum CompletionMode { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct LanguageModelParameters { pub provider: Option, pub model: Option, pub temperature: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, MergeFrom)] pub struct LanguageModelProviderSetting(pub String); impl JsonSchema for LanguageModelProviderSetting { @@ -277,7 +278,7 @@ impl From<&str> for LanguageModelProviderSetting { } #[skip_serializing_none] -#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, Debug)] +#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug)] pub struct AllAgentServersSettings { pub gemini: Option, pub claude: Option, @@ -288,7 +289,7 @@ pub struct AllAgentServersSettings { } #[skip_serializing_none] -#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct BuiltinAgentServerSettings { /// Absolute path to a binary to be used when launching this agent. /// @@ -320,7 +321,7 @@ pub struct BuiltinAgentServerSettings { } #[skip_serializing_none] -#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct CustomAgentServerSettings { #[serde(rename = "command")] pub path: PathBuf, diff --git a/crates/settings/src/settings_content/editor.rs b/crates/settings/src/settings_content/editor.rs index d5984d4213ea053589e51abc91b5ff9f1e7268f1..a443cf3faccb8755c6743acbe8ee11b0471a3e7f 100644 --- a/crates/settings/src/settings_content/editor.rs +++ b/crates/settings/src/settings_content/editor.rs @@ -4,11 +4,12 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use crate::{DiagnosticSeverityContent, ShowScrollbar}; #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct EditorSettingsContent { /// Whether the cursor blinks in the editor. /// @@ -194,7 +195,7 @@ pub struct EditorSettingsContent { // Status bar related settings #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct StatusBarContent { /// Whether to display the active language button in the status bar. /// @@ -208,7 +209,7 @@ pub struct StatusBarContent { // Toolbar related settings #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ToolbarContent { /// Whether to display breadcrumbs in the editor toolbar. /// @@ -235,7 +236,7 @@ pub struct ToolbarContent { /// Scrollbar related settings #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] pub struct ScrollbarContent { /// When to show the scrollbar in the editor. /// @@ -271,7 +272,7 @@ pub struct ScrollbarContent { /// Minimap related settings #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct MinimapContent { /// When to show the minimap in the editor. /// @@ -296,7 +297,7 @@ pub struct MinimapContent { /// How to highlight the current line in the minimap. /// /// Default: inherits editor line highlights setting - pub current_line_highlight: Option>, + pub current_line_highlight: Option, /// Maximum number of columns to display in the minimap. /// @@ -306,7 +307,7 @@ pub struct MinimapContent { /// Forcefully enable or disable the scrollbar for each axis #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] pub struct ScrollbarAxesContent { /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. /// @@ -321,7 +322,7 @@ pub struct ScrollbarAxesContent { /// Gutter related settings #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct GutterContent { /// Whether to show line numbers in the gutter. /// @@ -346,7 +347,9 @@ pub struct GutterContent { } /// How to render LSP `textDocument/documentColor` colors in the editor. -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum DocumentColorsRenderMode { /// Do not query and render document colors. @@ -360,7 +363,7 @@ pub enum DocumentColorsRenderMode { Background, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum CurrentLineHighlight { // Don't highlight the current line. @@ -374,7 +377,7 @@ pub enum CurrentLineHighlight { } /// When to populate a new search's query based on the text under the cursor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum SeedQuerySetting { /// Always populate the search query with the word under the cursor. @@ -386,7 +389,9 @@ pub enum SeedQuerySetting { } /// What to do when multibuffer is double clicked in some of its excerpts (parts of singleton buffers). -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum DoubleClickInMultibuffer { /// Behave as a regular buffer and select the whole word. @@ -400,7 +405,9 @@ pub enum DoubleClickInMultibuffer { /// When to show the minimap thumb. /// /// Default: always -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum MinimapThumb { /// Show the minimap thumb only when the mouse is hovering over the minimap. @@ -413,7 +420,9 @@ pub enum MinimapThumb { /// Defines the border style for the minimap's scrollbar thumb. /// /// Default: left_open -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum MinimapThumbBorder { /// Displays a border on all sides of the thumb. @@ -432,7 +441,7 @@ pub enum MinimapThumbBorder { /// Which diagnostic indicators to show in the scrollbar. /// /// Default: all -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ScrollbarDiagnostics { /// Show all diagnostic levels: hint, information, warnings, error. @@ -450,7 +459,7 @@ pub enum ScrollbarDiagnostics { /// The key to use for adding multiple cursors /// /// Default: alt -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum MultiCursorModifier { Alt, @@ -461,7 +470,7 @@ pub enum MultiCursorModifier { /// Whether the editor will scroll beyond the last line. /// /// Default: one_page -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ScrollBeyondLastLine { /// The editor will not scroll beyond the last line. @@ -475,7 +484,9 @@ pub enum ScrollBeyondLastLine { } /// The shape of a selection cursor. -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum CursorShape { /// A vertical bar @@ -490,7 +501,9 @@ pub enum CursorShape { } /// What to do when go to definition yields no results. -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum GoToDefinitionFallback { /// Disables the fallback. @@ -503,7 +516,9 @@ pub enum GoToDefinitionFallback { /// Determines when the mouse cursor should be hidden in an editor or input box. /// /// Default: on_typing_and_movement -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum HideMouseMode { /// Never hide the mouse cursor @@ -518,7 +533,9 @@ pub enum HideMouseMode { /// Determines how snippets are sorted relative to other completion items. /// /// Default: inline -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum SnippetSortOrder { /// Place snippets at the top of the completion list @@ -534,7 +551,7 @@ pub enum SnippetSortOrder { /// Default options for buffer and project search items. #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct SearchSettingsContent { /// Whether to show the project search button in the status bar. pub button: Option, @@ -545,7 +562,7 @@ pub struct SearchSettingsContent { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct JupyterContent { /// Whether the Jupyter feature is enabled. @@ -561,7 +578,7 @@ pub struct JupyterContent { /// Whether to allow drag and drop text selection in buffer. #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct DragAndDropSelectionContent { /// When true, enables drag and drop text selection in buffer. /// @@ -577,7 +594,9 @@ pub struct DragAndDropSelectionContent { /// When to show the minimap in the editor. /// /// Default: never -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum ShowMinimap { /// Follow the visibility of the scrollbar. @@ -592,7 +611,9 @@ pub enum ShowMinimap { /// Where to show the minimap in the editor. /// /// Default: all_editors -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum DisplayIn { /// Show on all open editors. diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index de8cf378d1e25f7e84047d788816572ffd97d25c..ef435d638359825128729d0c024cde8e5c5613c8 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -8,10 +8,10 @@ use serde::{ de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor}, }; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::sync::Arc; -use util::schemars::replace_subschema; -use crate::ParameterizedJsonSchema; +use crate::{ExtendingVec, merge_from}; #[skip_serializing_none] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] @@ -31,12 +31,50 @@ pub struct AllLanguageSettingsContent { /// Settings for associating file extensions and filenames /// with languages. #[serde(default)] - pub file_types: HashMap, Vec>, + pub file_types: HashMap, ExtendingVec>, +} + +fn merge_option(this: &mut Option, other: Option<&T>) { + let Some(other) = other else { return }; + if let Some(this) = this { + this.merge_from(Some(other)); + } else { + this.replace(other.clone()); + } +} + +impl merge_from::MergeFrom for AllLanguageSettingsContent { + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + self.file_types.merge_from(Some(&other.file_types)); + merge_option(&mut self.features, other.features.as_ref()); + merge_option(&mut self.edit_predictions, other.edit_predictions.as_ref()); + + // A user's global settings override the default global settings and + // all default language-specific settings. + // + self.defaults.merge_from(Some(&other.defaults)); + for language_settings in self.languages.0.values_mut() { + language_settings.merge_from(Some(&other.defaults)); + } + + // A user's language-specific settings override default language-specific settings. + for (language_name, user_language_settings) in &other.languages.0 { + if let Some(existing) = self.languages.0.get_mut(language_name) { + existing.merge_from(Some(&user_language_settings)); + } else { + let mut new_settings = self.defaults.clone(); + new_settings.merge_from(Some(&user_language_settings)); + + self.languages.0.insert(language_name.clone(), new_settings); + } + } + } } /// The settings for enabling/disabling features. #[skip_serializing_none] -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct FeaturesContent { /// Determines which edit prediction provider to use. @@ -44,7 +82,9 @@ pub struct FeaturesContent { } /// The provider that supplies edit predictions. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum EditPredictionProvider { None, @@ -56,7 +96,7 @@ pub enum EditPredictionProvider { /// The contents of the edit prediction settings. #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct EditPredictionSettingsContent { /// A list of globs representing files that edit predictions should be disabled for. /// This list adds to a pre-existing, sensible default set of globs. @@ -73,7 +113,7 @@ pub struct EditPredictionSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct CopilotSettingsContent { /// HTTP/HTTPS proxy to use for Copilot. /// @@ -90,7 +130,9 @@ pub struct CopilotSettingsContent { } /// The mode in which edit predictions should be displayed. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum EditPredictionsMode { /// If provider supports it, display inline when holding modifier key (e.g., alt). @@ -104,7 +146,7 @@ pub enum EditPredictionsMode { } /// Controls the soft-wrapping behavior in the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum SoftWrap { /// Prefer a single line generally, unless an overly long line is encountered. @@ -122,7 +164,7 @@ pub enum SoftWrap { /// The settings for a particular language. #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageSettingsContent { /// How many columns a tab should occupy. /// @@ -289,7 +331,7 @@ pub struct LanguageSettingsContent { } /// Controls how whitespace should be displayedin the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ShowWhitespaceSetting { /// Draw whitespace only for the selected text. @@ -310,7 +352,7 @@ pub enum ShowWhitespaceSetting { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct WhitespaceMap { pub space: Option, pub tab: Option, @@ -331,7 +373,7 @@ impl WhitespaceMap { } /// The behavior of `editor::Rewrap`. -#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum RewrapBehavior { /// Only rewrap within comments. @@ -344,7 +386,7 @@ pub enum RewrapBehavior { } #[skip_serializing_none] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct JsxTagAutoCloseSettingsContent { /// Enables or disables auto-closing of JSX tags. pub enabled: Option, @@ -352,7 +394,7 @@ pub struct JsxTagAutoCloseSettingsContent { /// The settings for inlay hints. #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct InlayHintSettingsContent { /// Global switch to toggle hints on and off. /// @@ -434,7 +476,7 @@ impl InlayHintKind { /// Controls how completions are processed for this language. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub struct CompletionSettingsContent { /// Controls how words are completed. @@ -462,7 +504,7 @@ pub struct CompletionSettingsContent { pub lsp_insert_mode: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum LspInsertMode { /// Replaces text before the cursor, using the `insert` range described in the LSP specification. @@ -478,7 +520,7 @@ pub enum LspInsertMode { } /// Controls how document's words are completed. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum WordsCompletionMode { /// Always fetch document's words for completions along with LSP completions. @@ -495,7 +537,7 @@ pub enum WordsCompletionMode { /// and configure default Prettier, used when no project-level Prettier installation is found. /// Prettier formatting is disabled by default. #[skip_serializing_none] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct PrettierSettingsContent { /// Enables or disables formatting with Prettier for a given language. pub allowed: Option, @@ -515,7 +557,7 @@ pub struct PrettierSettingsContent { } /// Controls the behavior of formatting files when they are saved. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, MergeFrom)] pub enum FormatOnSave { /// Files should be formatted on save. On, @@ -614,7 +656,7 @@ impl<'de> Deserialize<'de> for FormatOnSave { } /// Controls which formatter should be used when formatting code. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, MergeFrom)] pub enum SelectedFormatter { /// Format files using Zed's Prettier integration (if applicable), /// or falling back to formatting via language server. @@ -710,7 +752,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter { } /// Controls which formatters should be used when formatting code. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(untagged)] pub enum FormatterList { Single(Formatter), @@ -733,7 +775,7 @@ impl AsRef<[Formatter]> for FormatterList { } /// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration. -#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum Formatter { /// Format code using the current language server. @@ -754,7 +796,7 @@ pub enum Formatter { /// The settings for indent guides. #[skip_serializing_none] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct IndentGuideSettingsContent { /// Whether to display indent guides in the editor. /// @@ -780,7 +822,7 @@ pub struct IndentGuideSettingsContent { /// The task settings for a particular language. #[skip_serializing_none] -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema, MergeFrom)] pub struct LanguageTaskSettingsContent { /// Extra task variables to set for a particular language. #[serde(default)] @@ -796,37 +838,15 @@ pub struct LanguageTaskSettingsContent { pub prefer_lsp: Option, } -/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language -/// names in the keys. +/// Map from language name to settings. #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageToSettingsMap(pub HashMap); -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - let language_settings_content_ref = generator - .subschema_for::() - .to_value(); - replace_subschema::(generator, || json_schema!({ - "type": "object", - "properties": params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - language_settings_content_ref.clone(), - ) - }) - .collect::>() - })) - } - } -} - /// Determines how indent guides are colored. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive( + Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum IndentGuideColoring { /// Do not render any lines for indent guides. @@ -839,7 +859,9 @@ pub enum IndentGuideColoring { } /// Determines how indent guide backgrounds are colored. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive( + Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum IndentGuideBackgroundColoring { /// Do not render any background for indent guides. diff --git a/crates/settings/src/settings_content/language_model.rs b/crates/settings/src/settings_content/language_model.rs index cad06d5fff78d1fa4ad9cd8830ab24a438d3c9ea..8e20821cfd687f6464fe52c3b75b89fde2907ec3 100644 --- a/crates/settings/src/settings_content/language_model.rs +++ b/crates/settings/src/settings_content/language_model.rs @@ -2,11 +2,12 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::sync::Arc; #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AllLanguageModelSettingsContent { pub anthropic: Option, pub bedrock: Option, @@ -25,14 +26,14 @@ pub struct AllLanguageModelSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AnthropicSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct AnthropicAvailableModel { /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc pub name: String, @@ -53,7 +54,7 @@ pub struct AnthropicAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AmazonBedrockSettingsContent { pub available_models: Option>, pub endpoint_url: Option, @@ -63,7 +64,7 @@ pub struct AmazonBedrockSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct BedrockAvailableModel { pub name: String, pub display_name: Option, @@ -74,7 +75,7 @@ pub struct BedrockAvailableModel { pub mode: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub enum BedrockAuthMethodContent { #[serde(rename = "named_profile")] NamedProfile, @@ -86,14 +87,14 @@ pub enum BedrockAuthMethodContent { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OllamaSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OllamaAvailableModel { /// The model name in the Ollama API (e.g. "llama3.2:latest") pub name: String, @@ -111,7 +112,7 @@ pub struct OllamaAvailableModel { pub supports_thinking: Option, } -#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema)] +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema, MergeFrom)] #[serde(untagged)] pub enum KeepAlive { /// Keep model alive for N seconds @@ -134,14 +135,14 @@ impl Default for KeepAlive { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct LmStudioSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LmStudioAvailableModel { pub name: String, pub display_name: Option, @@ -151,14 +152,14 @@ pub struct LmStudioAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct DeepseekSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct DeepseekAvailableModel { pub name: String, pub display_name: Option, @@ -167,14 +168,14 @@ pub struct DeepseekAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct MistralSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct MistralAvailableModel { pub name: String, pub display_name: Option, @@ -187,14 +188,14 @@ pub struct MistralAvailableModel { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenAiSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiAvailableModel { pub name: String, pub display_name: Option, @@ -204,7 +205,7 @@ pub struct OpenAiAvailableModel { pub reasoning_effort: Option, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum OpenAiReasoningEffort { Minimal, @@ -214,14 +215,14 @@ pub enum OpenAiReasoningEffort { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleSettingsContent { pub api_url: String, pub available_models: Vec, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleAvailableModel { pub name: String, pub display_name: Option, @@ -233,7 +234,7 @@ pub struct OpenAiCompatibleAvailableModel { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleModelCapabilities { pub tools: bool, pub images: bool, @@ -253,14 +254,14 @@ impl Default for OpenAiCompatibleModelCapabilities { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct VercelSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct VercelAvailableModel { pub name: String, pub display_name: Option, @@ -270,14 +271,14 @@ pub struct VercelAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct GoogleSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GoogleAvailableModel { pub name: String, pub display_name: Option, @@ -286,14 +287,14 @@ pub struct GoogleAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct XAiSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct XaiAvailableModel { pub name: String, pub display_name: Option, @@ -303,13 +304,13 @@ pub struct XaiAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct ZedDotDevSettingsContent { pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ZedDotDevAvailableModel { /// The provider of the language model. pub provider: ZedDotDevAvailableProvider, @@ -336,7 +337,7 @@ pub struct ZedDotDevAvailableModel { pub mode: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum ZedDotDevAvailableProvider { Anthropic, @@ -345,14 +346,14 @@ pub enum ZedDotDevAvailableProvider { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenRouterSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenRouterAvailableModel { pub name: String, pub display_name: Option, @@ -366,7 +367,7 @@ pub struct OpenRouterAvailableModel { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenRouterProvider { order: Option>, #[serde(default = "default_true")] @@ -381,7 +382,7 @@ pub struct OpenRouterProvider { sort: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum DataCollection { Allow, @@ -400,14 +401,16 @@ fn default_true() -> bool { /// Configuration for caching language model messages. #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageModelCacheConfiguration { pub max_cache_anchors: usize, pub should_speculate: bool, pub min_total_token: u64, } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ModelMode { #[default] diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs index 2c154dd8ea337ae165cb1e76872640a8af334fe3..b44f77fcf171e754313542641ad0f6453a730ed8 100644 --- a/crates/settings/src/settings_content/project.rs +++ b/crates/settings/src/settings_content/project.rs @@ -4,12 +4,13 @@ use collections::{BTreeMap, HashMap}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use util::serde::default_true; -use crate::{AllLanguageSettingsContent, SlashCommandSettings}; +use crate::{AllLanguageSettingsContent, ExtendingVec, SlashCommandSettings}; #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ProjectSettingsContent { #[serde(flatten)] pub all_languages: AllLanguageSettingsContent, @@ -43,11 +44,11 @@ pub struct ProjectSettingsContent { pub slash_commands: Option, /// The list of custom Git hosting providers. - pub git_hosting_providers: Option>, + pub git_hosting_providers: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct WorktreeSettingsContent { /// The displayed name of this project. If not set, the root directory name /// will be displayed. @@ -81,11 +82,11 @@ pub struct WorktreeSettingsContent { /// Treat the files matching these globs as `.env` files. /// Default: ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"] - pub private_files: Option>, + pub private_files: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash)] #[serde(rename_all = "snake_case")] pub struct LspSettings { pub binary: Option, @@ -112,7 +113,9 @@ impl Default for LspSettings { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[derive( + Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash, +)] pub struct BinarySettings { pub path: Option, pub arguments: Option>, @@ -121,7 +124,9 @@ pub struct BinarySettings { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[derive( + Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash, +)] pub struct FetchSettings { // Whether to consider pre-releases for fetching pub pre_release: Option, @@ -129,7 +134,7 @@ pub struct FetchSettings { /// Common language server settings. #[skip_serializing_none] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GlobalLspSettingsContent { /// Whether to show the LSP servers button in the status bar. /// @@ -138,7 +143,7 @@ pub struct GlobalLspSettingsContent { } #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct DapSettingsContent { pub binary: Option, @@ -147,7 +152,9 @@ pub struct DapSettingsContent { } #[skip_serializing_none] -#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] +#[derive( + Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, +)] pub struct SessionSettingsContent { /// Whether or not to restore unsaved buffers on restart. /// @@ -158,7 +165,7 @@ pub struct SessionSettingsContent { pub restore_unsaved_buffers: Option, } -#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom, Debug)] #[serde(tag = "source", rename_all = "snake_case")] pub enum ContextServerSettingsContent { Custom { @@ -198,7 +205,7 @@ impl ContextServerSettingsContent { } #[skip_serializing_none] -#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom)] pub struct ContextServerCommand { #[serde(rename = "command")] pub path: PathBuf, @@ -234,7 +241,7 @@ impl std::fmt::Debug for ContextServerCommand { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GitSettings { /// Whether or not to show the git gutter. /// @@ -259,7 +266,7 @@ pub struct GitSettings { pub hunk_style: Option, } -#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum GitGutterSetting { /// Show git gutter in tracked files. @@ -270,7 +277,7 @@ pub enum GitGutterSetting { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct InlineBlameSettings { /// Whether or not to show git blame data inline in @@ -299,7 +306,7 @@ pub struct InlineBlameSettings { } #[skip_serializing_none] -#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct BranchPickerSettingsContent { /// Whether to show author name as part of the commit information. @@ -308,7 +315,7 @@ pub struct BranchPickerSettingsContent { pub show_author_name: Option, } -#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum GitHunkStyleSetting { /// Show unstaged hunks with a filled background and staged hunks hollow. @@ -319,7 +326,7 @@ pub enum GitHunkStyleSetting { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct DiagnosticsSettingsContent { /// Whether to show the project diagnostics button in the status bar. pub button: Option, @@ -335,7 +342,7 @@ pub struct DiagnosticsSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct LspPullDiagnosticsSettingsContent { /// Whether to pull for diagnostics or not. /// @@ -349,7 +356,7 @@ pub struct LspPullDiagnosticsSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Eq)] pub struct InlineDiagnosticsSettingsContent { /// Whether or not to show inline diagnostics /// @@ -376,7 +383,7 @@ pub struct InlineDiagnosticsSettingsContent { } #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct NodeBinarySettings { /// The path to the Node binary. pub path: Option, @@ -386,7 +393,7 @@ pub struct NodeBinarySettings { pub ignore_system_version: Option, } -#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum DirenvSettings { /// Load direnv configuration through a shell hook @@ -397,7 +404,17 @@ pub enum DirenvSettings { } #[derive( - Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema, + Clone, + Copy, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Serialize, + Deserialize, + JsonSchema, + MergeFrom, )] #[serde(rename_all = "snake_case")] pub enum DiagnosticSeverityContent { @@ -412,7 +429,7 @@ pub enum DiagnosticSeverityContent { /// A custom Git hosting provider. #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GitHostingProviderConfig { /// The type of the provider. /// @@ -426,7 +443,7 @@ pub struct GitHostingProviderConfig { pub name: String, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum GitHostingProviderKind { Github, diff --git a/crates/settings/src/settings_content/terminal.rs b/crates/settings/src/settings_content/terminal.rs index e4d76a049e03438dc351b2e1bcf628cfa0e5f5e1..017e89102dc2580e4924488809531806d6d662e8 100644 --- a/crates/settings/src/settings_content/terminal.rs +++ b/crates/settings/src/settings_content/terminal.rs @@ -5,11 +5,12 @@ use gpui::{AbsoluteLength, FontFeatures, SharedString, px}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use crate::FontFamilyName; #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct TerminalSettingsContent { /// What shell to use when opening a terminal. /// @@ -127,7 +128,7 @@ pub struct TerminalSettingsContent { } /// Shell configuration to open the terminal with. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum Shell { /// Use the system's default terminal configuration in /etc/passwd @@ -146,7 +147,7 @@ pub enum Shell { }, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectory { /// Use the current file's project directory. Will Fallback to the @@ -163,15 +164,15 @@ pub enum WorkingDirectory { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ScrollbarSettingsContent { /// When to show the scrollbar in the terminal. /// /// Default: inherits editor scrollbar settings - pub show: Option>, + pub show: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { /// Use a line height that's comfortable for reading, 1.618 @@ -198,7 +199,7 @@ impl TerminalLineHeight { /// When to show the scrollbar. /// /// Default: auto -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { /// Show the scrollbar if there's important information or @@ -212,7 +213,9 @@ pub enum ShowScrollbar { Never, } -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum CursorShapeContent { /// Cursor is a block like `█`. @@ -226,7 +229,7 @@ pub enum CursorShapeContent { Hollow, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum TerminalBlink { /// Never blink the cursor, ignoring the terminal mode. @@ -238,7 +241,7 @@ pub enum TerminalBlink { On, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum AlternateScroll { On, @@ -247,7 +250,7 @@ pub enum AlternateScroll { // Toolbar related settings #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct TerminalToolbarContent { /// Whether to display the terminal title in breadcrumbs inside the terminal pane. /// Only shown if the terminal title is not empty. @@ -259,7 +262,7 @@ pub struct TerminalToolbarContent { pub breadcrumbs: Option, } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum VenvSettings { #[default] @@ -297,7 +300,7 @@ impl VenvSettings { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum TerminalDockPosition { Left, @@ -305,7 +308,7 @@ pub enum TerminalDockPosition { Right, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ActivateScript { #[default] diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index 886448d696fa975749a1fcefe615694a903f8d29..bc6eed96ad12ef5f1358e7a21a4fe2db8a6a9d10 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/crates/settings/src/settings_content/theme.rs @@ -4,6 +4,7 @@ use schemars::{JsonSchema, JsonSchema_repr}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; +use settings_macros::MergeFrom; use std::sync::Arc; use serde_with::skip_serializing_none; @@ -11,7 +12,7 @@ use serde_with::skip_serializing_none; /// Settings for rendering text in UI and text buffers. #[skip_serializing_none] -#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ThemeSettingsContent { /// The default font size for text in the UI. #[serde(default)] @@ -53,7 +54,7 @@ pub struct ThemeSettingsContent { pub buffer_font_features: Option, /// The font size for the agent panel. Falls back to the UI font size if unset. #[serde(default)] - pub agent_font_size: Option>, + pub agent_font_size: Option, /// The name of the Zed theme to use. #[serde(default)] pub theme: Option, @@ -93,7 +94,7 @@ fn default_font_fallbacks() -> Option { } /// Represents the selection of a theme, which can be either static or dynamic. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(untagged)] pub enum ThemeSelection { /// A static theme selection, represented by a single theme name. @@ -111,7 +112,7 @@ pub enum ThemeSelection { } /// Represents the selection of an icon theme, which can be either static or dynamic. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(untagged)] pub enum IconThemeSelection { /// A static icon theme selection, represented by a single icon theme name. @@ -134,7 +135,9 @@ pub enum IconThemeSelection { /// `Light` and `Dark` will select their respective themes. /// /// `System` will select the theme based on the system's appearance. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum ThemeMode { /// Use the specified `light` theme. @@ -163,6 +166,7 @@ pub enum ThemeMode { Serialize, Deserialize, JsonSchema, + MergeFrom, )] #[serde(rename_all = "snake_case")] pub enum UiDensity { @@ -190,15 +194,14 @@ impl UiDensity { } } -/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at -/// runtime. +/// Font family name. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct FontFamilyName(pub Arc); /// The buffer's line height. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub enum BufferLineHeight { /// A less dense line height. @@ -226,7 +229,7 @@ where /// The content of a serialized theme. #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct ThemeStyleContent { #[serde(default, rename = "background.appearance")] @@ -249,31 +252,30 @@ pub struct ThemeStyleContent { pub syntax: IndexMap, } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct AccentContent(pub Option); -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct PlayerColorContent { pub cursor: Option, pub background: Option, pub selection: Option, } -/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime. +/// Theme name. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct ThemeName(pub Arc); -/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at -/// runtime. +/// Icon Theme Name #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct IconThemeName(pub Arc); #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct ThemeColorsContent { /// Border color. Used for most borders, is usually a high contrast color. @@ -778,7 +780,7 @@ pub struct ThemeColorsContent { } #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct HighlightStyleContent { pub color: Option, @@ -812,7 +814,7 @@ where } #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct StatusColorsContent { /// Indicates some kind of conflict, like a file changed on disk while it was open, or @@ -958,7 +960,7 @@ pub struct StatusColorsContent { } /// The background appearance of the window. -#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum WindowBackgroundContent { Opaque, @@ -976,7 +978,7 @@ impl Into for WindowBackgroundContent { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub enum FontStyleContent { Normal, @@ -994,7 +996,9 @@ impl From for FontStyle { } } -#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, JsonSchema_repr, PartialEq)] +#[derive( + Debug, Clone, Copy, Serialize_repr, Deserialize_repr, JsonSchema_repr, PartialEq, MergeFrom, +)] #[repr(u16)] pub enum FontWeightContent { Thin = 100, diff --git a/crates/settings/src/settings_content/workspace.rs b/crates/settings/src/settings_content/workspace.rs index aaa5817336058fad2e4300e70143e2100c9b72c7..9d210a73da1a92768e4e5cbb5e9804ce9d292ce5 100644 --- a/crates/settings/src/settings_content/workspace.rs +++ b/crates/settings/src/settings_content/workspace.rs @@ -4,11 +4,12 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use crate::{DockPosition, DockSide, ScrollbarSettingsContent, ShowIndentGuides}; #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct WorkspaceSettingsContent { /// Active pane styling settings. pub active_pane_modifiers: Option, @@ -108,7 +109,7 @@ pub struct WorkspaceSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ItemSettingsContent { /// Whether to show the Git file status on a tab item. /// @@ -138,7 +139,7 @@ pub struct ItemSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct PreviewTabsSettingsContent { /// Whether to show opened editors as preview tabs. /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic. @@ -155,7 +156,7 @@ pub struct PreviewTabsSettingsContent { pub enable_preview_from_code_navigation: Option, } -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum ClosePosition { Left, @@ -163,7 +164,7 @@ pub enum ClosePosition { Right, } -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum ShowCloseButton { Always, @@ -172,7 +173,9 @@ pub enum ShowCloseButton { Hidden, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum ShowDiagnostics { #[default] @@ -181,7 +184,7 @@ pub enum ShowDiagnostics { All, } -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ActivateOnClose { #[default] @@ -191,7 +194,7 @@ pub enum ActivateOnClose { } #[skip_serializing_none] -#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct ActivePanelModifiers { /// Size of the border surrounding the active pane. @@ -209,7 +212,7 @@ pub struct ActivePanelModifiers { pub inactive_opacity: Option, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum BottomDockLayout { /// Contained between the left and right docks @@ -223,7 +226,7 @@ pub enum BottomDockLayout { RightAligned, } -#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] #[serde(rename_all = "snake_case")] pub enum CloseWindowWhenNoItems { /// Match platform conventions by default, so "on" on macOS and "off" everywhere else @@ -245,7 +248,9 @@ impl CloseWindowWhenNoItems { } } -#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive( + Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, +)] #[serde(rename_all = "snake_case")] pub enum RestoreOnStartupBehavior { /// Always start with an empty editor @@ -258,7 +263,7 @@ pub enum RestoreOnStartupBehavior { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct TabBarSettingsContent { /// Whether or not to show the tab bar in the editor. /// @@ -274,7 +279,7 @@ pub struct TabBarSettingsContent { pub show_tab_bar_buttons: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum AutosaveSetting { /// Disable autosave. @@ -298,14 +303,14 @@ impl AutosaveSetting { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum PaneSplitDirectionHorizontal { Up, Down, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum PaneSplitDirectionVertical { Left, @@ -313,7 +318,7 @@ pub enum PaneSplitDirectionVertical { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub struct CenteredLayoutSettings { /// The relative width of the left padding of the central pane from the @@ -328,7 +333,7 @@ pub struct CenteredLayoutSettings { pub right_padding: Option, } -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)] +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)] #[serde(rename_all = "snake_case")] pub enum OnLastWindowClosed { /// Match platform conventions by default, so don't quit on macOS, and quit on other platforms @@ -348,7 +353,7 @@ impl OnLastWindowClosed { } #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct ProjectPanelSettingsContent { /// Whether to show the project panel button in the status bar. /// @@ -423,7 +428,9 @@ pub struct ProjectPanelSettingsContent { pub drag_and_drop: Option, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum ProjectPanelEntrySpacing { /// Comfortable spacing of entries. @@ -434,7 +441,7 @@ pub enum ProjectPanelEntrySpacing { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ProjectPanelIndentGuidesSettings { pub show: Option, } diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs index 762842d851b73a661baf93a7060f26fe5714c872..555a48e9f0972d708eaf9aaaaaf467852ccf7dd6 100644 --- a/crates/settings/src/settings_json.rs +++ b/crates/settings/src/settings_json.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gpui::App; +use gpui::SharedString; use serde::{Serialize, de::DeserializeOwned}; use serde_json::Value; use std::{ops::Range, sync::LazyLock}; @@ -10,16 +10,10 @@ use util::RangeExt; pub struct SettingsJsonSchemaParams<'a> { pub language_names: &'a [String], pub font_names: &'a [String], + pub theme_names: &'a [SharedString], + pub icon_theme_names: &'a [SharedString], } -/// Value registered which specifies JSON schemas that are generated at runtime. -pub struct ParameterizedJsonSchema { - pub add_and_get_ref: - fn(&mut schemars::SchemaGenerator, &SettingsJsonSchemaParams, &App) -> schemars::Schema, -} - -inventory::collect!(ParameterizedJsonSchema); - pub fn update_value_in_json_text<'a>( text: &mut String, key_path: &mut Vec<&'a str>, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index acbff18bc333515c81d994e8629e3dff21fb4146..fe2a5cfdfc6493cf3ef374a66c389022748e088b 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -10,7 +10,7 @@ use futures::{ use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal}; use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name}; -use schemars::JsonSchema; +use schemars::{JsonSchema, json_schema}; use serde_json::Value; use smallvec::SmallVec; use std::{ @@ -18,16 +18,23 @@ use std::{ fmt::Debug, ops::Range, path::{Path, PathBuf}, + rc::Rc, str::{self, FromStr}, sync::Arc, }; -use util::{ResultExt as _, schemars::DefaultDenyUnknownFields}; +use util::{ + ResultExt as _, + schemars::{DefaultDenyUnknownFields, replace_subschema}, +}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{ - ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry, - VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text, + ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent, + LanguageToSettingsMap, SettingsJsonSchemaParams, SettingsUiEntry, ThemeName, VsCodeSettings, + WorktreeId, + merge_from::MergeFrom, + parse_json_with_comments, replace_value_in_json_text, settings_content::{ ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent, UserSettingsContent, @@ -58,15 +65,10 @@ pub trait Settings: 'static + Send + Sync + Sized { const PRESERVED_KEYS: Option<&'static [&'static str]> = None; /// Read the value from default.json. + /// /// This function *should* panic if default values are missing, /// and you should add a default to default.json for documentation. - fn from_defaults(content: &SettingsContent, cx: &mut App) -> Self; - - /// Update the value based on the content from the current file. - /// - /// This function *should not* panic if there are problems, as the - /// content of user-provided settings files may be incomplete or invalid. - fn refine(&mut self, content: &SettingsContent, cx: &mut App); + fn from_settings(content: &SettingsContent, cx: &mut App) -> Self; fn missing_default() -> anyhow::Error { anyhow::anyhow!("missing default for: {}", std::any::type_name::()) @@ -140,12 +142,15 @@ pub struct SettingsLocation<'a> { /// A set of strongly-typed setting values defined via multiple config files. pub struct SettingsStore { setting_values: HashMap>, - default_settings: Box, + default_settings: Rc, user_settings: Option, global_settings: Option>, extension_settings: Option>, server_settings: Option>, + + merged_settings: Rc, + local_settings: BTreeMap<(WorktreeId, Arc), SettingsContent>, raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc), (String, Option)>, @@ -193,8 +198,7 @@ struct SettingValue { trait AnySettingValue: 'static + Send + Sync { fn setting_type_name(&self) -> &'static str; - fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box; - fn refine(&self, value: &mut dyn Any, s: &[&SettingsContent], cx: &mut App); + fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box; fn value_for_path(&self, path: Option) -> &dyn Any; fn all_local_values(&self) -> Vec<(WorktreeId, Arc, &dyn Any)>; @@ -210,14 +214,17 @@ trait AnySettingValue: 'static + Send + Sync { impl SettingsStore { pub fn new(cx: &App, default_settings: &str) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); - let default_settings = parse_json_with_comments(default_settings).unwrap(); + let default_settings: Rc = + parse_json_with_comments(default_settings).unwrap(); Self { setting_values: Default::default(), - default_settings, + default_settings: default_settings.clone(), global_settings: None, server_settings: None, user_settings: None, extension_settings: None, + + merged_settings: default_settings, local_settings: BTreeMap::default(), raw_editorconfig_settings: BTreeMap::default(), setting_file_updates_tx, @@ -257,38 +264,7 @@ impl SettingsStore { global_value: None, local_values: Vec::new(), })); - - let mut refinements = Vec::default(); - - if let Some(extension_settings) = self.extension_settings.as_deref() { - refinements.push(extension_settings) - } - - if let Some(global_settings) = self.global_settings.as_deref() { - refinements.push(global_settings) - } - - if let Some(user_settings) = self.user_settings.as_ref() { - refinements.push(&user_settings.content); - if let Some(release_channel) = user_settings.for_release_channel() { - refinements.push(release_channel) - } - if let Some(os) = user_settings.for_os() { - refinements.push(os) - } - if let Some(profile) = user_settings.for_profile(cx) { - refinements.push(profile) - } - } - - if let Some(server_settings) = self.server_settings.as_ref() { - refinements.push(server_settings) - } - let mut value = T::from_defaults(&self.default_settings, cx); - for refinement in refinements { - value.refine(refinement, cx) - } - + let value = T::from_settings(&self.merged_settings, cx); setting_value.set_global_value(Box::new(value)); } @@ -831,19 +807,56 @@ impl SettingsStore { }) } - pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value { + pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value { let mut generator = schemars::generate::SchemaSettings::draft2019_09() .with_transform(DefaultDenyUnknownFields) .into_generator(); - let schema = UserSettingsContent::json_schema(&mut generator); + UserSettingsContent::json_schema(&mut generator); + + let language_settings_content_ref = generator + .subschema_for::() + .to_value(); + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "object", + "properties": params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + language_settings_content_ref.clone(), + ) + }) + .collect::>() + }) + }); - // add schemas which are determined at runtime - for parameterized_json_schema in inventory::iter::() { - (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx); - } + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "string", + "enum": params.font_names, + }) + }); + + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "string", + "enum": params.theme_names, + }) + }); - schema.to_value() + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "string", + "enum": params.icon_theme_names, + }) + }); + + generator + .root_schema_for::() + .to_value() } fn recompute_values( @@ -852,74 +865,62 @@ impl SettingsStore { cx: &mut App, ) -> std::result::Result<(), InvalidSettingsError> { // Reload the global and local values for every setting. - let mut project_settings_stack = Vec::<&SettingsContent>::new(); + let mut project_settings_stack = Vec::::new(); let mut paths_stack = Vec::>::new(); - let mut refinements = Vec::default(); - - if let Some(extension_settings) = self.extension_settings.as_deref() { - refinements.push(extension_settings) - } - - if let Some(global_settings) = self.global_settings.as_deref() { - refinements.push(global_settings) - } - - if let Some(user_settings) = self.user_settings.as_ref() { - refinements.push(&user_settings.content); - if let Some(release_channel) = user_settings.for_release_channel() { - refinements.push(release_channel) + if changed_local_path.is_none() { + let mut merged = self.default_settings.as_ref().clone(); + merged.merge_from(self.extension_settings.as_deref()); + merged.merge_from(self.global_settings.as_deref()); + if let Some(user_settings) = self.user_settings.as_ref() { + merged.merge_from(Some(&user_settings.content)); + merged.merge_from(user_settings.for_release_channel()); + merged.merge_from(user_settings.for_os()); + merged.merge_from(user_settings.for_profile(cx)); } - if let Some(os) = user_settings.for_os() { - refinements.push(os) - } - if let Some(profile) = user_settings.for_profile(cx) { - refinements.push(profile) - } - } - - if let Some(server_settings) = self.server_settings.as_ref() { - refinements.push(server_settings) - } + merged.merge_from(self.server_settings.as_deref()); + self.merged_settings = Rc::new(merged); - for setting_value in self.setting_values.values_mut() { - // If the global settings file changed, reload the global value for the field. - if changed_local_path.is_none() { - let mut value = setting_value.from_default(&self.default_settings, cx); - setting_value.refine(value.as_mut(), &refinements, cx); + for setting_value in self.setting_values.values_mut() { + let value = setting_value.from_settings(&self.merged_settings, cx); setting_value.set_global_value(value); } + } - // Reload the local values for the setting. - paths_stack.clear(); - project_settings_stack.clear(); - for ((root_id, directory_path), local_settings) in &self.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 - && (root_id != prev_root_id || !directory_path.starts_with(prev_path)) - { - paths_stack.pop(); - project_settings_stack.pop(); - continue; - } - break; + for ((root_id, directory_path), local_settings) in &self.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 + && (root_id != prev_root_id || !directory_path.starts_with(prev_path)) + { + paths_stack.pop(); + project_settings_stack.pop(); + continue; } + break; + } - paths_stack.push(Some((*root_id, directory_path.as_ref()))); - project_settings_stack.push(local_settings); + paths_stack.push(Some((*root_id, directory_path.as_ref()))); + let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() { + (*deepest).clone() + } else { + self.merged_settings.as_ref().clone() + }; + merged_local_settings.merge_from(Some(local_settings)); - // If a local settings file changed, then avoid recomputing local - // settings for any path outside of that directory. - 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) - }) { - continue; - } + project_settings_stack.push(merged_local_settings); + + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + 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) + }) { + continue; + } - let mut value = setting_value.from_default(&self.default_settings, cx); - setting_value.refine(value.as_mut(), &refinements, cx); - setting_value.refine(value.as_mut(), &project_settings_stack, cx); + for setting_value in self.setting_values.values_mut() { + let value = + setting_value.from_settings(&project_settings_stack.last().unwrap(), cx); setting_value.set_local_value(*root_id, directory_path.clone(), value); } } @@ -1001,15 +1002,8 @@ impl Debug for SettingsStore { } impl AnySettingValue for SettingValue { - fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box { - Box::new(T::from_defaults(s, cx)) as _ - } - - fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) { - let value = value.downcast_mut::().unwrap(); - for refinement in refinements { - value.refine(refinement, cx) - } + fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box { + Box::new(T::from_settings(s, cx)) as _ } fn setting_type_name(&self) -> &'static str { @@ -1072,7 +1066,6 @@ mod tests { use super::*; use unindent::Unindent; - use util::MergeFrom; #[derive(Debug, PartialEq)] struct AutoUpdateSetting { @@ -1080,19 +1073,11 @@ mod tests { } impl Settings for AutoUpdateSetting { - fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _: &mut App) -> Self { AutoUpdateSetting { auto_update: content.auto_update.unwrap(), } } - - fn refine(&mut self, content: &SettingsContent, _: &mut App) { - if let Some(auto_update) = content.auto_update { - self.auto_update = auto_update; - } - } - - fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {} } #[derive(Debug, PartialEq)] @@ -1102,7 +1087,7 @@ mod tests { } impl Settings for TitleBarSettings { - fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _: &mut App) -> Self { let content = content.title_bar.clone().unwrap(); TitleBarSettings { show: content.show.unwrap(), @@ -1110,13 +1095,6 @@ mod tests { } } - fn refine(&mut self, content: &SettingsContent, _: &mut App) { - let Some(content) = content.title_bar.as_ref() else { - return; - }; - self.show.merge_from(&content.show) - } - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { let mut show = None; @@ -1138,7 +1116,7 @@ mod tests { } impl Settings for DefaultLanguageSettings { - fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _: &mut App) -> Self { let content = &content.project.all_languages.defaults; DefaultLanguageSettings { tab_size: content.tab_size.unwrap(), @@ -1146,13 +1124,6 @@ mod tests { } } - fn refine(&mut self, content: &SettingsContent, _: &mut App) { - let content = &content.project.all_languages.defaults; - self.tab_size.merge_from(&content.tab_size); - self.preferred_line_length - .merge_from(&content.preferred_line_length); - } - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { let content = &mut content.project.all_languages.defaults; diff --git a/crates/settings_ui_macros/Cargo.toml b/crates/settings_macros/Cargo.toml similarity index 73% rename from crates/settings_ui_macros/Cargo.toml rename to crates/settings_macros/Cargo.toml index ece6269f5999ad3ed7e08e34d3ff4c7cb97c9f63..06ce1d01e5fa9d25b5c0d3c742a2d325ec996e39 100644 --- a/crates/settings_ui_macros/Cargo.toml +++ b/crates/settings_macros/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "settings_ui_macros" +name = "settings_macros" version = "0.1.0" edition.workspace = true publish.workspace = true license = "GPL-3.0-or-later" [lib] -path = "src/settings_ui_macros.rs" +path = "src/settings_macros.rs" proc-macro = true [lints] @@ -16,8 +16,6 @@ workspace = true default = [] [dependencies] -heck.workspace = true -proc-macro2.workspace = true quote.workspace = true syn.workspace = true workspace-hack.workspace = true diff --git a/crates/settings_ui_macros/LICENSE-GPL b/crates/settings_macros/LICENSE-GPL similarity index 100% rename from crates/settings_ui_macros/LICENSE-GPL rename to crates/settings_macros/LICENSE-GPL diff --git a/crates/settings_macros/src/settings_macros.rs b/crates/settings_macros/src/settings_macros.rs new file mode 100644 index 0000000000000000000000000000000000000000..33c136b1f2b3e4bec3528d4dff632e05119bc516 --- /dev/null +++ b/crates/settings_macros/src/settings_macros.rs @@ -0,0 +1,132 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Fields, Type, parse_macro_input}; + +/// Derives the `MergeFrom` trait for a struct. +/// +/// This macro automatically implements `MergeFrom` by calling `merge_from` +/// on all fields in the struct. For `Option` fields, it merges by taking +/// the `other` value when `self` is `None`. For other types, it recursively +/// calls `merge_from` on the field. +/// +/// # Example +/// +/// ```ignore +/// #[derive(Clone, MergeFrom)] +/// struct MySettings { +/// field1: Option, +/// field2: SomeOtherSettings, +/// } +/// ``` +#[proc_macro_derive(MergeFrom)] +pub fn derive_merge_from(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let merge_body = match &input.data { + Data::Struct(data_struct) => match &data_struct.fields { + Fields::Named(fields) => { + let field_merges = fields.named.iter().map(|field| { + let field_name = &field.ident; + let field_type = &field.ty; + + if is_option_type(field_type) { + // For Option fields, merge by taking the other value if self is None + quote! { + if let Some(other_value) = other.#field_name.as_ref() { + if self.#field_name.is_none() { + self.#field_name = Some(other_value.clone()); + } else if let Some(self_value) = self.#field_name.as_mut() { + self_value.merge_from(Some(other_value)); + } + } + } + } else { + // For non-Option fields, recursively call merge_from + quote! { + self.#field_name.merge_from(Some(&other.#field_name)); + } + } + }); + + quote! { + if let Some(other) = other { + #(#field_merges)* + } + } + } + Fields::Unnamed(fields) => { + let field_merges = fields.unnamed.iter().enumerate().map(|(i, field)| { + let field_index = syn::Index::from(i); + let field_type = &field.ty; + + if is_option_type(field_type) { + // For Option fields, merge by taking the other value if self is None + quote! { + if let Some(other_value) = other.#field_index.as_ref() { + if self.#field_index.is_none() { + self.#field_index = Some(other_value.clone()); + } else if let Some(self_value) = self.#field_index.as_mut() { + self_value.merge_from(Some(other_value)); + } + } + } + } else { + // For non-Option fields, recursively call merge_from + quote! { + self.#field_index.merge_from(Some(&other.#field_index)); + } + } + }); + + quote! { + if let Some(other) = other { + #(#field_merges)* + } + } + } + Fields::Unit => { + quote! { + // No fields to merge for unit structs + } + } + }, + Data::Enum(_) => { + quote! { + if let Some(other) = other { + *self = other.clone(); + } + } + } + Data::Union(_) => { + panic!("MergeFrom cannot be derived for unions"); + } + }; + + let expanded = quote! { + impl #impl_generics crate::merge_from::MergeFrom for #name #ty_generics #where_clause { + fn merge_from(&mut self, other: ::core::option::Option<&Self>) { + use crate::merge_from::MergeFrom as _; + #merge_body + } + } + }; + + TokenStream::from(expanded) +} + +/// Check if a type is `Option` +fn is_option_type(ty: &Type) -> bool { + match ty { + Type::Path(type_path) => { + if let Some(segment) = type_path.path.segments.last() { + segment.ident == "Option" + } else { + false + } + } + _ => false, + } +} diff --git a/crates/settings_ui_macros/src/settings_ui_macros.rs b/crates/settings_ui_macros/src/settings_ui_macros.rs deleted file mode 100644 index 971adbfe4dd2356a8e19185767a121810cadd991..0000000000000000000000000000000000000000 --- a/crates/settings_ui_macros/src/settings_ui_macros.rs +++ /dev/null @@ -1,612 +0,0 @@ -use heck::{ToSnakeCase as _, ToTitleCase as _}; -use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; -use syn::{Data, DeriveInput, LitStr, Token, parse_macro_input}; - -/// Derive macro for the `SettingsUi` marker trait. -/// -/// This macro automatically implements the `SettingsUi` trait for the annotated type. -/// The `SettingsUi` trait is a marker trait used to indicate that a type can be -/// displayed in the settings UI. -/// -/// # Example -/// -/// ``` -/// use settings_ui_macros::SettingsUi; -/// -/// #[derive(SettingsUi)] -/// #[settings_ui(group = "Standard")] -/// struct MySettings { -/// enabled: bool, -/// count: usize, -/// } -/// ``` -#[proc_macro_derive(SettingsUi, attributes(settings_ui))] -pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = &input.ident; - - // Handle generic parameters if present - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let mut group_name = Option::::None; - let mut path_name = Option::::None; - - for attr in &input.attrs { - if attr.path().is_ident("settings_ui") { - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("group") { - if group_name.is_some() { - return Err(meta.error("Only one 'group' path can be specified")); - } - meta.input.parse::()?; - let lit: LitStr = meta.input.parse()?; - group_name = Some(lit.value()); - } else if meta.path.is_ident("path") { - // todo(settings_ui) rely entirely on settings_key, remove path attribute - if path_name.is_some() { - return Err(meta.error("Only one 'path' can be specified, either with `path` in `settings_ui` or with `settings_key`")); - } - meta.input.parse::()?; - let lit: LitStr = meta.input.parse()?; - path_name = Some(lit.value()); - } else if meta.path.is_ident("render") { - // Just consume the tokens even if we don't use them here - meta.input.parse::()?; - let _lit: LitStr = meta.input.parse()?; - } - Ok(()) - }) - .unwrap_or_else(|e| panic!("in #[settings_ui] attribute: {}", e)); - } else if let Some(settings_key) = parse_setting_key_attr(attr) { - // todo(settings_ui) either remove fallback key or handle it here - if path_name.is_some() && settings_key.key.is_some() { - panic!("Both 'path' and 'settings_key' are specified. Must specify only one"); - } - path_name = settings_key.key; - } - } - - let doc_str = parse_documentation_from_attrs(&input.attrs); - - let ui_item_fn_body = generate_ui_item_body(group_name.as_ref(), &input); - - // todo(settings_ui): make group name optional, repurpose group as tag indicating item is group, and have "title" tag for custom title - let title = group_name.unwrap_or(input.ident.to_string().to_title_case()); - - let ui_entry_fn_body = map_ui_item_to_entry( - path_name.as_deref(), - &title, - doc_str.as_deref(), - quote! { Self }, - ); - - let expanded = quote! { - impl #impl_generics settings::SettingsUi for #name #ty_generics #where_clause { - fn settings_ui_item() -> settings::SettingsUiItem { - #ui_item_fn_body - } - - fn settings_ui_entry() -> settings::SettingsUiEntry { - #ui_entry_fn_body - } - } - }; - - proc_macro::TokenStream::from(expanded) -} - -fn extract_type_from_option(ty: TokenStream) -> TokenStream { - match option_inner_type(ty.clone()) { - Some(inner_type) => inner_type, - None => ty, - } -} - -fn option_inner_type(ty: TokenStream) -> Option { - let ty = syn::parse2::(ty).ok()?; - let syn::Type::Path(path) = ty else { - return None; - }; - let segment = path.path.segments.last()?; - if segment.ident != "Option" { - return None; - } - let syn::PathArguments::AngleBracketed(args) = &segment.arguments else { - return None; - }; - let arg = args.args.first()?; - let syn::GenericArgument::Type(ty) = arg else { - return None; - }; - return Some(ty.to_token_stream()); -} - -fn map_ui_item_to_entry( - path: Option<&str>, - title: &str, - doc_str: Option<&str>, - ty: TokenStream, -) -> TokenStream { - // todo(settings_ui): does quote! just work with options? - let path = path.map_or_else(|| quote! {None}, |path| quote! {Some(#path)}); - let doc_str = doc_str.map_or_else(|| quote! {None}, |doc_str| quote! {Some(#doc_str)}); - let item = ui_item_from_type(ty); - quote! { - settings::SettingsUiEntry { - title: #title, - path: #path, - item: #item, - documentation: #doc_str, - } - } -} - -fn ui_item_from_type(ty: TokenStream) -> TokenStream { - let ty = extract_type_from_option(ty); - return trait_method_call(ty, quote! {settings::SettingsUi}, quote! {settings_ui_item}); -} - -fn trait_method_call( - ty: TokenStream, - trait_name: TokenStream, - method_name: TokenStream, -) -> TokenStream { - // doing the makes the error message better: - // -> "#ty Doesn't implement settings::SettingsUi" instead of "no item "settings_ui_item" for #ty" - // and ensures safety against name conflicts - // - // todo(settings_ui): Turn `Vec` into `Vec::` here as well - quote! { - <#ty as #trait_name>::#method_name() - } -} - -fn generate_ui_item_body(group_name: Option<&String>, input: &syn::DeriveInput) -> TokenStream { - match (group_name, &input.data) { - (_, Data::Union(_)) => unimplemented!("Derive SettingsUi for Unions"), - (None, Data::Struct(_)) => quote! { - settings::SettingsUiItem::None - }, - (Some(_), Data::Struct(data_struct)) => { - let parent_serde_attrs = parse_serde_attributes(&input.attrs); - item_group_from_fields(&data_struct.fields, &parent_serde_attrs) - } - (None, Data::Enum(data_enum)) => { - let serde_attrs = parse_serde_attributes(&input.attrs); - let render_as = parse_render_as(&input.attrs); - let length = data_enum.variants.len(); - - let mut variants = Vec::with_capacity(length); - let mut labels = Vec::with_capacity(length); - - for variant in &data_enum.variants { - // todo(settings_ui): Can #[serde(rename = )] be on enum variants? - let ident = variant.ident.clone().to_string(); - let variant_name = serde_attrs.rename_all.apply(&ident); - let title = variant_name.to_title_case(); - - variants.push(variant_name); - labels.push(title); - } - - let is_not_union = data_enum.variants.iter().all(|v| v.fields.is_empty()); - if is_not_union { - return match render_as { - RenderAs::ToggleGroup if length > 6 => { - panic!("Can't set toggle group with more than six entries"); - } - RenderAs::ToggleGroup => { - quote! { - settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::ToggleGroup{ variants: &[#(#variants),*], labels: &[#(#labels),*] }) - } - } - RenderAs::Default => { - quote! { - settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::DropDown{ variants: &[#(#variants),*], labels: &[#(#labels),*] }) - } - } - }; - } - // else: Union! - let enum_name = &input.ident; - - let options = data_enum.variants.iter().map(|variant| { - if variant.fields.is_empty() { - return quote! {None}; - } - let name = &variant.ident; - let item = item_group_from_fields(&variant.fields, &serde_attrs); - // todo(settings_ui): documentation - return quote! { - Some(settings::SettingsUiEntry { - path: None, - title: stringify!(#name), - documentation: None, - item: #item, - }) - }; - }); - let defaults = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - if variant.fields.is_empty() { - quote! { - serde_json::to_value(#enum_name::#variant_name).expect("Failed to serialize default value for #enum_name::#variant_name") - } - } else { - let fields = variant.fields.iter().enumerate().map(|(index, field)| { - let field_name = field.ident.as_ref().map_or_else(|| syn::Index::from(index).into_token_stream(), |ident| ident.to_token_stream()); - let field_type_is_option = option_inner_type(field.ty.to_token_stream()).is_some(); - let field_default = if field_type_is_option { - quote! { - None - } - } else { - quote! { - ::std::default::Default::default() - } - }; - - quote!{ - #field_name: #field_default - } - }); - quote! { - serde_json::to_value(#enum_name::#variant_name { - #(#fields),* - }).expect("Failed to serialize default value for #enum_name::#variant_name") - } - } - }); - // todo(settings_ui): Identify #[default] attr and use it for index, defaulting to 0 - let default_variant_index: usize = 0; - let determine_option_fn = { - let match_arms = data_enum - .variants - .iter() - .enumerate() - .map(|(index, variant)| { - let variant_name = &variant.ident; - quote! { - Ok(#variant_name {..}) => #index - } - }); - quote! { - |value: &serde_json::Value, _cx: &gpui::App| -> usize { - use #enum_name::*; - match serde_json::from_value::<#enum_name>(value.clone()) { - #(#match_arms),*, - Err(_) => #default_variant_index, - } - } - } - }; - // todo(settings_ui) should probably always use toggle group for unions, dropdown makes less sense - return quote! { - settings::SettingsUiItem::Union(settings::SettingsUiItemUnion { - defaults: Box::new([#(#defaults),*]), - labels: &[#(#labels),*], - options: Box::new([#(#options),*]), - determine_option: #determine_option_fn, - }) - }; - // panic!("Unhandled"); - } - // todo(settings_ui) discriminated unions - (_, Data::Enum(_)) => quote! { - settings::SettingsUiItem::None - }, - } -} - -fn item_group_from_fields(fields: &syn::Fields, parent_serde_attrs: &SerdeOptions) -> TokenStream { - let group_items = fields - .iter() - .filter(|field| { - !field.attrs.iter().any(|attr| { - let mut has_skip = false; - if attr.path().is_ident("settings_ui") { - let _ = attr.parse_nested_meta(|meta| { - if meta.path.is_ident("skip") { - has_skip = true; - } - Ok(()) - }); - } - - has_skip - }) - }) - .map(|field| { - let field_serde_attrs = parse_serde_attributes(&field.attrs); - let name = field.ident.as_ref().map(ToString::to_string); - let title = name.as_ref().map_or_else( - || "todo(settings_ui): Titles for tuple fields".to_string(), - |name| name.to_title_case(), - ); - let doc_str = parse_documentation_from_attrs(&field.attrs); - - ( - title, - doc_str, - name.filter(|_| !field_serde_attrs.flatten).map(|name| { - parent_serde_attrs.apply_rename_to_field(&field_serde_attrs, &name) - }), - field.ty.to_token_stream(), - ) - }) - // todo(settings_ui): Re-format field name as nice title, and support setting different title with attr - .map(|(title, doc_str, path, ty)| { - map_ui_item_to_entry(path.as_deref(), &title, doc_str.as_deref(), ty) - }); - - quote! { - settings::SettingsUiItem::Group(settings::SettingsUiItemGroup{ items: vec![#(#group_items),*] }) - } -} - -struct SerdeOptions { - rename_all: SerdeRenameAll, - rename: Option, - flatten: bool, - untagged: bool, - _alias: Option, // todo(settings_ui) -} - -#[derive(PartialEq)] -enum SerdeRenameAll { - Lowercase, - SnakeCase, - None, -} - -impl SerdeRenameAll { - fn apply(&self, name: &str) -> String { - match self { - SerdeRenameAll::Lowercase => name.to_lowercase(), - SerdeRenameAll::SnakeCase => name.to_snake_case(), - SerdeRenameAll::None => name.to_string(), - } - } -} - -impl SerdeOptions { - fn apply_rename_to_field(&self, field_options: &Self, name: &str) -> String { - // field renames take precedence over struct rename all cases - if let Some(rename) = &field_options.rename { - return rename.clone(); - } - return self.rename_all.apply(name); - } -} - -enum RenderAs { - ToggleGroup, - Default, -} - -fn parse_render_as(attrs: &[syn::Attribute]) -> RenderAs { - let mut render_as = RenderAs::Default; - - for attr in attrs { - if !attr.path().is_ident("settings_ui") { - continue; - } - - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("render") { - meta.input.parse::()?; - let lit = meta.input.parse::()?.value(); - - if lit == "toggle_group" { - render_as = RenderAs::ToggleGroup; - } else { - return Err(meta.error(format!("invalid `render` attribute: {}", lit))); - } - } - Ok(()) - }) - .unwrap(); - } - - render_as -} - -fn parse_serde_attributes(attrs: &[syn::Attribute]) -> SerdeOptions { - let mut options = SerdeOptions { - rename_all: SerdeRenameAll::None, - rename: None, - flatten: false, - untagged: false, - _alias: None, - }; - - for attr in attrs { - if !attr.path().is_ident("serde") { - continue; - } - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("rename_all") { - meta.input.parse::()?; - let lit = meta.input.parse::()?.value(); - - if options.rename_all != SerdeRenameAll::None { - return Err(meta.error("duplicate `rename_all` attribute")); - } else if lit == "lowercase" { - options.rename_all = SerdeRenameAll::Lowercase; - } else if lit == "snake_case" { - options.rename_all = SerdeRenameAll::SnakeCase; - } else { - return Err(meta.error(format!("invalid `rename_all` attribute: {}", lit))); - } - // todo(settings_ui): Other options? - } else if meta.path.is_ident("flatten") { - options.flatten = true; - } else if meta.path.is_ident("rename") { - if options.rename.is_some() { - return Err(meta.error("Can only have one rename attribute")); - } - - meta.input.parse::()?; - let lit = meta.input.parse::()?.value(); - options.rename = Some(lit); - } else if meta.path.is_ident("untagged") { - options.untagged = true; - } - Ok(()) - }) - .unwrap(); - } - - return options; -} - -fn parse_documentation_from_attrs(attrs: &[syn::Attribute]) -> Option { - let mut doc_str = Option::::None; - for attr in attrs { - if attr.path().is_ident("doc") { - // /// ... - // becomes - // #[doc = "..."] - use syn::{Expr::Lit, ExprLit, Lit::Str, Meta, MetaNameValue}; - if let Meta::NameValue(MetaNameValue { - value: - Lit(ExprLit { - lit: Str(ref lit_str), - .. - }), - .. - }) = attr.meta - { - let doc = lit_str.value(); - let doc_str = doc_str.get_or_insert_default(); - doc_str.push_str(doc.trim()); - doc_str.push('\n'); - } - } - } - return doc_str; -} - -struct SettingsKey { - key: Option, - fallback_key: Option, -} - -fn parse_setting_key_attr(attr: &syn::Attribute) -> Option { - if !attr.path().is_ident("settings_key") { - return None; - } - - let mut settings_key = SettingsKey { - key: None, - fallback_key: None, - }; - - let mut found_none = false; - - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("None") { - found_none = true; - } else if meta.path.is_ident("key") { - if settings_key.key.is_some() { - return Err(meta.error("Only one 'group' path can be specified")); - } - meta.input.parse::()?; - let lit: LitStr = meta.input.parse()?; - settings_key.key = Some(lit.value()); - } else if meta.path.is_ident("fallback_key") { - if found_none { - return Err(meta.error("Cannot specify 'fallback_key' and 'None'")); - } - - if settings_key.fallback_key.is_some() { - return Err(meta.error("Only one 'fallback_key' can be specified")); - } - - meta.input.parse::()?; - let lit: LitStr = meta.input.parse()?; - settings_key.fallback_key = Some(lit.value()); - } - Ok(()) - }) - .unwrap_or_else(|e| panic!("in #[settings_key] attribute: {}", e)); - - if found_none && settings_key.fallback_key.is_some() { - panic!("in #[settings_key] attribute: Cannot specify 'None' and 'fallback_key'"); - } - if found_none && settings_key.key.is_some() { - panic!("in #[settings_key] attribute: Cannot specify 'None' and 'key'"); - } - if !found_none && settings_key.key.is_none() { - panic!("in #[settings_key] attribute: 'key' must be specified"); - } - - return Some(settings_key); -} - -#[proc_macro_derive(SettingsKey, attributes(settings_key))] -pub fn derive_settings_key(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = &input.ident; - - // Handle generic parameters if present - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let mut settings_key = Option::::None; - - for attr in &input.attrs { - let parsed_settings_key = parse_setting_key_attr(attr); - if parsed_settings_key.is_some() && settings_key.is_some() { - panic!("Duplicate #[settings_key] attribute"); - } - settings_key = settings_key.or(parsed_settings_key); - } - - let Some(SettingsKey { key, fallback_key }) = settings_key else { - panic!("Missing #[settings_key] attribute"); - }; - - let key = key.map_or_else(|| quote! {None}, |key| quote! {Some(#key)}); - let fallback_key = fallback_key.map_or_else( - || quote! {None}, - |fallback_key| quote! {Some(#fallback_key)}, - ); - - let expanded = quote! { - impl #impl_generics settings::SettingsKey for #name #ty_generics #where_clause { - const KEY: Option<&'static str> = #key; - - const FALLBACK_KEY: Option<&'static str> = #fallback_key; - }; - }; - - proc_macro::TokenStream::from(expanded) -} - -#[cfg(test)] -mod tests { - use syn::{Attribute, parse_quote}; - - use super::*; - - #[test] - fn test_extract_key() { - let input: Attribute = parse_quote!( - #[settings_key(key = "my_key")] - ); - let settings_key = parse_setting_key_attr(&input).unwrap(); - assert_eq!(settings_key.key, Some("my_key".to_string())); - assert_eq!(settings_key.fallback_key, None); - } - - #[test] - fn test_empty_key() { - let input: Attribute = parse_quote!( - #[settings_key(None)] - ); - let settings_key = parse_setting_key_attr(&input).unwrap(); - assert_eq!(settings_key.key, None); - assert_eq!(settings_key.fallback_key, None); - } -} diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 9aa88c65a79032e96c9fe2951b2b37434be6927b..91a65f386fdb21556ea7fac8c2c3d26187f162c8 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -13,7 +13,6 @@ use settings::{ }; use task::Shell; use theme::FontFamilyName; -use util::MergeFrom; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Toolbar { @@ -73,7 +72,7 @@ fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell { } impl settings::Settings for TerminalSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let content = content.terminal.clone().unwrap(); TerminalSettings { shell: settings_shell_to_task_shell(content.shell.unwrap()), @@ -108,80 +107,12 @@ impl settings::Settings for TerminalSettings { breadcrumbs: content.toolbar.unwrap().breadcrumbs.unwrap(), }, scrollbar: ScrollbarSettings { - show: content.scrollbar.unwrap().show.flatten(), + show: content.scrollbar.unwrap().show, }, minimum_contrast: content.minimum_contrast.unwrap(), } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(content) = &content.terminal else { - return; - }; - self.shell - .merge_from(&content.shell.clone().map(settings_shell_to_task_shell)); - self.working_directory - .merge_from(&content.working_directory); - if let Some(font_size) = content.font_size.map(px) { - self.font_size = Some(font_size) - } - if let Some(font_family) = content.font_family.clone() { - self.font_family = Some(font_family); - } - if let Some(fallbacks) = content.font_fallbacks.clone() { - self.font_fallbacks = Some(FontFallbacks::from_fonts( - fallbacks - .into_iter() - .map(|family| family.0.to_string()) - .collect(), - )) - } - if let Some(font_features) = content.font_features.clone() { - self.font_features = Some(font_features) - } - if let Some(font_weight) = content.font_weight { - self.font_weight = Some(FontWeight(font_weight)); - } - self.line_height.merge_from(&content.line_height); - if let Some(env) = &content.env { - for (key, value) in env { - self.env.insert(key.clone(), value.clone()); - } - } - if let Some(cursor_shape) = content.cursor_shape { - self.cursor_shape = Some(cursor_shape.into()) - } - self.blinking.merge_from(&content.blinking); - self.alternate_scroll.merge_from(&content.alternate_scroll); - self.option_as_meta.merge_from(&content.option_as_meta); - self.copy_on_select.merge_from(&content.copy_on_select); - self.keep_selection_on_copy - .merge_from(&content.keep_selection_on_copy); - self.button.merge_from(&content.button); - self.dock.merge_from(&content.dock); - self.default_width - .merge_from(&content.default_width.map(px)); - self.default_height - .merge_from(&content.default_height.map(px)); - self.detect_venv.merge_from(&content.detect_venv); - if let Some(max_scroll_history_lines) = content.max_scroll_history_lines { - self.max_scroll_history_lines = Some(max_scroll_history_lines) - } - self.toolbar.breadcrumbs.merge_from( - &content - .toolbar - .as_ref() - .and_then(|toolbar| toolbar.breadcrumbs), - ); - self.scrollbar.show.merge_from( - &content - .scrollbar - .as_ref() - .and_then(|scrollbar| scrollbar.show), - ); - self.minimum_contrast.merge_from(&content.minimum_contrast); - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) { let mut default = TerminalSettingsContent::default(); let current = content.terminal.as_mut().unwrap_or(&mut default); diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 8c51b991ccc8580d2be3d38273a7c11118b37d52..9f84b523af48e5e2645ea7b850f526db2c2ebb7f 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -24,7 +24,6 @@ fs.workspace = true futures.workspace = true gpui.workspace = true indexmap.workspace = true -inventory.workspace = true log.workspace = true palette = { workspace = true, default-features = false, features = ["std"] } parking_lot.workspace = true diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 2efaa4f20c316952a300e669e8b93eee8e5bad39..e8ae1eed3cd7ca49ec946645160b98732be83884 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -7,17 +7,16 @@ use crate::{ use collections::HashMap; use derive_more::{Deref, DerefMut}; use gpui::{ - App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, SharedString, - Subscription, Window, px, + App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, Subscription, Window, + px, }; use refineable::Refineable; -use schemars::{JsonSchema, json_schema}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub use settings::{FontFamilyName, IconThemeName, ThemeMode, ThemeName}; -use settings::{ParameterizedJsonSchema, Settings, SettingsContent}; +use settings::{Settings, SettingsContent}; use std::sync::Arc; -use util::schemars::replace_subschema; -use util::{MergeFrom, ResultExt as _}; +use util::ResultExt as _; const MIN_FONT_SIZE: Pixels = px(6.0); const MAX_FONT_SIZE: Pixels = px(100.0); @@ -270,45 +269,6 @@ pub struct AgentFontSize(Pixels); impl Global for AgentFontSize {} -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, _params, cx| { - replace_subschema::(generator, || json_schema!({ - "type": "string", - "enum": ThemeRegistry::global(cx).list_names(), - })) - } - } -} - -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, _params, cx| { - replace_subschema::(generator, || json_schema!({ - "type": "string", - "enum": ThemeRegistry::global(cx) - .list_icon_themes() - .into_iter() - .map(|icon_theme| icon_theme.name) - .collect::>(), - })) - } - } -} - -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - replace_subschema::(generator, || { - json_schema!({ - "type": "string", - "enum": params.font_names, - }) - }) - } - } -} - /// Represents the selection of a theme, which can be either static or dynamic. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(untagged)] @@ -807,20 +767,20 @@ pub fn font_fallbacks_from_settings( } impl settings::Settings for ThemeSettings { - fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, cx: &mut App) -> Self { let content = &content.theme; // todo(settings_refactor). This should *not* require cx... let themes = ThemeRegistry::default_global(cx); let system_appearance = SystemAppearance::default_global(cx); let theme_selection: ThemeSelection = content.theme.clone().unwrap().into(); let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into(); - Self { - ui_font_size: content.ui_font_size.unwrap().into(), + let mut this = Self { + ui_font_size: clamp_font_size(content.ui_font_size.unwrap().into()), ui_font: Font { family: content.ui_font_family.as_ref().unwrap().0.clone().into(), features: content.ui_font_features.clone().unwrap(), fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()), - weight: content.ui_font_weight.map(FontWeight).unwrap(), + weight: clamp_font_weight(content.ui_font_weight.unwrap()), style: Default::default(), }, buffer_font: Font { @@ -833,12 +793,12 @@ impl settings::Settings for ThemeSettings { .into(), features: content.buffer_font_features.clone().unwrap(), fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()), - weight: content.buffer_font_weight.map(FontWeight).unwrap(), + weight: clamp_font_weight(content.buffer_font_weight.unwrap()), style: FontStyle::default(), }, - buffer_font_size: content.buffer_font_size.unwrap().into(), + buffer_font_size: clamp_font_size(content.buffer_font_size.unwrap().into()), buffer_line_height: content.buffer_line_height.unwrap().into(), - agent_font_size: content.agent_font_size.flatten().map(Into::into), + agent_font_size: content.agent_font_size.map(Into::into), active_theme: themes .get(theme_selection.theme(*system_appearance)) .or(themes.get(&zed_default_dark().name)) @@ -852,110 +812,10 @@ impl settings::Settings for ThemeSettings { .unwrap(), icon_theme_selection: Some(icon_theme_selection), ui_density: content.ui_density.unwrap_or_default().into(), - unnecessary_code_fade: content.unnecessary_code_fade.unwrap(), - } - } - - fn refine(&mut self, content: &SettingsContent, cx: &mut App) { - let value = &content.theme; - - let themes = ThemeRegistry::default_global(cx); - let system_appearance = SystemAppearance::default_global(cx); - - self.ui_density - .merge_from(&value.ui_density.map(Into::into)); - - if let Some(value) = value.buffer_font_family.clone() { - self.buffer_font.family = value.0.into(); - } - if let Some(value) = value.buffer_font_features.clone() { - self.buffer_font.features = value; - } - if let Some(value) = value.buffer_font_fallbacks.clone() { - self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value)); - } - if let Some(value) = value.buffer_font_weight { - self.buffer_font.weight = clamp_font_weight(value); - } - - if let Some(value) = value.ui_font_family.clone() { - self.ui_font.family = value.0.into(); - } - if let Some(value) = value.ui_font_features.clone() { - self.ui_font.features = value; - } - if let Some(value) = value.ui_font_fallbacks.clone() { - self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value)); - } - if let Some(value) = value.ui_font_weight { - self.ui_font.weight = clamp_font_weight(value); - } - - if let Some(value) = &value.theme { - self.theme_selection = Some(value.clone().into()); - - let theme_name = self - .theme_selection - .as_ref() - .unwrap() - .theme(*system_appearance); - - match themes.get(theme_name) { - Ok(theme) => { - self.active_theme = theme; - } - Err(err @ ThemeNotFoundError(_)) => { - if themes.extensions_loaded() { - log::error!("{err}"); - } - } - } - } - - self.experimental_theme_overrides - .clone_from(&value.experimental_theme_overrides); - self.theme_overrides.clone_from(&value.theme_overrides); - - self.apply_theme_overrides(); - - if let Some(value) = &value.icon_theme { - self.icon_theme_selection = Some(value.clone().into()); - - let icon_theme_name = self - .icon_theme_selection - .as_ref() - .unwrap() - .icon_theme(*system_appearance); - - match themes.get_icon_theme(icon_theme_name) { - Ok(icon_theme) => { - self.active_icon_theme = icon_theme; - } - Err(err @ IconThemeNotFoundError(_)) => { - if themes.extensions_loaded() { - log::error!("{err}"); - } - } - } - } - - self.ui_font_size - .merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size)); - self.buffer_font_size - .merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size)); - self.agent_font_size.merge_from( - &value - .agent_font_size - .map(|value| value.map(Into::into).map(clamp_font_size)), - ); - - self.buffer_line_height - .merge_from(&value.buffer_line_height.map(Into::into)); - - // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely. - self.unnecessary_code_fade - .merge_from(&value.unnecessary_code_fade); - self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9); + unnecessary_code_fade: content.unnecessary_code_fade.unwrap().clamp(0.0, 0.9), + }; + this.apply_theme_overrides(); + this } fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 670d085756c5e27c45f8640e2d053a7f6929aead..41d74de611dd8b99f82b6a1a723c85c27b4a2d19 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -1,7 +1,6 @@ pub use settings::TitleBarVisibility; use settings::{Settings, SettingsContent}; use ui::App; -use util::MergeFrom; #[derive(Copy, Clone, Debug)] pub struct TitleBarSettings { @@ -16,7 +15,7 @@ pub struct TitleBarSettings { } impl Settings for TitleBarSettings { - fn from_defaults(s: &SettingsContent, _: &mut App) -> Self { + fn from_settings(s: &SettingsContent, _: &mut App) -> Self { let content = s.title_bar.clone().unwrap(); TitleBarSettings { show: content.show.unwrap(), @@ -29,24 +28,4 @@ impl Settings for TitleBarSettings { show_menus: content.show_menus.unwrap(), } } - - fn refine(&mut self, s: &SettingsContent, _: &mut App) { - let Some(content) = &s.title_bar else { - return; - }; - - self.show.merge_from(&content.show); - self.show_branch_icon.merge_from(&content.show_branch_icon); - self.show_onboarding_banner - .merge_from(&content.show_onboarding_banner); - self.show_user_picture - .merge_from(&content.show_user_picture); - self.show_branch_name.merge_from(&content.show_branch_name); - self.show_project_items - .merge_from(&content.show_project_items); - self.show_sign_in.merge_from(&content.show_sign_in); - self.show_menus.merge_from(&content.show_menus); - } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) {} } diff --git a/crates/util/src/schemars.rs b/crates/util/src/schemars.rs index 22e0570cdb85efa82904153eda619b84b430eb61..9314eda4ac4d5003d7186c3115137e2e54c66794 100644 --- a/crates/util/src/schemars.rs +++ b/crates/util/src/schemars.rs @@ -18,9 +18,8 @@ pub fn replace_subschema( let schema_name = T::schema_name(); let definitions = generator.definitions_mut(); assert!(!definitions.contains_key(&format!("{schema_name}2"))); - if definitions.contains_key(schema_name.as_ref()) { - definitions.insert(schema_name.to_string(), schema().to_value()); - } + assert!(definitions.contains_key(schema_name.as_ref())); + definitions.insert(schema_name.to_string(), schema().to_value()); schemars::Schema::new_ref(format!("{DEFS_PATH}{schema_name}")) } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 3f3fcd0412e636c60669a6972b3c42bc0c0d7cef..ee18093784e4b0f2b78db9240d918685b6f01b6f 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1390,21 +1390,3 @@ Line 3"# assert_eq!(result[1], (10..15, "world")); // '🦀' is 4 bytes } } - -pub fn refine(dest: &mut T, src: &Option) { - if let Some(src) = src { - *dest = src.clone() - } -} - -pub trait MergeFrom: Sized + Clone { - fn merge_from(&mut self, src: &Option); -} - -impl MergeFrom for T { - fn merge_from(&mut self, src: &Option) { - if let Some(src) = src { - *self = src.clone(); - } - } -} diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 309806be02a1e283770276c26ad544af2bcedcba..9e7fb4a564751335db7ba6fe2afe61563ea0f161 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -45,7 +45,6 @@ use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; use theme::ThemeSettings; use ui::{IntoElement, SharedString, px}; -use util::MergeFrom; use vim_mode_setting::HelixModeSetting; use vim_mode_setting::VimModeSetting; use workspace::{self, Pane, Workspace}; @@ -1845,7 +1844,7 @@ impl From for Mode { } impl Settings for VimSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let vim = content.vim.clone().unwrap(); Self { default_mode: vim.default_mode.unwrap().into(), @@ -1857,29 +1856,4 @@ impl Settings for VimSettings { cursor_shape: vim.cursor_shape.unwrap().into(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(vim) = content.vim.as_ref() else { - return; - }; - self.default_mode - .merge_from(&vim.default_mode.map(Into::into)); - self.toggle_relative_line_numbers - .merge_from(&vim.toggle_relative_line_numbers); - self.use_system_clipboard - .merge_from(&vim.use_system_clipboard); - self.use_smartcase_find.merge_from(&vim.use_smartcase_find); - self.custom_digraphs.merge_from(&vim.custom_digraphs); - self.highlight_on_yank_duration - .merge_from(&vim.highlight_on_yank_duration); - self.cursor_shape - .merge_from(&vim.cursor_shape.map(Into::into)); - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - // TODO: translate vim extension settings - } } diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs index 7c0a21e443fe908191bf3cff9d557a1bf38971b9..c82109f6b1f653c29942430fbf1fc557c09270fd 100644 --- a/crates/vim_mode_setting/src/vim_mode_setting.rs +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -16,16 +16,10 @@ pub fn init(cx: &mut App) { pub struct VimModeSetting(pub bool); impl Settings for VimModeSetting { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { Self(content.vim_mode.unwrap()) } - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - if let Some(vim_mode) = content.vim_mode { - self.0 = vim_mode; - } - } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _content: &mut SettingsContent) { // TODO: could possibly check if any of the `vim.` keys are set? } @@ -34,15 +28,9 @@ impl Settings for VimModeSetting { pub struct HelixModeSetting(pub bool); impl Settings for HelixModeSetting { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { Self(content.helix_mode.unwrap()) } - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - if let Some(helix_mode) = content.helix_mode { - self.0 = helix_mode; - } - } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index da4e557776072e33bd691c38d73b0c6623ac5a4c..83cadf5c6aa8c32630fb88cde7c75185b690437a 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -30,7 +30,7 @@ use std::{ }; use theme::Theme; use ui::{Color, Icon, IntoElement, Label, LabelCommon}; -use util::{MergeFrom as _, ResultExt}; +use util::ResultExt; pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); @@ -65,7 +65,7 @@ pub struct PreviewTabsSettings { } impl Settings for ItemSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let tabs = content.tabs.as_ref().unwrap(); Self { git_status: tabs.git_status.unwrap(), @@ -77,18 +77,6 @@ impl Settings for ItemSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(tabs) = content.tabs.as_ref() else { - return; - }; - self.git_status.merge_from(&tabs.git_status); - self.close_position.merge_from(&tabs.close_position); - self.activate_on_close.merge_from(&tabs.activate_on_close); - self.file_icons.merge_from(&tabs.file_icons); - self.show_diagnostics.merge_from(&tabs.show_diagnostics); - self.show_close_button.merge_from(&tabs.show_close_button); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, @@ -125,7 +113,7 @@ impl Settings for ItemSettings { } impl Settings for PreviewTabsSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let preview_tabs = content.preview_tabs.as_ref().unwrap(); Self { enabled: preview_tabs.enabled.unwrap(), @@ -136,18 +124,6 @@ impl Settings for PreviewTabsSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(preview_tabs) = content.preview_tabs.as_ref() else { - return; - }; - - self.enabled.merge_from(&preview_tabs.enabled); - self.enable_preview_from_file_finder - .merge_from(&preview_tabs.enable_preview_from_file_finder); - self.enable_preview_from_code_navigation - .merge_from(&preview_tabs.enable_preview_from_code_navigation); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 30bebb7c18b1f03e5bf59425026aea5a0f4bd552..dfef0bd77ded79b4a1c3037f1506f64d4c0fe54c 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -10,7 +10,6 @@ pub use settings::{ BottomDockLayout, PaneSplitDirectionHorizontal, PaneSplitDirectionVertical, RestoreOnStartupBehavior, }; -use util::MergeFrom as _; pub struct WorkspaceSettings { pub active_pane_modifiers: ActivePanelModifiers, @@ -63,7 +62,7 @@ pub struct TabBarSettings { } impl Settings for WorkspaceSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let workspace = &content.workspace; Self { active_pane_modifiers: ActivePanelModifiers { @@ -111,65 +110,6 @@ impl Settings for WorkspaceSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let workspace = &content.workspace; - if let Some(border_size) = workspace - .active_pane_modifiers - .and_then(|modifier| modifier.border_size) - { - self.active_pane_modifiers.border_size = Some(border_size); - } - - if let Some(inactive_opacity) = workspace - .active_pane_modifiers - .and_then(|modifier| modifier.inactive_opacity) - { - self.active_pane_modifiers.inactive_opacity = Some(inactive_opacity); - } - - self.bottom_dock_layout - .merge_from(&workspace.bottom_dock_layout); - self.pane_split_direction_horizontal - .merge_from(&workspace.pane_split_direction_horizontal); - self.pane_split_direction_vertical - .merge_from(&workspace.pane_split_direction_vertical); - self.centered_layout.merge_from(&workspace.centered_layout); - self.confirm_quit.merge_from(&workspace.confirm_quit); - self.show_call_status_icon - .merge_from(&workspace.show_call_status_icon); - self.autosave.merge_from(&workspace.autosave); - self.restore_on_startup - .merge_from(&workspace.restore_on_startup); - self.restore_on_file_reopen - .merge_from(&workspace.restore_on_file_reopen); - self.drop_target_size - .merge_from(&workspace.drop_target_size); - self.use_system_path_prompts - .merge_from(&workspace.use_system_path_prompts); - self.use_system_prompts - .merge_from(&workspace.use_system_prompts); - self.command_aliases - .extend(workspace.command_aliases.clone()); - if let Some(max_tabs) = workspace.max_tabs { - self.max_tabs = Some(max_tabs); - } - self.when_closing_with_no_tabs - .merge_from(&workspace.when_closing_with_no_tabs); - self.on_last_window_closed - .merge_from(&workspace.on_last_window_closed); - self.resize_all_panels_in_dock.merge_from( - &workspace - .resize_all_panels_in_dock - .as_ref() - .map(|resize| resize.clone().into_iter().map(Into::into).collect()), - ); - self.close_on_file_delete - .merge_from(&workspace.close_on_file_delete); - self.use_system_window_tabs - .merge_from(&workspace.use_system_window_tabs); - self.zoomed_padding.merge_from(&workspace.zoomed_padding); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, @@ -257,7 +197,7 @@ impl Settings for WorkspaceSettings { } impl Settings for TabBarSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let tab_bar = content.tab_bar.clone().unwrap(); TabBarSettings { show: tab_bar.show.unwrap(), @@ -266,17 +206,6 @@ impl Settings for TabBarSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(tab_bar) = &content.tab_bar else { - return; - }; - self.show.merge_from(&tab_bar.show); - self.show_nav_history_buttons - .merge_from(&tab_bar.show_nav_history_buttons); - self.show_tab_bar_buttons - .merge_from(&tab_bar.show_tab_bar_buttons); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 5093988ac2ac66682aba9447a6347ecfc54ecf76..2d4246236e8c696661c1ed07544c1d73058fb1bc 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -31,11 +31,11 @@ impl WorktreeSettings { } impl Settings for WorktreeSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let worktree = content.project.worktree.clone(); let file_scan_exclusions = worktree.file_scan_exclusions.unwrap(); let file_scan_inclusions = worktree.file_scan_inclusions.unwrap(); - let private_files = worktree.private_files.unwrap(); + let private_files = worktree.private_files.unwrap().0; let parsed_file_scan_inclusions: Vec = file_scan_inclusions .iter() .flat_map(|glob| { @@ -49,54 +49,16 @@ impl Settings for WorktreeSettings { Self { project_name: None, file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions") - .unwrap(), + .log_err() + .unwrap_or_default(), file_scan_inclusions: path_matchers( parsed_file_scan_inclusions, "file_scan_inclusions", ) .unwrap(), - private_files: path_matchers(private_files, "private_files").unwrap(), - } - } - - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let worktree = &content.project.worktree; - - if let Some(project_name) = worktree.project_name.clone() { - self.project_name = Some(project_name); - } - - if let Some(mut private_files) = worktree.private_files.clone() { - let sources = self.private_files.sources(); - private_files.extend_from_slice(sources); - if let Some(matchers) = path_matchers(private_files, "private_files").log_err() { - self.private_files = matchers; - } - } - - if let Some(file_scan_exclusions) = worktree.file_scan_exclusions.clone() { - if let Some(matchers) = - path_matchers(file_scan_exclusions, "file_scan_exclusions").log_err() - { - self.file_scan_exclusions = matchers - } - } - - if let Some(file_scan_inclusions) = worktree.file_scan_inclusions.clone() { - let parsed_file_scan_inclusions: Vec = file_scan_inclusions - .iter() - .flat_map(|glob| { - Path::new(glob) - .ancestors() - .map(|a| a.to_string_lossy().into()) - }) - .filter(|p: &String| !p.is_empty()) - .collect(); - if let Some(matchers) = - path_matchers(parsed_file_scan_inclusions, "file_scan_inclusions").log_err() - { - self.file_scan_inclusions = matchers - } + private_files: path_matchers(private_files, "private_files") + .log_err() + .unwrap_or_default(), } } diff --git a/crates/zlog_settings/src/zlog_settings.rs b/crates/zlog_settings/src/zlog_settings.rs index 0d464b872067683e7fad8db6f4049eff92d5ed8b..cb564fcff3f024d37f0126c8870b436e449a0e1d 100644 --- a/crates/zlog_settings/src/zlog_settings.rs +++ b/crates/zlog_settings/src/zlog_settings.rs @@ -24,17 +24,11 @@ pub struct ZlogSettings { } impl Settings for ZlogSettings { - fn from_defaults(content: &settings::SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _: &mut App) -> Self { ZlogSettings { scopes: content.log.clone().unwrap(), } } - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if let Some(log) = &content.log { - self.scopes.extend(log.clone()); - } - } - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} } From f18b19a73e61cfbbf05c6b427dd8f5b1e82fa868 Mon Sep 17 00:00:00 2001 From: tidely <43219534+tidely@users.noreply.github.com> Date: Fri, 19 Sep 2025 02:39:26 +0300 Subject: [PATCH 132/721] http_client: Relax lifetime bounds and add fluent builder methods (#38448) `HttpClient`: Relaxes the lifetime bound to `&self` in `get`/`post` by returning the `self.send` future directly. This makes both methods return `'static` futures without extra boxing. `HttpRequestExt`: Added fluent builder methods to `HttpRequestExt` inspired by the `gpui::FluentBuilder` trait. Release Notes: - N/A --- .../cloud_api_client/src/cloud_api_client.rs | 21 +++++---- crates/copilot/src/copilot_chat.rs | 13 +++-- crates/http_client/src/http_client.rs | 35 ++++++++++---- crates/language_models/src/provider/cloud.rs | 17 +++---- crates/ollama/src/ollama.rs | 47 +++++++++---------- 5 files changed, 72 insertions(+), 61 deletions(-) diff --git a/crates/cloud_api_client/src/cloud_api_client.rs b/crates/cloud_api_client/src/cloud_api_client.rs index 7fd96fcef0e8fd764bbcaa8ab59a9666095f9db9..53b2b16a6a7c9447face6daa199bc4b2125445b9 100644 --- a/crates/cloud_api_client/src/cloud_api_client.rs +++ b/crates/cloud_api_client/src/cloud_api_client.rs @@ -9,7 +9,7 @@ use futures::AsyncReadExt as _; use gpui::{App, Task}; use gpui_tokio::Tokio; use http_client::http::request; -use http_client::{AsyncBody, HttpClientWithUrl, Method, Request, StatusCode}; +use http_client::{AsyncBody, HttpClientWithUrl, HttpRequestExt, Method, Request, StatusCode}; use parking_lot::RwLock; use yawc::WebSocket; @@ -119,15 +119,16 @@ impl CloudApiClient { &self, system_id: Option, ) -> Result { - let mut request_builder = Request::builder().method(Method::POST).uri( - self.http_client - .build_zed_cloud_url("/client/llm_tokens", &[])? - .as_ref(), - ); - - if let Some(system_id) = system_id { - request_builder = request_builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id); - } + let request_builder = Request::builder() + .method(Method::POST) + .uri( + self.http_client + .build_zed_cloud_url("/client/llm_tokens", &[])? + .as_ref(), + ) + .when_some(system_id, |builder, system_id| { + builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id) + }); let request = self.build_request(request_builder, AsyncBody::default())?; diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs index ccd8f09613eec54f2d30b619f142d111bf2a3497..a6758ce53c0aa18d04dcd376c2e0afb93add6ab5 100644 --- a/crates/copilot/src/copilot_chat.rs +++ b/crates/copilot/src/copilot_chat.rs @@ -10,6 +10,7 @@ use fs::Fs; use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream}; use gpui::WeakEntity; use gpui::{App, AsyncApp, Global, prelude::*}; +use http_client::HttpRequestExt; use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; use itertools::Itertools; use paths::home_dir; @@ -741,7 +742,7 @@ async fn stream_completion( let request_initiator = if is_user_initiated { "user" } else { "agent" }; - let mut request_builder = HttpRequest::builder() + let request_builder = HttpRequest::builder() .method(Method::POST) .uri(completion_url.as_ref()) .header( @@ -754,12 +755,10 @@ async fn stream_completion( .header("Authorization", format!("Bearer {}", api_key)) .header("Content-Type", "application/json") .header("Copilot-Integration-Id", "vscode-chat") - .header("X-Initiator", request_initiator); - - if is_vision_request { - request_builder = - request_builder.header("Copilot-Vision-Request", is_vision_request.to_string()); - } + .header("X-Initiator", request_initiator) + .when(is_vision_request, |builder| { + builder.header("Copilot-Vision-Request", is_vision_request.to_string()) + }); let is_streaming = request.stream; diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index 1429b7bf941fab5b1b508b977e898b8e153942d1..0bbb7ce037fcda014b346556202256b99e832529 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -28,6 +28,25 @@ pub enum RedirectPolicy { pub struct FollowRedirects(pub bool); pub trait HttpRequestExt { + /// Conditionally modify self with the given closure. + fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self + where + Self: Sized, + { + if condition { then(self) } else { self } + } + + /// Conditionally unwrap and modify self with the given closure, if the given option is Some. + fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self + where + Self: Sized, + { + match option { + Some(value) => then(self, value), + None => self, + } + } + /// Whether or not to follow redirects fn follow_redirects(self, follow: RedirectPolicy) -> Self; } @@ -48,12 +67,12 @@ pub trait HttpClient: 'static + Send + Sync { req: http::Request, ) -> BoxFuture<'static, anyhow::Result>>; - fn get<'a>( - &'a self, + fn get( + &self, uri: &str, body: AsyncBody, follow_redirects: bool, - ) -> BoxFuture<'a, anyhow::Result>> { + ) -> BoxFuture<'static, anyhow::Result>> { let request = Builder::new() .uri(uri) .follow_redirects(if follow_redirects { @@ -64,16 +83,16 @@ pub trait HttpClient: 'static + Send + Sync { .body(body); match request { - Ok(request) => Box::pin(async move { self.send(request).await }), + Ok(request) => self.send(request), Err(e) => Box::pin(async move { Err(e.into()) }), } } - fn post_json<'a>( - &'a self, + fn post_json( + &self, uri: &str, body: AsyncBody, - ) -> BoxFuture<'a, anyhow::Result>> { + ) -> BoxFuture<'static, anyhow::Result>> { let request = Builder::new() .uri(uri) .method(Method::POST) @@ -81,7 +100,7 @@ pub trait HttpClient: 'static + Send + Sync { .body(body); match request { - Ok(request) => Box::pin(async move { self.send(request).await }), + Ok(request) => self.send(request), Err(e) => Box::pin(async move { Err(e.into()) }), } } diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index c62a6989501a71e444b07992bff0cbe1a1bbd6d6..40958af77535b34e2d68afde49e7ab97f07f911f 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -19,7 +19,7 @@ use gpui::{ AnyElement, AnyView, App, AsyncApp, Context, Entity, SemanticVersion, Subscription, Task, }; use http_client::http::{HeaderMap, HeaderValue}; -use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode}; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Response, StatusCode}; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, @@ -391,20 +391,17 @@ impl CloudLanguageModel { let mut refreshed_token = false; loop { - let request_builder = http_client::Request::builder() + let request = http_client::Request::builder() .method(Method::POST) - .uri(http_client.build_zed_llm_url("/completions", &[])?.as_ref()); - let request_builder = if let Some(app_version) = app_version { - request_builder.header(ZED_VERSION_HEADER_NAME, app_version.to_string()) - } else { - request_builder - }; - - let request = request_builder + .uri(http_client.build_zed_llm_url("/completions", &[])?.as_ref()) + .when_some(app_version, |builder, app_version| { + builder.header(ZED_VERSION_HEADER_NAME, app_version.to_string()) + }) .header("Content-Type", "application/json") .header("Authorization", format!("Bearer {token}")) .header(CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, "true") .body(serde_json::to_string(&body)?.into())?; + let mut response = http_client.send(request).await?; let status = response.status(); if status.is_success() { diff --git a/crates/ollama/src/ollama.rs b/crates/ollama/src/ollama.rs index dced37e0fc1e19e61bba5e14010812f08fe3a1e5..48124f9625bf28a646ec4e9dc194bb1dd0df4c57 100644 --- a/crates/ollama/src/ollama.rs +++ b/crates/ollama/src/ollama.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream}; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, http}; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use serde::{Deserialize, Serialize}; use serde_json::Value; pub use settings::KeepAlive; @@ -261,16 +261,15 @@ pub async fn stream_chat_completion( request: ChatRequest, ) -> Result>> { let uri = format!("{api_url}/api/chat"); - let mut request_builder = http::Request::builder() + let request = HttpRequest::builder() .method(Method::POST) .uri(uri) - .header("Content-Type", "application/json"); - - if let Some(api_key) = api_key { - request_builder = request_builder.header("Authorization", format!("Bearer {api_key}")) - } + .header("Content-Type", "application/json") + .when_some(api_key, |builder, api_key| { + builder.header("Authorization", format!("Bearer {api_key}")) + }) + .body(AsyncBody::from(serde_json::to_string(&request)?))?; - let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; if response.status().is_success() { let reader = BufReader::new(response.into_body()); @@ -300,16 +299,14 @@ pub async fn get_models( _: Option, ) -> Result> { let uri = format!("{api_url}/api/tags"); - let mut request_builder = HttpRequest::builder() + let request = HttpRequest::builder() .method(Method::GET) .uri(uri) - .header("Accept", "application/json"); - - if let Some(api_key) = api_key { - request_builder = request_builder.header("Authorization", format!("Bearer {api_key}")); - } - - let request = request_builder.body(AsyncBody::default())?; + .header("Accept", "application/json") + .when_some(api_key, |builder, api_key| { + builder.header("Authorization", format!("Bearer {api_key}")) + }) + .body(AsyncBody::default())?; let mut response = client.send(request).await?; @@ -335,18 +332,16 @@ pub async fn show_model( model: &str, ) -> Result { let uri = format!("{api_url}/api/show"); - let mut request_builder = HttpRequest::builder() + let request = HttpRequest::builder() .method(Method::POST) .uri(uri) - .header("Content-Type", "application/json"); - - if let Some(api_key) = api_key { - request_builder = request_builder.header("Authorization", format!("Bearer {api_key}")) - } - - let request = request_builder.body(AsyncBody::from( - serde_json::json!({ "model": model }).to_string(), - ))?; + .header("Content-Type", "application/json") + .when_some(api_key, |builder, api_key| { + builder.header("Authorization", format!("Bearer {api_key}")) + }) + .body(AsyncBody::from( + serde_json::json!({ "model": model }).to_string(), + ))?; let mut response = client.send(request).await?; let mut body = String::new(); From 166b2352f32d7953e663bb5842093970700eae60 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Thu, 18 Sep 2025 19:21:42 -0500 Subject: [PATCH 133/721] Respect user's font-smoothing setting (#38467) #37622 was incorrectly forcing font smoothing to be enabled on macos even when the user had disabled that setting at the OS level. See [this comment](https://github.com/zed-industries/zed/pull/37622#issuecomment-3310030659) for an example of the difference that font smoothing makes. Release Notes: - N/A --- crates/gpui/src/platform/mac/text_system.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 9144b2a23a40bd527e1441cf71adcc2562c33f3c..7f765fbaac80e27f8db4b9c4f2d00de90e991a9a 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -397,7 +397,6 @@ impl MacTextSystemState { .subpixel_variant .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); cx.set_allows_font_smoothing(true); - cx.set_should_smooth_fonts(true); cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); cx.set_gray_fill_color(0.0, 1.0); cx.set_allows_antialiasing(true); From e5e308ba78c3de679b822e50a07b3432a1624ce1 Mon Sep 17 00:00:00 2001 From: Nia Date: Fri, 19 Sep 2025 02:45:59 +0200 Subject: [PATCH 134/721] fuzzy: Fixup atomic ordering (#38468) Hopefully partially addresses some crashes that can be triggered in this code. Release Notes: - N/A --- crates/agent_ui/src/context_picker/file_context_picker.rs | 2 +- crates/assistant_slash_commands/src/diagnostics_command.rs | 2 +- crates/assistant_slash_commands/src/file_command.rs | 2 +- crates/file_finder/src/file_finder.rs | 6 +++--- crates/fuzzy/src/matcher.rs | 2 +- crates/fuzzy/src/paths.rs | 7 +++---- crates/fuzzy/src/strings.rs | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) 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 d64de23f4e42b8a79dc9bdcbc1c2fa9677c09372..d6f2af7083eb4049e168f6409cef22022cbe404b 100644 --- a/crates/agent_ui/src/context_picker/file_context_picker.rs +++ b/crates/agent_ui/src/context_picker/file_context_picker.rs @@ -251,7 +251,7 @@ pub(crate) fn search_files( fuzzy::match_path_sets( candidate_sets.as_slice(), query.as_str(), - None, + &None, false, 100, &cancellation_flag, diff --git a/crates/assistant_slash_commands/src/diagnostics_command.rs b/crates/assistant_slash_commands/src/diagnostics_command.rs index 8b1dbd515cabeb498d2a639387b426527dcda651..dd54565c2abc168bb995325f2ebf930bbde90793 100644 --- a/crates/assistant_slash_commands/src/diagnostics_command.rs +++ b/crates/assistant_slash_commands/src/diagnostics_command.rs @@ -73,7 +73,7 @@ impl DiagnosticsSlashCommand { fuzzy::match_path_sets( candidate_sets.as_slice(), query.as_str(), - None, + &None, false, 100, &cancellation_flag, diff --git a/crates/assistant_slash_commands/src/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs index 261e15bc0ae8b9e886d4d146696db78e5c0c831d..4bf53bad9b5364c7fd488cf74644701c6f176b99 100644 --- a/crates/assistant_slash_commands/src/file_command.rs +++ b/crates/assistant_slash_commands/src/file_command.rs @@ -104,7 +104,7 @@ impl FileSlashCommand { fuzzy::match_path_sets( candidate_sets.as_slice(), query.as_str(), - None, + &None, false, 100, &cancellation_flag, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index eda01466f6dda2f90fbdbd9f92f3cf812b083026..dadd3ea299304e845bbc0f412c3962d14e2006e4 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -886,14 +886,14 @@ impl FileFinderDelegate { .collect::>(); let search_id = util::post_inc(&mut self.search_count); - self.cancel_flag.store(true, atomic::Ordering::Relaxed); + self.cancel_flag.store(true, atomic::Ordering::Release); self.cancel_flag = Arc::new(AtomicBool::new(false)); let cancel_flag = self.cancel_flag.clone(); cx.spawn_in(window, async move |picker, cx| { let matches = fuzzy::match_path_sets( candidate_sets.as_slice(), query.path_query(), - relative_to, + &relative_to, false, 100, &cancel_flag, @@ -902,7 +902,7 @@ impl FileFinderDelegate { .await .into_iter() .map(ProjectPanelOrdMatch); - let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); + let did_cancel = cancel_flag.load(atomic::Ordering::Acquire); picker .update(cx, |picker, cx| { picker diff --git a/crates/fuzzy/src/matcher.rs b/crates/fuzzy/src/matcher.rs index e649d47dd646b80e312e2465f0929f630fecf81f..88253d4848b4b3866b9380256eccf1826213cfd1 100644 --- a/crates/fuzzy/src/matcher.rs +++ b/crates/fuzzy/src/matcher.rs @@ -76,7 +76,7 @@ impl<'a> Matcher<'a> { continue; } - if cancel_flag.load(atomic::Ordering::Relaxed) { + if cancel_flag.load(atomic::Ordering::Acquire) { break; } diff --git a/crates/fuzzy/src/paths.rs b/crates/fuzzy/src/paths.rs index 78030d5f964edb73e0f43f43ad412446dfbc9b34..de6284e957a5320b5eac15ad4ff23a8c4ff5b420 100644 --- a/crates/fuzzy/src/paths.rs +++ b/crates/fuzzy/src/paths.rs @@ -121,7 +121,7 @@ pub fn match_fixed_path_set( pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( candidate_sets: &'a [Set], query: &str, - relative_to: Option>, + relative_to: &Option>, smart_case: bool, max_results: usize, cancel_flag: &AtomicBool, @@ -148,7 +148,6 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( executor .scoped(|scope| { for (segment_idx, results) in segment_results.iter_mut().enumerate() { - let relative_to = relative_to.clone(); scope.spawn(async move { let segment_start = segment_idx * segment_size; let segment_end = segment_start + segment_size; @@ -157,7 +156,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( let mut tree_start = 0; for candidate_set in candidate_sets { - if cancel_flag.load(atomic::Ordering::Relaxed) { + if cancel_flag.load(atomic::Ordering::Acquire) { break; } @@ -209,7 +208,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( }) .await; - if cancel_flag.load(atomic::Ordering::Relaxed) { + if cancel_flag.load(atomic::Ordering::Acquire) { return Vec::new(); } diff --git a/crates/fuzzy/src/strings.rs b/crates/fuzzy/src/strings.rs index 5bd7b66c0b5352370d010a479e85d01177aac8bd..7c866de05c4566c060fa01a362931e1355cd8c37 100644 --- a/crates/fuzzy/src/strings.rs +++ b/crates/fuzzy/src/strings.rs @@ -189,7 +189,7 @@ where }) .await; - if cancel_flag.load(atomic::Ordering::Relaxed) { + if cancel_flag.load(atomic::Ordering::Acquire) { return Vec::new(); } From c826ce6fc6cf6e269e44d21595026d28d6238f63 Mon Sep 17 00:00:00 2001 From: Nia Date: Fri, 19 Sep 2025 03:51:41 +0200 Subject: [PATCH 135/721] markdown: Use the faster hasher (#38469) Micro-optimisation in the markdown crate to use the faster hasher. Release Notes: - N/A --- Cargo.lock | 1 + crates/markdown/Cargo.toml | 1 + crates/markdown/src/markdown.rs | 7 +++--- crates/markdown/src/parser.rs | 32 +++++++++++++++---------- crates/zed/src/zed/component_preview.rs | 2 +- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5654d206701f5efd5d91cd33c9ab456701b1e667..3acfed9bd7cfa8bc2742bb4f006c38a4f65a1f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10418,6 +10418,7 @@ version = "0.1.0" dependencies = [ "assets", "base64 0.22.1", + "collections", "env_logger 0.11.8", "fs", "futures 0.3.31", diff --git a/crates/markdown/Cargo.toml b/crates/markdown/Cargo.toml index 9dfb3fdcd6c38f65357d93e5701cb0b72a6814a7..650338ef4f05485535313e408db64f0b7fe1188d 100644 --- a/crates/markdown/Cargo.toml +++ b/crates/markdown/Cargo.toml @@ -20,6 +20,7 @@ test-support = [ [dependencies] base64.workspace = true +collections.workspace = true futures.workspace = true gpui.workspace = true language.workspace = true diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index c2f8025e32d70cdd9500afdf0a4fc02a334a8521..fdf0f2bbf20190d15b533d02b9f0122746439c89 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -9,8 +9,6 @@ use log::Level; pub use path_range::{LineCol, PathWithRange}; use std::borrow::Cow; -use std::collections::HashMap; -use std::collections::HashSet; use std::iter; use std::mem; use std::ops::Range; @@ -19,6 +17,7 @@ use std::rc::Rc; use std::sync::Arc; use std::time::Duration; +use collections::{HashMap, HashSet}; use gpui::{ AnyElement, App, BorderStyle, Bounds, ClipboardItem, CursorStyle, DispatchPhase, Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, Image, @@ -176,7 +175,7 @@ impl Markdown { options: Options { parse_links_only: false, }, - copied_code_blocks: HashSet::new(), + copied_code_blocks: HashSet::default(), }; this.parse(cx); this @@ -199,7 +198,7 @@ impl Markdown { options: Options { parse_links_only: true, }, - copied_code_blocks: HashSet::new(), + copied_code_blocks: HashSet::default(), }; this.parse(cx); this diff --git a/crates/markdown/src/parser.rs b/crates/markdown/src/parser.rs index d60d34b41e7efc99970f72b15a8ea9c4c79eb6f9..1b4d5b5755c0b825124f37f68466bae7c0838b1a 100644 --- a/crates/markdown/src/parser.rs +++ b/crates/markdown/src/parser.rs @@ -4,7 +4,9 @@ pub use pulldown_cmark::TagEnd as MarkdownTagEnd; use pulldown_cmark::{ Alignment, CowStr, HeadingLevel, LinkType, MetadataBlockKind, Options, Parser, }; -use std::{collections::HashSet, ops::Range, path::Path, sync::Arc}; +use std::{ops::Range, path::Path, sync::Arc}; + +use collections::HashSet; use crate::path_range::PathWithRange; @@ -26,8 +28,8 @@ pub fn parse_markdown( HashSet>, ) { let mut events = Vec::new(); - let mut language_names = HashSet::new(); - let mut language_paths = HashSet::new(); + let mut language_names = HashSet::default(); + let mut language_paths = HashSet::default(); let mut within_link = false; let mut within_metadata = false; let mut parser = Parser::new_ext(text, PARSE_OPTIONS) @@ -579,8 +581,8 @@ mod tests { (30..37, Text), (30..37, End(MarkdownTagEnd::Paragraph)) ], - HashSet::new(), - HashSet::new() + HashSet::default(), + HashSet::default() ) ) } @@ -613,8 +615,8 @@ mod tests { (46..51, Text), (0..51, End(MarkdownTagEnd::Paragraph)) ], - HashSet::new(), - HashSet::new() + HashSet::default(), + HashSet::default() ) ); } @@ -670,8 +672,8 @@ mod tests { (43..53, SubstitutedText("–––––".into())), (0..53, End(MarkdownTagEnd::Paragraph)) ], - HashSet::new(), - HashSet::new() + HashSet::default(), + HashSet::default() ) ) } @@ -695,8 +697,12 @@ mod tests { (8..34, Text), (0..37, End(MarkdownTagEnd::CodeBlock)), ], - HashSet::from(["rust".into()]), - HashSet::new() + { + let mut h = HashSet::default(); + h.insert("rust".into()); + h + }, + HashSet::default() ) ); assert_eq!( @@ -716,8 +722,8 @@ mod tests { (4..16, Text), (4..16, End(MarkdownTagEnd::CodeBlock)) ], - HashSet::new(), - HashSet::new() + HashSet::default(), + HashSet::default() ) ); } diff --git a/crates/zed/src/zed/component_preview.rs b/crates/zed/src/zed/component_preview.rs index 176b176d59b23ed7f605988cf682c9d52dfdb95b..7a287cf3d83f24e7f4d42221bda420053a975860 100644 --- a/crates/zed/src/zed/component_preview.rs +++ b/crates/zed/src/zed/component_preview.rs @@ -216,7 +216,7 @@ impl ComponentPreview { } fn scope_ordered_entries(&self) -> Vec { - use std::collections::HashMap; + use collections::HashMap; let mut scope_groups: HashMap< ComponentScope, From e62dd2a0e584154886ff86cc1e9e3e060558b977 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Sep 2025 22:28:17 -0600 Subject: [PATCH 136/721] Tighten up MergeFrom trait (#38473) Release Notes: - N/A --- crates/language/src/language_settings.rs | 2 +- crates/settings/src/merge_from.rs | 96 ++++++++++--------- crates/settings/src/settings_content.rs | 24 +++-- .../settings/src/settings_content/language.rs | 26 ++--- crates/settings/src/settings_store.rs | 16 ++-- crates/settings_macros/src/settings_macros.rs | 76 +++------------ 6 files changed, 97 insertions(+), 143 deletions(-) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 64744ee99d24a56abb357e0c034e11afa4dae9d0..0e05123033bf92d537eef5eab258db4eac7e7a56 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -582,7 +582,7 @@ impl settings::Settings for AllLanguageSettings { let mut languages = HashMap::default(); for (language_name, settings) in &all_languages.languages.0 { let mut language_settings = all_languages.defaults.clone(); - settings::merge_from::MergeFrom::merge_from(&mut language_settings, Some(settings)); + settings::merge_from::MergeFrom::merge_from(&mut language_settings, settings); languages.insert( LanguageName(language_name.clone()), load_from_content(language_settings), diff --git a/crates/settings/src/merge_from.rs b/crates/settings/src/merge_from.rs index 11c0785bcb466e26de956475fb5bd4f9821c2790..c12994786ff5fadb6686c6ab1b93d9700195eb2a 100644 --- a/crates/settings/src/merge_from.rs +++ b/crates/settings/src/merge_from.rs @@ -1,29 +1,37 @@ -use std::rc::Rc; - /// Trait for recursively merging settings structures. /// -/// This trait allows settings objects to be merged from optional sources, -/// where `None` values are ignored and `Some` values override existing values. +/// When Zed starts it loads settinsg from `default.json` to initialize +/// everything. These may be further refined by loading the user's settings, +/// and any settings profiles; and then further refined by loading any +/// local project settings. +/// +/// The default behaviour of merging is: +/// * For objects with named keys (HashMap, structs, etc.). The values are merged deeply +/// (so if the default settings has languages.JSON.prettier.allowed = true, and the user's settings has +/// languages.JSON.tab_size = 4; the merged settings file will have both settings). +/// * For options, a None value is ignored, but Some values are merged recursively. +/// * For other types (including Vec), a merge overwrites the current value. /// -/// HashMaps, structs and similar types are merged by combining their contents key-wise, -/// but all other types (including Vecs) are last-write-wins. -/// (Though see also ExtendingVec and SaturatingBool) +/// If you want to break the rules you can (e.g. ExtendingVec, or SaturatingBool). #[allow(unused)] pub trait MergeFrom { + /// Merge from a source of the same type. + fn merge_from(&mut self, other: &Self); + /// Merge from an optional source of the same type. - /// If `other` is `None`, no changes are made. - /// If `other` is `Some(value)`, fields from `value` are merged into `self`. - fn merge_from(&mut self, other: Option<&Self>); + fn merge_from_option(&mut self, other: Option<&Self>) { + if let Some(other) = other { + self.merge_from(other); + } + } } macro_rules! merge_from_overwrites { ($($type:ty),+) => { $( impl MergeFrom for $type { - fn merge_from(&mut self, other: Option<&Self>) { - if let Some(value) = other { - *self = value.clone(); - } + fn merge_from(&mut self, other: &Self) { + *self = other.clone(); } } )+ @@ -51,25 +59,41 @@ merge_from_overwrites!( gpui::FontFeatures ); -impl MergeFrom for Vec { - fn merge_from(&mut self, other: Option<&Self>) { - if let Some(other) = other { - *self = other.clone() +impl MergeFrom for Option { + fn merge_from(&mut self, other: &Self) { + let Some(other) = other else { + return; + }; + if let Some(this) = self { + this.merge_from(other); + } else { + self.replace(other.clone()); } } } +impl MergeFrom for Vec { + fn merge_from(&mut self, other: &Self) { + *self = other.clone() + } +} + +impl MergeFrom for Box { + fn merge_from(&mut self, other: &Self) { + self.as_mut().merge_from(other.as_ref()) + } +} + // Implementations for collections that extend/merge their contents impl MergeFrom for collections::HashMap where K: Clone + std::hash::Hash + Eq, V: Clone + MergeFrom, { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; + fn merge_from(&mut self, other: &Self) { for (k, v) in other { if let Some(existing) = self.get_mut(k) { - existing.merge_from(Some(v)); + existing.merge_from(v); } else { self.insert(k.clone(), v.clone()); } @@ -82,11 +106,10 @@ where K: Clone + std::hash::Hash + Eq + Ord, V: Clone + MergeFrom, { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; + fn merge_from(&mut self, other: &Self) { for (k, v) in other { if let Some(existing) = self.get_mut(k) { - existing.merge_from(Some(v)); + existing.merge_from(v); } else { self.insert(k.clone(), v.clone()); } @@ -100,11 +123,10 @@ where // Q: ?Sized + std::hash::Hash + collections::Equivalent + Eq, V: Clone + MergeFrom, { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; + fn merge_from(&mut self, other: &Self) { for (k, v) in other { if let Some(existing) = self.get_mut(k) { - existing.merge_from(Some(v)); + existing.merge_from(v); } else { self.insert(k.clone(), v.clone()); } @@ -116,8 +138,7 @@ impl MergeFrom for collections::BTreeSet where T: Clone + Ord, { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; + fn merge_from(&mut self, other: &Self) { for item in other { self.insert(item.clone()); } @@ -128,8 +149,7 @@ impl MergeFrom for collections::HashSet where T: Clone + std::hash::Hash + Eq, { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; + fn merge_from(&mut self, other: &Self) { for item in other { self.insert(item.clone()); } @@ -137,13 +157,12 @@ where } impl MergeFrom for serde_json::Value { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; + fn merge_from(&mut self, other: &Self) { match (self, other) { (serde_json::Value::Object(this), serde_json::Value::Object(other)) => { for (k, v) in other { if let Some(existing) = this.get_mut(k) { - existing.merge_from(other.get(k)); + existing.merge_from(v); } else { this.insert(k.clone(), v.clone()); } @@ -153,12 +172,3 @@ impl MergeFrom for serde_json::Value { } } } - -impl MergeFrom for Rc { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; - let mut this: T = self.as_ref().clone(); - this.merge_from(Some(other.as_ref())); - *self = Rc::new(this) - } -} diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 2ef42d8ebd730343f749d3e2e48055a2d02819ad..43402cae0e6c723b4cc2e94f28c1ba7d0c61c928 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -827,6 +827,14 @@ pub struct ReplSettingsContent { } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +/// An ExtendingVec in the settings can only accumulate new values. +/// +/// This is useful for things like private files where you only want +/// to allow new values to be added. +/// +/// Consider using a HashMap instead of this type +/// (like auto_install_extensions) so that user settings files can both add +/// and remove values from the set. pub struct ExtendingVec(pub Vec); impl Into> for ExtendingVec { @@ -841,13 +849,15 @@ impl From> for ExtendingVec { } impl merge_from::MergeFrom for ExtendingVec { - fn merge_from(&mut self, other: Option<&Self>) { - if let Some(other) = other { - self.0.extend_from_slice(other.0.as_slice()); - } + fn merge_from(&mut self, other: &Self) { + self.0.extend_from_slice(other.0.as_slice()); } } +/// A SaturatingBool in the settings can only ever be set to true, +/// later attempts to set it to false will be ignored. +/// +/// Used by `disable_ai`. #[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct SaturatingBool(pub bool); @@ -858,9 +868,7 @@ impl From for SaturatingBool { } impl merge_from::MergeFrom for SaturatingBool { - fn merge_from(&mut self, other: Option<&Self>) { - if let Some(other) = other { - self.0 |= other.0 - } + fn merge_from(&mut self, other: &Self) { + self.0 |= other.0 } } diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index ef435d638359825128729d0c024cde8e5c5613c8..6052afee671edba49e05b56ddef147a01866e364 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -34,37 +34,27 @@ pub struct AllLanguageSettingsContent { pub file_types: HashMap, ExtendingVec>, } -fn merge_option(this: &mut Option, other: Option<&T>) { - let Some(other) = other else { return }; - if let Some(this) = this { - this.merge_from(Some(other)); - } else { - this.replace(other.clone()); - } -} - impl merge_from::MergeFrom for AllLanguageSettingsContent { - fn merge_from(&mut self, other: Option<&Self>) { - let Some(other) = other else { return }; - self.file_types.merge_from(Some(&other.file_types)); - merge_option(&mut self.features, other.features.as_ref()); - merge_option(&mut self.edit_predictions, other.edit_predictions.as_ref()); + fn merge_from(&mut self, other: &Self) { + self.file_types.merge_from(&other.file_types); + self.features.merge_from(&other.features); + self.edit_predictions.merge_from(&other.edit_predictions); // A user's global settings override the default global settings and // all default language-specific settings. // - self.defaults.merge_from(Some(&other.defaults)); + self.defaults.merge_from(&other.defaults); for language_settings in self.languages.0.values_mut() { - language_settings.merge_from(Some(&other.defaults)); + language_settings.merge_from(&other.defaults); } // A user's language-specific settings override default language-specific settings. for (language_name, user_language_settings) in &other.languages.0 { if let Some(existing) = self.languages.0.get_mut(language_name) { - existing.merge_from(Some(&user_language_settings)); + existing.merge_from(&user_language_settings); } else { let mut new_settings = self.defaults.clone(); - new_settings.merge_from(Some(&user_language_settings)); + new_settings.merge_from(&user_language_settings); self.languages.0.insert(language_name.clone(), new_settings); } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index fe2a5cfdfc6493cf3ef374a66c389022748e088b..dc703e50f1de43aee8059e144dc4cb0815b3472d 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -870,15 +870,15 @@ impl SettingsStore { if changed_local_path.is_none() { let mut merged = self.default_settings.as_ref().clone(); - merged.merge_from(self.extension_settings.as_deref()); - merged.merge_from(self.global_settings.as_deref()); + merged.merge_from_option(self.extension_settings.as_deref()); + merged.merge_from_option(self.global_settings.as_deref()); if let Some(user_settings) = self.user_settings.as_ref() { - merged.merge_from(Some(&user_settings.content)); - merged.merge_from(user_settings.for_release_channel()); - merged.merge_from(user_settings.for_os()); - merged.merge_from(user_settings.for_profile(cx)); + merged.merge_from(&user_settings.content); + merged.merge_from_option(user_settings.for_release_channel()); + merged.merge_from_option(user_settings.for_os()); + merged.merge_from_option(user_settings.for_profile(cx)); } - merged.merge_from(self.server_settings.as_deref()); + merged.merge_from_option(self.server_settings.as_deref()); self.merged_settings = Rc::new(merged); for setting_value in self.setting_values.values_mut() { @@ -906,7 +906,7 @@ impl SettingsStore { } else { self.merged_settings.as_ref().clone() }; - merged_local_settings.merge_from(Some(local_settings)); + merged_local_settings.merge_from(local_settings); project_settings_stack.push(merged_local_settings); diff --git a/crates/settings_macros/src/settings_macros.rs b/crates/settings_macros/src/settings_macros.rs index 33c136b1f2b3e4bec3528d4dff632e05119bc516..1a7c391847e5093754f241ffccb079cc5ddd1a6b 100644 --- a/crates/settings_macros/src/settings_macros.rs +++ b/crates/settings_macros/src/settings_macros.rs @@ -1,13 +1,11 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{Data, DeriveInput, Fields, Type, parse_macro_input}; +use syn::{Data, DeriveInput, Fields, parse_macro_input}; /// Derives the `MergeFrom` trait for a struct. /// /// This macro automatically implements `MergeFrom` by calling `merge_from` -/// on all fields in the struct. For `Option` fields, it merges by taking -/// the `other` value when `self` is `None`. For other types, it recursively -/// calls `merge_from` on the field. +/// on all fields in the struct. /// /// # Example /// @@ -30,61 +28,25 @@ pub fn derive_merge_from(input: TokenStream) -> TokenStream { Fields::Named(fields) => { let field_merges = fields.named.iter().map(|field| { let field_name = &field.ident; - let field_type = &field.ty; - - if is_option_type(field_type) { - // For Option fields, merge by taking the other value if self is None - quote! { - if let Some(other_value) = other.#field_name.as_ref() { - if self.#field_name.is_none() { - self.#field_name = Some(other_value.clone()); - } else if let Some(self_value) = self.#field_name.as_mut() { - self_value.merge_from(Some(other_value)); - } - } - } - } else { - // For non-Option fields, recursively call merge_from - quote! { - self.#field_name.merge_from(Some(&other.#field_name)); - } + quote! { + self.#field_name.merge_from(&other.#field_name); } }); quote! { - if let Some(other) = other { - #(#field_merges)* - } + #(#field_merges)* } } Fields::Unnamed(fields) => { - let field_merges = fields.unnamed.iter().enumerate().map(|(i, field)| { + let field_merges = fields.unnamed.iter().enumerate().map(|(i, _)| { let field_index = syn::Index::from(i); - let field_type = &field.ty; - - if is_option_type(field_type) { - // For Option fields, merge by taking the other value if self is None - quote! { - if let Some(other_value) = other.#field_index.as_ref() { - if self.#field_index.is_none() { - self.#field_index = Some(other_value.clone()); - } else if let Some(self_value) = self.#field_index.as_mut() { - self_value.merge_from(Some(other_value)); - } - } - } - } else { - // For non-Option fields, recursively call merge_from - quote! { - self.#field_index.merge_from(Some(&other.#field_index)); - } + quote! { + self.#field_index.merge_from(&other.#field_index); } }); quote! { - if let Some(other) = other { - #(#field_merges)* - } + #(#field_merges)* } } Fields::Unit => { @@ -95,9 +57,7 @@ pub fn derive_merge_from(input: TokenStream) -> TokenStream { }, Data::Enum(_) => { quote! { - if let Some(other) = other { - *self = other.clone(); - } + *self = other.clone(); } } Data::Union(_) => { @@ -107,7 +67,7 @@ pub fn derive_merge_from(input: TokenStream) -> TokenStream { let expanded = quote! { impl #impl_generics crate::merge_from::MergeFrom for #name #ty_generics #where_clause { - fn merge_from(&mut self, other: ::core::option::Option<&Self>) { + fn merge_from(&mut self, other: &Self) { use crate::merge_from::MergeFrom as _; #merge_body } @@ -116,17 +76,3 @@ pub fn derive_merge_from(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } - -/// Check if a type is `Option` -fn is_option_type(ty: &Type) -> bool { - match ty { - Type::Path(type_path) => { - if let Some(segment) = type_path.path.segments.last() { - segment.ident == "Option" - } else { - false - } - } - _ => false, - } -} From 66f2fda6252fcab00f5eb8de5fe04ccc2fd74f31 Mon Sep 17 00:00:00 2001 From: jneem Date: Fri, 19 Sep 2025 04:42:04 -0500 Subject: [PATCH 137/721] helix: Initial support for helix-mode paste (#37963) This is a redo of #29776. I went for a separate function -- instead of adding a bunch of conditions to `vim::Paste` -- because there were quite a few differences. Release Notes: - Added a `vim::HelixPaste` command that imitates Helix's paste behavior --------- Co-authored-by: Jakub Konka --- assets/keymaps/vim.json | 2 + crates/vim/src/helix.rs | 2 + crates/vim/src/helix/paste.rs | 413 ++++++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 crates/vim/src/helix/paste.rs diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 817198659657814dcc597926d689063ae2182c78..590e84cf7fc10f7af5dd317bc114b75390414e4f 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -433,6 +433,8 @@ "h": "vim::WrappingLeft", "l": "vim::WrappingRight", "y": "vim::HelixYank", + "p": "vim::HelixPaste", + "shift-p": ["vim::HelixPaste", { "before": true }], "alt-;": "vim::OtherEnd", "ctrl-r": "vim::Redo", "f": ["vim::PushFindForward", { "before": false, "multiline": true }], diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index cc235d67ae6efcae2fb5a5c5d899b9f7776cbda4..ec1618311f8b8e16b71a39fc1d29b5c60eb49c96 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -1,5 +1,6 @@ mod boundary; mod object; +mod paste; mod select; use editor::display_map::DisplaySnapshot; @@ -40,6 +41,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::helix_append); Vim::action(editor, cx, Vim::helix_yank); Vim::action(editor, cx, Vim::helix_goto_last_modification); + Vim::action(editor, cx, Vim::helix_paste); } impl Vim { diff --git a/crates/vim/src/helix/paste.rs b/crates/vim/src/helix/paste.rs new file mode 100644 index 0000000000000000000000000000000000000000..ecfdaa499257ad91d8518f488be9a4d4dbb51f1c --- /dev/null +++ b/crates/vim/src/helix/paste.rs @@ -0,0 +1,413 @@ +use editor::{ToOffset, movement}; +use gpui::{Action, Context, Window}; +use schemars::JsonSchema; +use serde::Deserialize; + +use crate::{Vim, state::Mode}; + +/// Pastes text from the specified register at the cursor position. +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] +#[serde(deny_unknown_fields)] +pub struct HelixPaste { + #[serde(default)] + before: bool, +} + +impl Vim { + pub fn helix_paste( + &mut self, + action: &HelixPaste, + window: &mut Window, + cx: &mut Context, + ) { + self.record_current_action(cx); + self.store_visual_marks(window, cx); + let count = Vim::take_count(cx).unwrap_or(1); + // TODO: vim paste calls take_forced_motion here, but I don't know what that does + // (none of the other helix_ methods call it) + + self.update_editor(cx, |vim, editor, cx| { + editor.transact(window, cx, |editor, window, cx| { + editor.set_clip_at_line_ends(false, cx); + + let selected_register = vim.selected_register.take(); + + let Some((text, clipboard_selections)) = Vim::update_globals(cx, |globals, cx| { + globals.read_register(selected_register, Some(editor), cx) + }) + .and_then(|reg| { + (!reg.text.is_empty()) + .then_some(reg.text) + .zip(reg.clipboard_selections) + }) else { + return; + }; + + let (display_map, current_selections) = editor.selections.all_adjusted_display(cx); + + // The clipboard can have multiple selections, and there can + // be multiple selections. Helix zips them together, so the first + // clipboard entry gets pasted at the first selection, the second + // entry gets pasted at the second selection, and so on. If there + // are more clipboard selections than selections, the extra ones + // don't get pasted anywhere. If there are more selections than + // clipboard selections, the last clipboard selection gets + // pasted at all remaining selections. + + let mut edits = Vec::new(); + let mut new_selections = Vec::new(); + let mut start_offset = 0; + + let mut replacement_texts: Vec = Vec::new(); + + for ix in 0..current_selections.len() { + let to_insert = if let Some(clip_sel) = clipboard_selections.get(ix) { + let end_offset = start_offset + clip_sel.len; + let text = text[start_offset..end_offset].to_string(); + start_offset = end_offset + 1; + text + } else if let Some(last_text) = replacement_texts.last() { + // We have more current selections than clipboard selections: repeat the last one. + last_text.to_owned() + } else { + text.to_string() + }; + replacement_texts.push(to_insert); + } + + let line_mode = replacement_texts.iter().any(|text| text.ends_with('\n')); + + for (to_insert, sel) in replacement_texts.into_iter().zip(current_selections) { + // Helix doesn't care about the head/tail of the selection. + // Pasting before means pasting before the whole selection. + let display_point = if line_mode { + if action.before { + movement::line_beginning(&display_map, sel.start, false) + } else if sel.end.column() == 0 { + sel.end + } else { + movement::right( + &display_map, + movement::line_end(&display_map, sel.end, false), + ) + } + } else if action.before { + sel.start + } else if sel.start == sel.end { + // Helix and Zed differ in how they understand + // single-point cursors. In Helix, a single-point cursor + // is "on top" of some character, and pasting after that + // cursor means that the pasted content should go after + // that character. (If the cursor is at the end of a + // line, the pasted content goes on the next line.) + movement::right(&display_map, sel.end) + } else { + sel.end + }; + let point = display_point.to_point(&display_map); + let anchor = if action.before { + display_map.buffer_snapshot.anchor_after(point) + } else { + display_map.buffer_snapshot.anchor_before(point) + }; + edits.push((point..point, to_insert.repeat(count))); + new_selections.push((anchor, to_insert.len() * count)); + } + + editor.edit(edits, cx); + + editor.change_selections(Default::default(), window, cx, |s| { + let snapshot = s.buffer().clone(); + s.select_ranges(new_selections.into_iter().map(|(anchor, len)| { + let offset = anchor.to_offset(&snapshot); + if action.before { + offset.saturating_sub(len)..offset + } else { + offset..(offset + len) + } + })); + }) + }); + }); + + self.switch_mode(Mode::HelixNormal, true, window, cx); + } +} + +#[cfg(test)] +mod test { + use indoc::indoc; + + use crate::{state::Mode, test::VimTestContext}; + + #[gpui::test] + async fn test_paste(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + cx.set_state( + indoc! {" + The «quiˇ»ck brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("y w p"); + + cx.assert_state( + indoc! {" + The quick «quiˇ»brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Pasting before the selection: + cx.set_state( + indoc! {" + The quick brown + fox «jumpsˇ» over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("shift-p"); + cx.assert_state( + indoc! {" + The quick brown + fox «quiˇ»jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + } + + #[gpui::test] + async fn test_point_selection_paste(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + cx.set_state( + indoc! {" + The quiˇck brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("y"); + + // Pasting before the selection: + cx.set_state( + indoc! {" + The quick brown + fox jumpsˇ over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("shift-p"); + cx.assert_state( + indoc! {" + The quick brown + fox jumps«cˇ» over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Pasting after the selection: + cx.set_state( + indoc! {" + The quick brown + fox jumpsˇ over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("p"); + cx.assert_state( + indoc! {" + The quick brown + fox jumps «cˇ»over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Pasting after the selection at the end of a line: + cx.set_state( + indoc! {" + The quick brown + fox jumps overˇ + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("p"); + cx.assert_state( + indoc! {" + The quick brown + fox jumps over + «cˇ»the lazy dog."}, + Mode::HelixNormal, + ); + } + + #[gpui::test] + async fn test_multi_cursor_paste(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + // Select two blocks of text. + cx.set_state( + indoc! {" + The «quiˇ»ck brown + fox ju«mpsˇ» over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("y"); + + // Only one cursor: only the first block gets pasted. + cx.set_state( + indoc! {" + ˇThe quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("shift-p"); + cx.assert_state( + indoc! {" + «quiˇ»The quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Two cursors: both get pasted. + cx.set_state( + indoc! {" + ˇThe ˇquick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("shift-p"); + cx.assert_state( + indoc! {" + «quiˇ»The «mpsˇ»quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Three cursors: the second yanked block is duplicated. + cx.set_state( + indoc! {" + ˇThe ˇquick brown + fox jumpsˇ over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("shift-p"); + cx.assert_state( + indoc! {" + «quiˇ»The «mpsˇ»quick brown + fox jumps«mpsˇ» over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Again with three cursors. All three should be pasted twice. + cx.set_state( + indoc! {" + ˇThe ˇquick brown + fox jumpsˇ over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("2 shift-p"); + cx.assert_state( + indoc! {" + «quiquiˇ»The «mpsmpsˇ»quick brown + fox jumps«mpsmpsˇ» over + the lazy dog."}, + Mode::HelixNormal, + ); + } + + #[gpui::test] + async fn test_line_mode_paste(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + cx.set_state( + indoc! {" + The quick brow«n + ˇ»fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("y shift-p"); + + cx.assert_state( + indoc! {" + «n + ˇ»The quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // In line mode, if we're in the middle of a line then pasting before pastes on + // the line before. + cx.set_state( + indoc! {" + The quick brown + fox jumpsˇ over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("shift-p"); + cx.assert_state( + indoc! {" + The quick brown + «n + ˇ»fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // In line mode, if we're in the middle of a line then pasting after pastes on + // the line after. + cx.set_state( + indoc! {" + The quick brown + fox jumpsˇ over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("p"); + cx.assert_state( + indoc! {" + The quick brown + fox jumps over + «n + ˇ»the lazy dog."}, + Mode::HelixNormal, + ); + + // If we're currently at the end of a line, "the line after" + // means right after the cursor. + cx.set_state( + indoc! {" + The quick brown + fox jumps over + ˇthe lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("p"); + cx.assert_state( + indoc! {" + The quick brown + fox jumps over + «n + ˇ»the lazy dog."}, + Mode::HelixNormal, + ); + } +} From 194a13ffb5623e2657a1916db4edab314914720a Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Fri, 19 Sep 2025 12:31:54 +0200 Subject: [PATCH 138/721] Add denoising & prepare for migrating to new samplerate & channel count (#38493) Uses the previously merged denoising crate (and fixes a bug in it that snug in during refactoring) in the microphone input. The experimental audio path now picks the samplerate and channel count depending on a setting. It can handle incoming streams with both the current (future legacy) and new samplerate & channel count. These are url-encoded into the livekit track name. --- Cargo.lock | 2 +- assets/settings/default.json | 32 +++- crates/audio/Cargo.toml | 1 + crates/audio/src/audio.rs | 65 +++++-- crates/audio/src/audio_settings.rs | 95 +++++++--- crates/audio/src/rodio_ext.rs | 167 +++++++++++++++++- crates/denoise/src/engine.rs | 6 +- crates/denoise/src/lib.rs | 2 +- crates/livekit_client/src/livekit_client.rs | 20 ++- .../src/livekit_client/playback.rs | 89 +++++++--- .../src/livekit_client/playback/source.rs | 40 +++-- crates/settings/src/settings_content.rs | 44 +++-- tooling/workspace-hack/Cargo.toml | 14 +- 13 files changed, 454 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3acfed9bd7cfa8bc2742bb4f006c38a4f65a1f0e..be3e5b04ca18d56024eabe45f14562fca3d56375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1405,6 +1405,7 @@ dependencies = [ "async-tar", "collections", "crossbeam", + "denoise", "gpui", "libwebrtc", "log", @@ -20742,7 +20743,6 @@ dependencies = [ "nix 0.29.0", "nix 0.30.1", "nom 7.1.3", - "num", "num-bigint", "num-bigint-dig", "num-complex", diff --git a/assets/settings/default.json b/assets/settings/default.json index 091231521470ebec50cf1351a76063e9205a3d24..d469638ab28ea02eb9b7675296ee9582e2de3ccd 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -413,15 +413,33 @@ "experimental.rodio_audio": false, // Requires 'rodio_audio: true' // - // Use the new audio systems automatic gain control for your microphone. - // This affects how loud you sound to others. - "experimental.control_input_volume": false, + // Automatically increase or decrease you microphone's volume. This affects how + // loud you sound to others. + // + // Recommended: off (default) + // Microphones are too quite in zed, until everyone is on experimental + // audio and has auto speaker volume on this will make you very loud + // compared to other speakers. + "experimental.auto_microphone_volume": false, + // Requires 'rodio_audio: true' + // + // Automatically increate or decrease the volume of other call members. + // This only affects how things sound for you. + "experimental.auto_speaker_volume": true, // Requires 'rodio_audio: true' // - // Use the new audio systems automatic gain control on everyone in the - // call. This makes call members who are too quite louder and those who are - // too loud quieter. This only affects how things sound for you. - "experimental.control_output_volume": false + // Remove background noises. Works great for typing, cars, dogs, AC. Does + // not work well on music. + "experimental.denoise": true, + // Requires 'rodio_audio: true' + // + // Use audio parameters compatible with the previous versions of + // experimental audio and non-experimental audio. When this is false you + // will sound strange to anyone not on the latest experimental audio. In + // the future we will migrate by setting this to false + // + // You need to rejoin a call for this setting to apply + "experimental.legacy_audio_compatible": true }, // Scrollbar related settings "scrollbar": { diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index c083c9a659e50aef37acc2cdfc239696bd469c1e..7f2fed80e2315e51fca7d8477b04885998336632 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -18,6 +18,7 @@ async-tar.workspace = true collections.workspace = true crossbeam.workspace = true gpui.workspace = true +denoise = { path = "../denoise" } log.workspace = true parking_lot.workspace = true rodio = { workspace = true, features = [ "wav", "playback", "wav_output" ] } diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs index f60ddb87b9615d2da9c2be248ab397c19a463616..dc4d97a8fa47f11f9120cf5144a37ae6fd94bc2a 100644 --- a/crates/audio/src/audio.rs +++ b/crates/audio/src/audio.rs @@ -9,7 +9,7 @@ mod non_windows_and_freebsd_deps { pub(super) use log::info; pub(super) use parking_lot::Mutex; pub(super) use rodio::cpal::Sample; - pub(super) use rodio::source::{LimitSettings, UniformSourceIterator}; + pub(super) use rodio::source::LimitSettings; pub(super) use std::sync::Arc; } @@ -31,18 +31,20 @@ pub use rodio_ext::RodioExt; use crate::audio_settings::LIVE_SETTINGS; -// NOTE: We used to use WebRTC's mixer which only supported -// 16kHz, 32kHz and 48kHz. As 48 is the most common "next step up" -// for audio output devices like speakers/bluetooth, we just hard-code -// this; and downsample when we need to. +// We are migrating to 16kHz sample rate from 48kHz. In the future +// once we are reasonably sure most users have upgraded we will +// remove the LEGACY parameters. // -// Since most noise cancelling requires 16kHz we will move to -// that in the future. -pub const SAMPLE_RATE: NonZero = nz!(48000); -pub const CHANNEL_COUNT: NonZero = nz!(2); +// We migrate to 16kHz because it is sufficient for speech and required +// by the denoiser and future Speech to Text layers. +pub const SAMPLE_RATE: NonZero = nz!(16000); +pub const CHANNEL_COUNT: NonZero = nz!(1); pub const BUFFER_SIZE: usize = // echo canceller and livekit want 10ms of audio (SAMPLE_RATE.get() as usize / 100) * CHANNEL_COUNT.get() as usize; +pub const LEGACY_SAMPLE_RATE: NonZero = nz!(48000); +pub const LEGACY_CHANNEL_COUNT: NonZero = nz!(2); + pub const REPLAY_DURATION: Duration = Duration::from_secs(30); pub fn init(cx: &mut App) { @@ -106,6 +108,11 @@ impl Global for Audio {} impl Audio { fn ensure_output_exists(&mut self) -> Result<&Mixer> { + #[cfg(debug_assertions)] + log::warn!( + "Audio does not sound correct without optimizations. Use a release build to debug audio issues" + ); + if self.output_handle.is_none() { self.output_handle = Some( OutputStreamBuilder::open_default_stream() @@ -160,13 +167,20 @@ impl Audio { let stream = rodio::microphone::MicrophoneBuilder::new() .default_device()? .default_config()? - .prefer_sample_rates([SAMPLE_RATE, SAMPLE_RATE.saturating_mul(nz!(2))]) - // .prefer_channel_counts([nz!(1), nz!(2)]) + .prefer_sample_rates([ + SAMPLE_RATE, // sample rates trivially resamplable to `SAMPLE_RATE` + SAMPLE_RATE.saturating_mul(nz!(2)), + SAMPLE_RATE.saturating_mul(nz!(3)), + SAMPLE_RATE.saturating_mul(nz!(4)), + ]) + .prefer_channel_counts([nz!(1), nz!(2), nz!(3), nz!(4)]) .prefer_buffer_sizes(512..) .open_stream()?; info!("Opened microphone: {:?}", stream.config()); - let (replay, stream) = UniformSourceIterator::new(stream, CHANNEL_COUNT, SAMPLE_RATE) + let (replay, stream) = stream + .possibly_disconnected_channels_to_mono() + .constant_samplerate(SAMPLE_RATE) .limit(LimitSettings::live_performance()) .process_buffer::(move |buffer| { let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample()); @@ -187,15 +201,28 @@ impl Audio { } } }) - .automatic_gain_control(1.0, 4.0, 0.0, 5.0) + .denoise() + .context("Could not set up denoiser")? + .periodic_access(Duration::from_millis(100), move |denoise| { + denoise.set_enabled(LIVE_SETTINGS.denoise.load(Ordering::Relaxed)); + }) + .automatic_gain_control(1.0, 2.0, 0.0, 5.0) .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); + agc_source + .set_enabled(LIVE_SETTINGS.auto_microphone_volume.load(Ordering::Relaxed)); }) .replayable(REPLAY_DURATION)?; voip_parts .replays .add_voip_stream("local microphone".to_string(), replay); + + let stream = if voip_parts.legacy_audio_compatible { + stream.constant_params(LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE) + } else { + stream.constant_params(CHANNEL_COUNT, SAMPLE_RATE) + }; + Ok(stream) } @@ -206,9 +233,10 @@ impl Audio { cx: &mut App, ) -> anyhow::Result<()> { let (replay_source, source) = source - .automatic_gain_control(1.0, 4.0, 0.0, 5.0) + .constant_params(CHANNEL_COUNT, SAMPLE_RATE) + .automatic_gain_control(1.0, 2.0, 0.0, 5.0) .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); + agc_source.set_enabled(LIVE_SETTINGS.auto_speaker_volume.load(Ordering::Relaxed)); }) .replayable(REPLAY_DURATION) .expect("REPLAY_DURATION is longer than 100ms"); @@ -269,6 +297,7 @@ impl Audio { pub struct VoipParts { echo_canceller: Arc>, replays: replays::Replays, + legacy_audio_compatible: bool, } #[cfg(not(any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")))] @@ -277,8 +306,12 @@ impl VoipParts { let (apm, replays) = cx.try_read_default_global::(|audio, _| { (Arc::clone(&audio.echo_canceller), audio.replays.clone()) })?; + let legacy_audio_compatible = + AudioSettings::try_read_global(cx, |settings| settings.legacy_audio_compatible) + .unwrap_or_default(); Ok(Self { + legacy_audio_compatible, echo_canceller: apm, replays, }) diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index 2c9db4989efa5edcf4ef84c4e3031b53980fad51..cba7d45c31f4674be6a69c10ab34f00e0b8cbbd1 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -6,18 +6,38 @@ use settings::{Settings, SettingsStore}; #[derive(Clone, Debug)] pub struct AudioSettings { /// Opt into the new audio system. + /// + /// You need to rejoin a call for this setting to apply pub rodio_audio: bool, // default is false /// Requires 'rodio_audio: true' /// - /// Use the new audio systems automatic gain control for your microphone. - /// This affects how loud you sound to others. - pub control_input_volume: bool, + /// Automatically increase or decrease you microphone's volume. This affects how + /// loud you sound to others. + /// + /// Recommended: off (default) + /// Microphones are too quite in zed, until everyone is on experimental + /// audio and has auto speaker volume on this will make you very loud + /// compared to other speakers. + pub auto_microphone_volume: bool, + /// Requires 'rodio_audio: true' + /// + /// Automatically increate or decrease the volume of other call members. + /// This only affects how things sound for you. + pub auto_speaker_volume: bool, + /// Requires 'rodio_audio: true' + /// + /// Remove background noises. Works great for typing, cars, dogs, AC. Does + /// not work well on music. + pub denoise: bool, /// Requires 'rodio_audio: true' /// - /// Use the new audio systems automatic gain control on everyone in the - /// call. This makes call members who are too quite louder and those who are - /// too loud quieter. This only affects how things sound for you. - pub control_output_volume: bool, + /// Use audio parameters compatible with the previous versions of + /// experimental audio and non-experimental audio. When this is false you + /// will sound strange to anyone not on the latest experimental audio. In + /// the future we will migrate by setting this to false + /// + /// You need to rejoin a call for this setting to apply + pub legacy_audio_compatible: bool, } /// Configuration of audio in Zed @@ -25,46 +45,66 @@ impl Settings for AudioSettings { fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let audio = &content.audio.as_ref().unwrap(); AudioSettings { - control_input_volume: audio.control_input_volume.unwrap(), - control_output_volume: audio.control_output_volume.unwrap(), rodio_audio: audio.rodio_audio.unwrap(), + auto_microphone_volume: audio.auto_microphone_volume.unwrap(), + auto_speaker_volume: audio.auto_speaker_volume.unwrap(), + denoise: audio.denoise.unwrap(), + legacy_audio_compatible: audio.legacy_audio_compatible.unwrap(), } } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - } } /// See docs on [LIVE_SETTINGS] pub(crate) struct LiveSettings { - pub(crate) control_input_volume: AtomicBool, - pub(crate) control_output_volume: AtomicBool, + pub(crate) auto_microphone_volume: AtomicBool, + pub(crate) auto_speaker_volume: AtomicBool, + pub(crate) denoise: AtomicBool, } impl LiveSettings { pub(crate) fn initialize(&self, cx: &mut App) { cx.observe_global::(move |cx| { - LIVE_SETTINGS.control_input_volume.store( - AudioSettings::get_global(cx).control_input_volume, + LIVE_SETTINGS.auto_microphone_volume.store( + AudioSettings::get_global(cx).auto_microphone_volume, Ordering::Relaxed, ); - LIVE_SETTINGS.control_output_volume.store( - AudioSettings::get_global(cx).control_output_volume, + LIVE_SETTINGS.auto_speaker_volume.store( + AudioSettings::get_global(cx).auto_speaker_volume, Ordering::Relaxed, ); + + let denoise_enabled = AudioSettings::get_global(cx).denoise; + #[cfg(debug_assertions)] + { + static DENOISE_WARNING_SEND: AtomicBool = AtomicBool::new(false); + if denoise_enabled && !DENOISE_WARNING_SEND.load(Ordering::Relaxed) { + DENOISE_WARNING_SEND.store(true, Ordering::Relaxed); + log::warn!("Denoise does not work on debug builds, not enabling") + } + } + #[cfg(not(debug_assertions))] + LIVE_SETTINGS + .denoise + .store(denoise_enabled, Ordering::Relaxed); }) .detach(); let init_settings = AudioSettings::get_global(cx); LIVE_SETTINGS - .control_input_volume - .store(init_settings.control_input_volume, Ordering::Relaxed); + .auto_microphone_volume + .store(init_settings.auto_microphone_volume, Ordering::Relaxed); + LIVE_SETTINGS + .auto_speaker_volume + .store(init_settings.auto_speaker_volume, Ordering::Relaxed); + let denoise_enabled = AudioSettings::get_global(cx).denoise; + #[cfg(debug_assertions)] + if denoise_enabled { + log::warn!("Denoise does not work on debug builds, not enabling") + } + #[cfg(not(debug_assertions))] LIVE_SETTINGS - .control_output_volume - .store(init_settings.control_output_volume, Ordering::Relaxed); + .denoise + .store(denoise_enabled, Ordering::Relaxed); } } @@ -73,6 +113,7 @@ impl LiveSettings { /// real time and must each run in a dedicated OS thread, therefore we can not /// use the background executor. pub(crate) static LIVE_SETTINGS: LiveSettings = LiveSettings { - control_input_volume: AtomicBool::new(true), - control_output_volume: AtomicBool::new(true), + auto_microphone_volume: AtomicBool::new(true), + auto_speaker_volume: AtomicBool::new(true), + denoise: AtomicBool::new(true), }; diff --git a/crates/audio/src/rodio_ext.rs b/crates/audio/src/rodio_ext.rs index e80b00e15a8fdbd3fc438b78a9ca45d0902dcef1..af4cc89252dfdc1498471ec7ac09b56d59b62eca 100644 --- a/crates/audio/src/rodio_ext.rs +++ b/crates/audio/src/rodio_ext.rs @@ -1,4 +1,5 @@ use std::{ + num::NonZero, sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, @@ -7,12 +8,22 @@ use std::{ }; use crossbeam::queue::ArrayQueue; -use rodio::{ChannelCount, Sample, SampleRate, Source}; +use denoise::{Denoiser, DenoiserError}; +use log::warn; +use rodio::{ + ChannelCount, Sample, SampleRate, Source, conversions::SampleRateConverter, nz, + source::UniformSourceIterator, +}; + +const MAX_CHANNELS: usize = 8; #[derive(Debug, thiserror::Error)] #[error("Replay duration is too short must be >= 100ms")] pub struct ReplayDurationTooShort; +// These all require constant sources (so the span is infinitely long) +// this is not guaranteed by rodio however we know it to be true in all our +// applications. Rodio desperately needs a constant source concept. pub trait RodioExt: Source + Sized { fn process_buffer(self, callback: F) -> ProcessBuffer where @@ -25,6 +36,14 @@ pub trait RodioExt: Source + Sized { duration: Duration, ) -> Result<(Replay, Replayable), ReplayDurationTooShort>; fn take_samples(self, n: usize) -> TakeSamples; + fn denoise(self) -> Result, DenoiserError>; + fn constant_params( + self, + channel_count: ChannelCount, + sample_rate: SampleRate, + ) -> UniformSourceIterator; + fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate; + fn possibly_disconnected_channels_to_mono(self) -> ToMono; } impl RodioExt for S { @@ -101,8 +120,149 @@ impl RodioExt for S { left_to_take: n, } } + fn denoise(self) -> Result, DenoiserError> { + let res = Denoiser::try_new(self); + res + } + fn constant_params( + self, + channel_count: ChannelCount, + sample_rate: SampleRate, + ) -> UniformSourceIterator { + UniformSourceIterator::new(self, channel_count, sample_rate) + } + fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate { + ConstantSampleRate::new(self, sample_rate) + } + fn possibly_disconnected_channels_to_mono(self) -> ToMono { + ToMono::new(self) + } +} + +pub struct ConstantSampleRate { + inner: SampleRateConverter, + channels: ChannelCount, + sample_rate: SampleRate, +} + +impl ConstantSampleRate { + fn new(source: S, target_rate: SampleRate) -> Self { + let input_sample_rate = source.sample_rate(); + let channels = source.channels(); + let inner = SampleRateConverter::new(source, input_sample_rate, target_rate, channels); + Self { + inner, + channels, + sample_rate: target_rate, + } + } +} + +impl Iterator for ConstantSampleRate { + type Item = rodio::Sample; + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl Source for ConstantSampleRate { + fn current_span_len(&self) -> Option { + None + } + + fn channels(&self) -> ChannelCount { + self.channels + } + + fn sample_rate(&self) -> SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + None // not supported (not used by us) + } +} + +const TYPICAL_NOISE_FLOOR: Sample = 1e-3; + +/// constant source, only works on a single span +pub struct ToMono { + inner: S, + input_channel_count: ChannelCount, + connected_channels: ChannelCount, + /// running mean of second channel 'volume' + means: [f32; MAX_CHANNELS], +} +impl ToMono { + fn new(input: S) -> Self { + let channels = input + .channels() + .min(const { NonZero::::new(MAX_CHANNELS as u16).unwrap() }); + if channels < input.channels() { + warn!("Ignoring input channels {}..", channels.get()); + } + + Self { + connected_channels: channels, + input_channel_count: channels, + inner: input, + means: [TYPICAL_NOISE_FLOOR; MAX_CHANNELS], + } + } +} + +impl Source for ToMono { + fn current_span_len(&self) -> Option { + None + } + + fn channels(&self) -> ChannelCount { + rodio::nz!(1) + } + + fn sample_rate(&self) -> SampleRate { + self.inner.sample_rate() + } + + fn total_duration(&self) -> Option { + self.inner.total_duration() + } +} + +fn update_mean(mean: &mut f32, sample: Sample) { + const HISTORY: f32 = 500.0; + *mean *= (HISTORY - 1.0) / HISTORY; + *mean += sample.abs() / HISTORY; +} + +impl Iterator for ToMono { + type Item = Sample; + + fn next(&mut self) -> Option { + let mut mono_sample = 0f32; + let mut active_channels = 0; + for channel in 0..self.input_channel_count.get() as usize { + let sample = self.inner.next()?; + mono_sample += sample; + + update_mean(&mut self.means[channel], sample); + if self.means[channel] > TYPICAL_NOISE_FLOOR / 10.0 { + active_channels += 1; + } + } + mono_sample /= self.connected_channels.get() as f32; + self.connected_channels = NonZero::new(active_channels).unwrap_or(nz!(1)); + + Some(mono_sample) + } } +/// constant source, only works on a single span pub struct TakeSamples { inner: S, left_to_take: usize, @@ -147,6 +307,7 @@ impl Source for TakeSamples { } } +/// constant source, only works on a single span #[derive(Debug)] struct ReplayQueue { inner: ArrayQueue>, @@ -193,6 +354,7 @@ impl ReplayQueue { } } +/// constant source, only works on a single span pub struct ProcessBuffer where S: Source + Sized, @@ -260,6 +422,7 @@ where } } +/// constant source, only works on a single span pub struct InspectBuffer where S: Source + Sized, @@ -324,6 +487,7 @@ where } } +/// constant source, only works on a single span #[derive(Debug)] pub struct Replayable { inner: S, @@ -375,6 +539,7 @@ impl Source for Replayable { } } +/// constant source, only works on a single span #[derive(Debug)] pub struct Replay { rx: Arc, diff --git a/crates/denoise/src/engine.rs b/crates/denoise/src/engine.rs index 5196b70b5ba02f665385c022a0dfa9cd22c1db9c..be0548c689e3b902342cd1cb6d6d8e29351e8be4 100644 --- a/crates/denoise/src/engine.rs +++ b/crates/denoise/src/engine.rs @@ -138,13 +138,13 @@ impl Engine { const SPECTRUM_INPUT: &str = "input_2"; const MEMORY_INPUT: &str = "input_3"; - let memory_input = + let spectrum = Tensor::from_slice::<_, f32>(&self.in_magnitude, (1, 1, FFT_OUT_SIZE), &Device::Cpu) .expect("the in magnitude has enough elements to fill the Tensor"); let inputs = HashMap::from([ - (MEMORY_INPUT.to_string(), memory_input), - (SPECTRUM_INPUT.to_string(), self.spectral_memory.clone()), + (SPECTRUM_INPUT.to_string(), spectrum), + (MEMORY_INPUT.to_string(), self.spectral_memory.clone()), ]); inputs } diff --git a/crates/denoise/src/lib.rs b/crates/denoise/src/lib.rs index 1422c81a4b915d571d35585447165c04d3695b73..f6cbf0fadf1f216cc6168c2b249f807b557869af 100644 --- a/crates/denoise/src/lib.rs +++ b/crates/denoise/src/lib.rs @@ -84,7 +84,7 @@ impl Denoiser { .spawn(move || { run_neural_denoiser(denoised_tx, input_rx); }) - .unwrap(); + .expect("Should be ablet to spawn threads"); Ok(Self { inner: source, diff --git a/crates/livekit_client/src/livekit_client.rs b/crates/livekit_client/src/livekit_client.rs index 45e929cb2ec0bebf054497632d614af1975f6397..04e669869ddbf64ffd92cbcad4bf927bfec55cb5 100644 --- a/crates/livekit_client/src/livekit_client.rs +++ b/crates/livekit_client/src/livekit_client.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, Result, anyhow}; use audio::AudioSettings; use collections::HashMap; use futures::{SinkExt, channel::mpsc}; @@ -12,7 +12,10 @@ use settings::Settings; mod playback; -use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication}; +use crate::{ + LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication, + livekit_client::playback::Speaker, +}; pub use playback::AudioStream; pub(crate) use playback::{RemoteVideoFrame, play_remote_video_track}; @@ -132,11 +135,20 @@ impl Room { track: &RemoteAudioTrack, cx: &mut App, ) -> Result { + let speaker: Speaker = + serde_urlencoded::from_str(&track.0.name()).unwrap_or_else(|_| Speaker { + name: track.0.name(), + is_staff: false, + legacy_audio_compatible: true, + }); + if AudioSettings::get_global(cx).rodio_audio { info!("Using experimental.rodio_audio audio pipeline for output"); - playback::play_remote_audio_track(&track.0, cx) - } else { + playback::play_remote_audio_track(&track.0, speaker, cx) + } else if speaker.legacy_audio_compatible { Ok(self.playback.play_remote_audio_track(&track.0)) + } else { + Err(anyhow!("Client version too old to play audio in call")) } } } diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index df8b5ea54fb1ce11bf871faa912757bbff1fd7f9..b4cd68e08e4a88f9cb248e3b7ac64fbfca4c39de 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; -use audio::{AudioSettings, CHANNEL_COUNT, SAMPLE_RATE}; +use audio::{AudioSettings, CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE, SAMPLE_RATE}; use cpal::traits::{DeviceTrait, StreamTrait as _}; use futures::channel::mpsc::UnboundedSender; use futures::{Stream, StreamExt as _}; @@ -43,12 +43,17 @@ pub(crate) struct AudioStack { pub(crate) fn play_remote_audio_track( track: &livekit::track::RemoteAudioTrack, + speaker: Speaker, cx: &mut gpui::App, ) -> Result { + let stream = source::LiveKitStream::new( + cx.background_executor(), + track, + speaker.legacy_audio_compatible, + ); + let stop_handle = Arc::new(AtomicBool::new(false)); let stop_handle_clone = stop_handle.clone(); - let stream = source::LiveKitStream::new(cx.background_executor(), track); - let stream = stream .stoppable() .periodic_access(Duration::from_millis(50), move |s| { @@ -57,10 +62,6 @@ pub(crate) fn play_remote_audio_track( } }); - let speaker: Speaker = serde_urlencoded::from_str(&track.name()).unwrap_or_else(|_| Speaker { - name: track.name(), - is_staff: false, - }); audio::Audio::play_voip_stream(stream, speaker.name, speaker.is_staff, cx) .context("Could not play audio")?; @@ -152,17 +153,32 @@ impl AudioStack { is_staff: bool, cx: &AsyncApp, ) -> Result<(crate::LocalAudioTrack, AudioStream)> { - let source = NativeAudioSource::new( - // n.b. this struct's options are always ignored, noise cancellation is provided by apm. - AudioSourceOptions::default(), - SAMPLE_RATE.get(), - CHANNEL_COUNT.get().into(), - 10, - ); + let legacy_audio_compatible = + AudioSettings::try_read_global(cx, |setting| setting.legacy_audio_compatible) + .unwrap_or_default(); + + let source = if legacy_audio_compatible { + NativeAudioSource::new( + // n.b. this struct's options are always ignored, noise cancellation is provided by apm. + AudioSourceOptions::default(), + LEGACY_SAMPLE_RATE.get(), + LEGACY_CHANNEL_COUNT.get().into(), + 10, + ) + } else { + NativeAudioSource::new( + // n.b. this struct's options are always ignored, noise cancellation is provided by apm. + AudioSourceOptions::default(), + SAMPLE_RATE.get(), + CHANNEL_COUNT.get().into(), + 10, + ) + }; let track_name = serde_urlencoded::to_string(Speaker { name: user_name, is_staff, + legacy_audio_compatible, }) .context("Could not encode user information in track name")?; @@ -186,22 +202,32 @@ impl AudioStack { let capture_task = if rodio_pipeline { info!("Using experimental.rodio_audio audio pipeline"); let voip_parts = audio::VoipParts::new(cx)?; - // Audio needs to run real-time and should never be paused. That is why we are using a - // normal std::thread and not a background task + // Audio needs to run real-time and should never be paused. That is + // why we are using a normal std::thread and not a background task thread::Builder::new() - .name("AudioCapture".to_string()) + .name("MicrophoneToLivekit".to_string()) .spawn(move || { // microphone is non send on mac - let microphone = audio::Audio::open_microphone(voip_parts)?; + let microphone = match audio::Audio::open_microphone(voip_parts) { + Ok(m) => m, + Err(e) => { + log::error!("Could not open microphone: {e}"); + return; + } + }; send_to_livekit(frame_tx, microphone); - Ok::<(), anyhow::Error>(()) }) - .unwrap(); + .expect("should be able to spawn threads"); Task::ready(Ok(())) } else { self.executor.spawn(async move { - Self::capture_input(apm, frame_tx, SAMPLE_RATE.get(), CHANNEL_COUNT.get().into()) - .await + Self::capture_input( + apm, + frame_tx, + LEGACY_SAMPLE_RATE.get(), + LEGACY_CHANNEL_COUNT.get().into(), + ) + .await }) }; @@ -389,25 +415,30 @@ impl AudioStack { } #[derive(Serialize, Deserialize)] -struct Speaker { - name: String, - is_staff: bool, +pub struct Speaker { + pub name: String, + pub is_staff: bool, + pub legacy_audio_compatible: bool, } fn send_to_livekit(frame_tx: UnboundedSender>, mut microphone: impl Source) { use cpal::Sample; + let sample_rate = microphone.sample_rate().get(); + let num_channels = microphone.channels().get() as u32; + let buffer_size = sample_rate / 100 * num_channels; + loop { let sampled: Vec<_> = microphone .by_ref() - .take(audio::BUFFER_SIZE) + .take(buffer_size as usize) .map(|s| s.to_sample()) .collect(); if frame_tx .unbounded_send(AudioFrame { - sample_rate: SAMPLE_RATE.get(), - num_channels: CHANNEL_COUNT.get() as u32, - samples_per_channel: sampled.len() as u32 / CHANNEL_COUNT.get() as u32, + sample_rate, + num_channels, + samples_per_channel: sampled.len() as u32 / num_channels, data: Cow::Owned(sampled), }) .is_err() diff --git a/crates/livekit_client/src/livekit_client/playback/source.rs b/crates/livekit_client/src/livekit_client/playback/source.rs index f605b3d517cd816491f0eceadce5ac778ef75d21..cde4b19fda2e053346ad535e7c75b2abda60431a 100644 --- a/crates/livekit_client/src/livekit_client/playback/source.rs +++ b/crates/livekit_client/src/livekit_client/playback/source.rs @@ -3,17 +3,19 @@ use std::num::NonZero; use futures::StreamExt; use libwebrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame}; use livekit::track::RemoteAudioTrack; -use rodio::{Source, buffer::SamplesBuffer, conversions::SampleTypeConverter, nz}; +use rodio::{ + ChannelCount, SampleRate, Source, buffer::SamplesBuffer, conversions::SampleTypeConverter, +}; -use audio::{CHANNEL_COUNT, SAMPLE_RATE}; +use audio::{CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE, SAMPLE_RATE}; fn frame_to_samplesbuffer(frame: AudioFrame) -> SamplesBuffer { let samples = frame.data.iter().copied(); let samples = SampleTypeConverter::<_, _>::new(samples); let samples: Vec = samples.collect(); SamplesBuffer::new( - nz!(2), // frame always has two channels - NonZero::new(frame.sample_rate).expect("audio frame sample rate is nonzero"), + NonZero::new(frame.num_channels as u16).expect("zero channels is nonsense"), + NonZero::new(frame.sample_rate).expect("samplerate zero is nonsense"), samples, ) } @@ -22,14 +24,26 @@ pub struct LiveKitStream { // shared_buffer: SharedBuffer, inner: rodio::queue::SourcesQueueOutput, _receiver_task: gpui::Task<()>, + channel_count: ChannelCount, + sample_rate: SampleRate, } impl LiveKitStream { - pub fn new(executor: &gpui::BackgroundExecutor, track: &RemoteAudioTrack) -> Self { + pub fn new( + executor: &gpui::BackgroundExecutor, + track: &RemoteAudioTrack, + legacy: bool, + ) -> Self { + let (channel_count, sample_rate) = if legacy { + (LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE) + } else { + (CHANNEL_COUNT, SAMPLE_RATE) + }; + let mut stream = NativeAudioStream::new( track.rtc_track(), - SAMPLE_RATE.get() as i32, - CHANNEL_COUNT.get().into(), + sample_rate.get() as i32, + channel_count.get().into(), ); let (queue_input, queue_output) = rodio::queue::queue(true); // spawn rtc stream @@ -45,6 +59,8 @@ impl LiveKitStream { LiveKitStream { _receiver_task: receiver_task, inner: queue_output, + sample_rate, + channel_count, } } } @@ -63,17 +79,11 @@ impl Source for LiveKitStream { } fn channels(&self) -> rodio::ChannelCount { - // This must be hardcoded because the playback source assumes constant - // sample rate and channel count. The queue upon which this is build - // will however report different counts and rates. Even though we put in - // only items with our (constant) CHANNEL_COUNT & SAMPLE_RATE this will - // play silence on one channel and at 44100 which is not what our - // constants are. - CHANNEL_COUNT + self.channel_count } fn sample_rate(&self) -> rodio::SampleRate { - SAMPLE_RATE // see comment on channels + self.sample_rate } fn total_duration(&self) -> Option { diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 43402cae0e6c723b4cc2e94f28c1ba7d0c61c928..b47755be58445e8ba335c6ea64416265d176fc17 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -291,21 +291,43 @@ pub enum TitleBarVisibility { #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct AudioSettingsContent { /// Opt into the new audio system. - #[serde(rename = "experimental.rodio_audio", default)] - pub rodio_audio: Option, + /// + /// You need to rejoin a call for this setting to apply + #[serde(rename = "experimental.rodio_audio")] + pub rodio_audio: Option, // default is false + /// Requires 'rodio_audio: true' + /// + /// Automatically increase or decrease you microphone's volume. This affects how + /// loud you sound to others. + /// + /// Recommended: off (default) + /// Microphones are too quite in zed, until everyone is on experimental + /// audio and has auto speaker volume on this will make you very loud + /// compared to other speakers. + #[serde(rename = "experimental.auto_microphone_volume")] + pub auto_microphone_volume: Option, /// Requires 'rodio_audio: true' /// - /// Use the new audio systems automatic gain control for your microphone. - /// This affects how loud you sound to others. - #[serde(rename = "experimental.control_input_volume", default)] - pub control_input_volume: Option, + /// Automatically increate or decrease the volume of other call members. + /// This only affects how things sound for you. + #[serde(rename = "experimental.auto_speaker_volume")] + pub auto_speaker_volume: Option, /// Requires 'rodio_audio: true' /// - /// Use the new audio systems automatic gain control on everyone in the - /// call. This makes call members who are too quite louder and those who are - /// too loud quieter. This only affects how things sound for you. - #[serde(rename = "experimental.control_output_volume", default)] - pub control_output_volume: Option, + /// Remove background noises. Works great for typing, cars, dogs, AC. Does + /// not work well on music. + #[serde(rename = "experimental.denoise")] + pub denoise: Option, + /// Requires 'rodio_audio: true' + /// + /// Use audio parameters compatible with the previous versions of + /// experimental audio and non-experimental audio. When this is false you + /// will sound strange to anyone not on the latest experimental audio. In + /// the future we will migrate by setting this to false + /// + /// You need to rejoin a call for this setting to apply + #[serde(rename = "experimental.legacy_audio_compatible")] + pub legacy_audio_compatible: Option, } /// Control what info is collected by Zed. diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index ec9629685d8366864b92a6160ece623450f72b0c..b50854abd55af883af1e97eac4afd51dbb31df3b 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -90,7 +90,6 @@ mime_guess = { version = "2" } miniz_oxide = { version = "0.8", features = ["simd"] } nom = { version = "7" } num-bigint = { version = "0.4" } -num-complex = { version = "0.4", features = ["bytemuck"] } num-integer = { version = "0.1", features = ["i128"] } num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] } num-rational = { version = "0.4", features = ["num-bigint-std"] } @@ -229,7 +228,6 @@ mime_guess = { version = "2" } miniz_oxide = { version = "0.8", features = ["simd"] } nom = { version = "7" } num-bigint = { version = "0.4" } -num-complex = { version = "0.4", features = ["bytemuck"] } num-integer = { version = "0.1", features = ["i128"] } num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] } num-rational = { version = "0.4", features = ["num-bigint-std"] } @@ -308,7 +306,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } -num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -338,7 +335,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } -num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -369,7 +365,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } -num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -399,7 +394,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } -num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -442,6 +436,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } +num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -483,6 +478,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } +num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -522,6 +518,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } +num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -563,6 +560,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } +num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -587,7 +585,6 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } -num = { version = "0.4" } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "fs", "net"] } @@ -613,7 +610,6 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } -num = { version = "0.4" } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } @@ -655,6 +651,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } +num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -696,6 +693,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } +num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } From 5f728efccfe2dfc115934d5274d4eda4b37d1dea Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Fri, 19 Sep 2025 14:21:28 +0200 Subject: [PATCH 139/721] agent: Show custom MCP servers in agent configuration (#38500) Fixes a regression introduced in #38419 Release Notes: - N/A --- crates/agent_ui/src/agent_configuration.rs | 78 +++++++++------------- crates/project/src/context_server_store.rs | 21 +++--- 2 files changed, 43 insertions(+), 56 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index 48203e75af3274fa30da826026c65869f96841f2..3fd78c44ec5a249c6acf4ddd9ac548988a51612c 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -543,35 +543,23 @@ impl AgentConfiguration { window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let mut registry_descriptors = self + let mut context_server_ids = self .context_server_store .read(cx) - .all_registry_descriptor_ids(cx); - let server_count = registry_descriptors.len(); - - // Sort context servers: non-mcp-server ones first, then mcp-server ones - registry_descriptors.sort_by(|a, b| { - let has_mcp_prefix_a = a.0.starts_with("mcp-server-"); - let has_mcp_prefix_b = b.0.starts_with("mcp-server-"); + .server_ids(cx) + .into_iter() + .collect::>(); - match (has_mcp_prefix_a, has_mcp_prefix_b) { + // Sort context servers: ones without mcp-server- prefix first, then prefixed ones + context_server_ids.sort_by(|a, b| { + const MCP_PREFIX: &str = "mcp-server-"; + match (a.0.strip_prefix(MCP_PREFIX), b.0.strip_prefix(MCP_PREFIX)) { // If one has mcp-server- prefix and other doesn't, non-mcp comes first - (true, false) => std::cmp::Ordering::Greater, - (false, true) => std::cmp::Ordering::Less, + (Some(_), None) => std::cmp::Ordering::Greater, + (None, Some(_)) => std::cmp::Ordering::Less, // If both have same prefix status, sort by appropriate key - _ => { - let get_sort_key = |server_id: &str| -> String { - if let Some(suffix) = server_id.strip_prefix("mcp-server-") { - suffix.to_string() - } else { - server_id.to_string() - } - }; - - let key_a = get_sort_key(&a.0); - let key_b = get_sort_key(&b.0); - key_a.cmp(&key_b) - } + (Some(a), Some(b)) => a.cmp(b), + (None, None) => a.0.cmp(&b.0), } }); @@ -636,8 +624,8 @@ impl AgentConfiguration { ) .child(add_server_popover), ) - .child(v_flex().w_full().gap_1().map(|parent| { - if registry_descriptors.is_empty() { + .child(v_flex().w_full().gap_1().map(|mut parent| { + if context_server_ids.is_empty() { parent.child( h_flex() .p_4() @@ -653,26 +641,18 @@ impl AgentConfiguration { ), ) } else { - { - parent.children(registry_descriptors.into_iter().enumerate().flat_map( - |(index, context_server_id)| { - let mut elements: Vec = vec![ - self.render_context_server(context_server_id, window, cx) - .into_any_element(), - ]; - - if index < server_count - 1 { - elements.push( - Divider::horizontal() - .color(DividerColor::BorderFaded) - .into_any_element(), - ); - } - - elements - }, - )) + for (index, context_server_id) in context_server_ids.into_iter().enumerate() { + if index > 0 { + parent = parent.child( + Divider::horizontal() + .color(DividerColor::BorderFaded) + .into_any_element(), + ); + } + parent = + parent.child(self.render_context_server(context_server_id, window, cx)); } + parent } })) } @@ -1106,7 +1086,13 @@ impl AgentConfiguration { IconName::AiClaude, "Claude Code", )) - .children(user_defined_agents), + .map(|mut parent| { + for agent in user_defined_agents { + parent = parent.child(Divider::horizontal().color(DividerColor::BorderFaded)) + .child(agent); + } + parent + }) ) } diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 70f2bb53d42ed843178dad75eb8d503924fb87f5..364128ae4f8cf5703bf7987117b0109462fa4e3c 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -282,16 +282,17 @@ impl ContextServerStore { self.servers.get(id).map(|state| state.configuration()) } - pub fn all_server_ids(&self) -> Vec { - self.servers.keys().cloned().collect() - } - - pub fn all_registry_descriptor_ids(&self, cx: &App) -> Vec { - self.registry - .read(cx) - .context_server_descriptors() - .into_iter() - .map(|(id, _)| ContextServerId(id)) + pub fn server_ids(&self, cx: &App) -> HashSet { + self.servers + .keys() + .cloned() + .chain( + self.registry + .read(cx) + .context_server_descriptors() + .into_iter() + .map(|(id, _)| ContextServerId(id)), + ) .collect() } From 430ac5175f47dd39526f765fff4eb9bef8d181a0 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 19 Sep 2025 09:14:52 -0400 Subject: [PATCH 140/721] python: Install basedpyright with npm instead of pip (#38471) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/languages/src/lib.rs | 2 +- crates/languages/src/python.rs | 246 ++++++++++++++++----------------- 2 files changed, 117 insertions(+), 131 deletions(-) diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index f5a4a8c6f6480de7589f0a418157fafbf5fbe2ed..186d50d6ffbe9ea9861ccd5325a89c23062fd89e 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -94,7 +94,7 @@ pub fn init(languages: Arc, fs: Arc, node: NodeRuntime let ty_lsp_adapter = Arc::new(python::TyLspAdapter::new(fs.clone())); let python_context_provider = Arc::new(python::PythonContextProvider); let python_lsp_adapter = Arc::new(python::PyrightLspAdapter::new(node.clone())); - let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new()); + let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new(node.clone())); let ruff_lsp_adapter = Arc::new(RuffLspAdapter::new(fs.clone())); let python_toolchain_provider = Arc::new(python::PythonToolchainProvider); let rust_context_provider = Arc::new(rust::RustContextProvider); diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 91c1c675f8090d9d0161b6d3733d34ed386cfb50..d6d22399b4b8cbc04c87e416a27db8fcdc5eca24 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -29,7 +29,6 @@ use parking_lot::Mutex; use std::str::FromStr; use std::{ borrow::Cow, - ffi::OsString, fmt::Write, path::{Path, PathBuf}, sync::Arc, @@ -65,9 +64,6 @@ impl ManifestProvider for PyprojectTomlManifestProvider { } } -const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; -const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; - enum TestRunner { UNITTEST, PYTEST, @@ -85,10 +81,6 @@ impl FromStr for TestRunner { } } -fn server_binary_arguments(server_path: &Path) -> Vec { - vec![server_path.into(), "--stdio".into()] -} - /// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. /// Where `XX` is the sorting category, `YYYY` is based on most recent usage, /// and `name` is the symbol name itself. @@ -334,10 +326,29 @@ pub struct PyrightLspAdapter { impl PyrightLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright"); + const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; + const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; pub fn new(node: NodeRuntime) -> Self { PyrightLspAdapter { node } } + + async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, + ) -> Option { + let server_path = container_dir.join(Self::SERVER_PATH); + if server_path.exists() { + Some(LanguageServerBinary { + path: node.binary_path().await.log_err()?, + env: None, + arguments: vec![server_path.into(), "--stdio".into()], + }) + } else { + log::error!("missing executable in directory {:?}", server_path); + None + } + } } #[async_trait(?Send)] @@ -550,13 +561,13 @@ impl LspInstaller for PyrightLspAdapter { .await .log_err()??; - let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); + let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH); let env = delegate.shell_env().await; Some(LanguageServerBinary { path: node, env: Some(env), - arguments: server_binary_arguments(&path), + arguments: vec![path.into(), "--stdio".into()], }) } } @@ -567,7 +578,7 @@ impl LspInstaller for PyrightLspAdapter { container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { - let server_path = container_dir.join(SERVER_PATH); + let server_path = container_dir.join(Self::SERVER_PATH); self.node .npm_install_packages( @@ -580,7 +591,7 @@ impl LspInstaller for PyrightLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, env: Some(env), - arguments: server_binary_arguments(&server_path), + arguments: vec![server_path.into(), "--stdio".into()], }) } @@ -590,7 +601,7 @@ impl LspInstaller for PyrightLspAdapter { container_dir: &PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option { - let server_path = container_dir.join(SERVER_PATH); + let server_path = container_dir.join(Self::SERVER_PATH); let should_install_language_server = self .node @@ -609,7 +620,7 @@ impl LspInstaller for PyrightLspAdapter { Some(LanguageServerBinary { path: self.node.binary_path().await.ok()?, env: Some(env), - arguments: server_binary_arguments(&server_path), + arguments: vec![server_path.into(), "--stdio".into()], }) } } @@ -619,29 +630,12 @@ impl LspInstaller for PyrightLspAdapter { container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option { - let mut binary = get_cached_server_binary(container_dir, &self.node).await?; + let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?; binary.env = Some(delegate.shell_env().await); Some(binary) } } -async fn get_cached_server_binary( - container_dir: PathBuf, - node: &NodeRuntime, -) -> Option { - let server_path = container_dir.join(SERVER_PATH); - if server_path.exists() { - Some(LanguageServerBinary { - path: node.binary_path().await.log_err()?, - env: None, - arguments: server_binary_arguments(&server_path), - }) - } else { - log::error!("missing executable in directory {:?}", server_path); - None - } -} - pub(crate) struct PythonContextProvider; const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName = @@ -1606,64 +1600,34 @@ impl LspInstaller for PyLspAdapter { } pub(crate) struct BasedPyrightLspAdapter { - python_venv_base: OnceCell, String>>, + node: NodeRuntime, } impl BasedPyrightLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("basedpyright"); const BINARY_NAME: &'static str = "basedpyright-langserver"; + const SERVER_PATH: &str = "node_modules/basedpyright/langserver.index.js"; + const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "basedpyright/langserver.index.js"; - pub(crate) fn new() -> Self { - Self { - python_venv_base: OnceCell::new(), - } + pub(crate) fn new(node: NodeRuntime) -> Self { + BasedPyrightLspAdapter { node } } - async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result> { - let python_path = Self::find_base_python(delegate) - .await - .context("Could not find Python installation for basedpyright")?; - let work_dir = delegate - .language_server_download_dir(&Self::SERVER_NAME) - .await - .context("Could not get working directory for basedpyright")?; - let mut path = PathBuf::from(work_dir.as_ref()); - path.push("basedpyright-venv"); - if !path.exists() { - util::command::new_smol_command(python_path) - .arg("-m") - .arg("venv") - .arg("basedpyright-venv") - .current_dir(work_dir) - .spawn() - .context("spawning child")? - .output() - .await - .context("getting child output")?; - } - - Ok(path.into()) - } - - // Find "baseline", user python version from which we'll create our own venv. - async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option { - for path in ["python3", "python"] { - if let Some(path) = delegate.which(path.as_ref()).await { - return Some(path); - } - } - None - } - - async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result, String> { - self.python_venv_base - .get_or_init(move || async move { - Self::ensure_venv(delegate) - .await - .map_err(|e| format!("{e}")) + async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, + ) -> Option { + let server_path = container_dir.join(Self::SERVER_PATH); + if server_path.exists() { + Some(LanguageServerBinary { + path: node.binary_path().await.log_err()?, + env: None, + arguments: vec![server_path.into(), "--stdio".into()], }) - .await - .clone() + } else { + log::error!("missing executable in directory {:?}", server_path); + None + } } } @@ -1853,90 +1817,112 @@ impl LspAdapter for BasedPyrightLspAdapter { } impl LspInstaller for BasedPyrightLspAdapter { - type BinaryVersion = (); + type BinaryVersion = String; async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, _: bool, _: &mut AsyncApp, - ) -> Result<()> { - Ok(()) + ) -> Result { + self.node + .npm_package_latest_version(Self::SERVER_NAME.as_ref()) + .await } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, - toolchain: Option, + _: Option, _: &AsyncApp, ) -> Option { - if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await { + if let Some(path) = delegate.which(Self::BINARY_NAME.as_ref()).await { let env = delegate.shell_env().await; Some(LanguageServerBinary { - path: bin, + path, env: Some(env), arguments: vec!["--stdio".into()], }) } else { - let path = Path::new(toolchain?.path.as_ref()) - .parent()? - .join(Self::BINARY_NAME); - delegate - .which(path.as_os_str()) + // TODO shouldn't this be self.node.binary_path()? + let node = delegate.which("node".as_ref()).await?; + let (node_modules_path, _) = delegate + .npm_package_installed_version(Self::SERVER_NAME.as_ref()) .await - .map(|_| LanguageServerBinary { - path, - arguments: vec!["--stdio".into()], - env: None, - }) + .log_err()??; + + let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH); + + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: node, + env: Some(env), + arguments: vec![path.into(), "--stdio".into()], + }) } } async fn fetch_server_binary( &self, - _latest_version: (), - _container_dir: PathBuf, + latest_version: Self::BinaryVersion, + container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { - let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; - let pip_path = venv.join(BINARY_DIR).join("pip3"); - ensure!( - util::command::new_smol_command(pip_path.as_path()) - .arg("install") - .arg("basedpyright") - .arg("--upgrade") - .output() - .await - .context("getting pip install output")? - .status - .success(), - "basedpyright installation failed" - ); - let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); - ensure!( - delegate.which(path.as_os_str()).await.is_some(), - "basedpyright installation was incomplete" - ); + let server_path = container_dir.join(Self::SERVER_PATH); + + self.node + .npm_install_packages( + &container_dir, + &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())], + ) + .await?; + + let env = delegate.shell_env().await; Ok(LanguageServerBinary { - path, - env: None, - arguments: vec!["--stdio".into()], + path: self.node.binary_path().await?, + env: Some(env), + arguments: vec![server_path.into(), "--stdio".into()], }) } + async fn check_if_version_installed( + &self, + version: &Self::BinaryVersion, + container_dir: &PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Option { + let server_path = container_dir.join(Self::SERVER_PATH); + + let should_install_language_server = self + .node + .should_install_npm_package( + Self::SERVER_NAME.as_ref(), + &server_path, + container_dir, + VersionStrategy::Latest(version), + ) + .await; + + if should_install_language_server { + None + } else { + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: self.node.binary_path().await.ok()?, + env: Some(env), + arguments: vec![server_path.into(), "--stdio".into()], + }) + } + } + async fn cached_server_binary( &self, - _container_dir: PathBuf, + container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option { - let venv = self.base_venv(delegate).await.ok()?; - let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); - delegate.which(path.as_os_str()).await?; - Some(LanguageServerBinary { - path, - env: None, - arguments: vec!["--stdio".into()], - }) + let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?; + binary.env = Some(delegate.shell_env().await); + Some(binary) } } From 9e6f1d5a6eac1b3b603d828470064718d97fbc01 Mon Sep 17 00:00:00 2001 From: Derek Nguyen <79728577+derekntnguyen@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:29:40 -0400 Subject: [PATCH 141/721] python: Fix ty binary path and required args (#38458) Closes #38347 Release Notes: - Fixed path and args to ty lsp binary When attempting to use the new ty lsp integration in the preview, I noticed issues related to accessing the binary. After deleting the downloaded archive and adding the following changes that: - downloads the archive with the correct `AssetKind::TarGz` - uses the correct path to the extracted binary - adds the `server` argument to initialize the lsp (like ruff) After the above changes the LSP starts correctly ```bash 2025-09-18T16:17:03-05:00 INFO [lsp] starting language server process. binary path: "/Users/dereknguyen/Library/Application Support/Zed/languages/ty/ty-0.0.1-alpha.20/ty-aarch64-apple-darwin/ty", working directory: "/Users/dereknguyen/projects/test-project", args: ["server"] ``` image --------- Co-authored-by: Cole Miller --- crates/languages/src/python.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index d6d22399b4b8cbc04c87e416a27db8fcdc5eca24..a8824d3776b08bdfdb99d216c8ab75e88e714c6c 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -100,7 +100,7 @@ pub struct TyLspAdapter { #[cfg(target_os = "macos")] impl TyLspAdapter { - const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; + const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz; const ARCH_SERVER_NAME: &str = "apple-darwin"; } @@ -216,15 +216,20 @@ impl LspInstaller for TyLspAdapter { digest: expected_digest, } = latest_version; let destination_path = container_dir.join(format!("ty-{name}")); + + async_fs::create_dir_all(&destination_path).await?; + let server_path = match Self::GITHUB_ASSET_KIND { - AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place. - AssetKind::Zip => destination_path.clone().join("ty.exe"), // zip contains a .exe + AssetKind::TarGz | AssetKind::Gz => destination_path + .join(Self::build_asset_name()?.0) + .join("ty"), + AssetKind::Zip => destination_path.clone().join("ty.exe"), }; let binary = LanguageServerBinary { path: server_path.clone(), env: None, - arguments: Default::default(), + arguments: vec!["server".into()], }; let metadata_path = destination_path.with_extension("metadata"); @@ -283,7 +288,7 @@ impl LspInstaller for TyLspAdapter { Ok(LanguageServerBinary { path: server_path, env: None, - arguments: Default::default(), + arguments: vec!["server".into()], }) } @@ -305,14 +310,16 @@ impl LspInstaller for TyLspAdapter { let path = last.context("no cached binary")?; let path = match TyLspAdapter::GITHUB_ASSET_KIND { - AssetKind::TarGz | AssetKind::Gz => path, // Tar and gzip extract in place. - AssetKind::Zip => path.join("ty.exe"), // zip contains a .exe + AssetKind::TarGz | AssetKind::Gz => { + path.join(Self::build_asset_name()?.0).join("ty") + } + AssetKind::Zip => path.join("ty.exe"), }; anyhow::Ok(LanguageServerBinary { path, env: None, - arguments: Default::default(), + arguments: vec!["server".into()], }) }) .await From a3da66cec0f770b7d79b24891e960cbc3c81529d Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Fri, 19 Sep 2025 15:41:52 +0200 Subject: [PATCH 142/721] editor: Correct "Toggle Excerpt Fold" tip on macOS (#38487) Show `"Option+click to toggle all"` instead of `"Alt+click to toggle all" on macOS. Screenshot 2025-09-19 at 10 16 11 Release Notes: - N/A --- crates/editor/src/element.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 3bc05ccd537710c34c1c4e8e6d63c26440360f2e..28fe68e71cb4fac36f84d1161020e16ba2d0605f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3838,7 +3838,11 @@ impl EditorElement { Tooltip::with_meta_in( "Toggle Excerpt Fold", Some(&ToggleFold), - "Alt+click to toggle all", + if cfg!(target_os = "macos") { + "Option+click to toggle all" + } else { + "Alt+click to toggle all" + }, &focus_handle, window, cx, From 3217bcb83efda44392047ceae5f3ac0febb3207d Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 19 Sep 2025 09:59:13 -0400 Subject: [PATCH 143/721] docs: Add Kotlin JAVA_HOME example (#38507) Closes: https://github.com/zed-extensions/kotlin/issues/46 Release Notes: - N/A --- docs/src/languages/kotlin.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/src/languages/kotlin.md b/docs/src/languages/kotlin.md index 3955062a77783629c0bd838f7dd093af31f3aa0d..60d66f277eb62c2bdf9905687045abbca4db20b9 100644 --- a/docs/src/languages/kotlin.md +++ b/docs/src/languages/kotlin.md @@ -11,6 +11,12 @@ Report issues to: [https://github.com/zed-extensions/kotlin/issues](https://gith Workspace configuration options can be passed to the language server via lsp settings in `settings.json`. +The full list of lsp `settings` can be found +[here](https://github.com/fwcd/kotlin-language-server/blob/main/server/src/main/kotlin/org/javacs/kt/Configuration.kt) +under `class Configuration` and initialization_options under `class InitializationOptions`. + +### JVM Target + The following example changes the JVM target from `default` (which is 1.8) to `17`: @@ -30,5 +36,20 @@ The following example changes the JVM target from `default` (which is 1.8) to } ``` -The full list of workspace configuration options can be found -[here](https://github.com/fwcd/kotlin-language-server/blob/main/server/src/main/kotlin/org/javacs/kt/Configuration.kt). +### JAVA_HOME + +To use a specific java installation, just specify the `JAVA_HOME` environment variable with: + +```json +{ + "lsp": { + "kotlin-language-server": { + "binary": { + "env": { + "JAVA_HOME": "/Users/whatever/Applications/Work/Android Studio.app/Contents/jbr/Contents/Home" + } + } + } + } +} +``` From aa5b99dc1153db2faf02ad80f4e3287537f19190 Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Fri, 19 Sep 2025 16:12:49 +0200 Subject: [PATCH 144/721] Fully qualify images in Docker Compose (#38496) This enables podman-compose (easier to install and run on linux) as drop in replacement for docker-compose Release Notes: - N/A --- compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compose.yml b/compose.yml index d0d9bac425356687bfb33efab9ee24e76d1b30a0..00a5780b597738260f90020f139627e7d0b0107c 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,6 @@ services: postgres: - image: postgres:15 + image: docker.io/library/postgres:15 container_name: zed_postgres ports: - 5432:5432 @@ -23,7 +23,7 @@ services: - ./.blob_store:/data livekit_server: - image: livekit/livekit-server + image: docker.io/livekit/livekit-server container_name: livekit_server entrypoint: /livekit-server --config /livekit.yaml ports: @@ -34,7 +34,7 @@ services: - ./livekit.yaml:/livekit.yaml postgrest_app: - image: postgrest/postgrest + image: docker.io/postgrest/postgrest container_name: postgrest_app ports: - 8081:8081 @@ -47,7 +47,7 @@ services: - postgres postgrest_llm: - image: postgrest/postgrest + image: docker.io/postgrest/postgrest container_name: postgrest_llm ports: - 8082:8082 @@ -60,7 +60,7 @@ services: - postgres stripe-mock: - image: stripe/stripe-mock:v0.178.0 + image: docker.io/stripe/stripe-mock:v0.178.0 ports: - 12111:12111 - 12112:12112 From 2e97ef32c4391681ba891d25aaa5a9bdd3710d3a Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Fri, 19 Sep 2025 16:33:38 +0200 Subject: [PATCH 145/721] Revert "Audio fixes and mic denoise" (#38509) Reverts zed-industries/zed#38493 Release Notes: - N/A --- Cargo.lock | 2 +- assets/settings/default.json | 32 +--- crates/audio/Cargo.toml | 1 - crates/audio/src/audio.rs | 65 ++----- crates/audio/src/audio_settings.rs | 95 +++------- crates/audio/src/rodio_ext.rs | 167 +----------------- crates/denoise/src/engine.rs | 6 +- crates/denoise/src/lib.rs | 2 +- crates/livekit_client/src/livekit_client.rs | 20 +-- .../src/livekit_client/playback.rs | 89 +++------- .../src/livekit_client/playback/source.rs | 40 ++--- crates/settings/src/settings_content.rs | 44 ++--- tooling/workspace-hack/Cargo.toml | 14 +- 13 files changed, 123 insertions(+), 454 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be3e5b04ca18d56024eabe45f14562fca3d56375..3acfed9bd7cfa8bc2742bb4f006c38a4f65a1f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1405,7 +1405,6 @@ dependencies = [ "async-tar", "collections", "crossbeam", - "denoise", "gpui", "libwebrtc", "log", @@ -20743,6 +20742,7 @@ dependencies = [ "nix 0.29.0", "nix 0.30.1", "nom 7.1.3", + "num", "num-bigint", "num-bigint-dig", "num-complex", diff --git a/assets/settings/default.json b/assets/settings/default.json index d469638ab28ea02eb9b7675296ee9582e2de3ccd..091231521470ebec50cf1351a76063e9205a3d24 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -413,33 +413,15 @@ "experimental.rodio_audio": false, // Requires 'rodio_audio: true' // - // Automatically increase or decrease you microphone's volume. This affects how - // loud you sound to others. - // - // Recommended: off (default) - // Microphones are too quite in zed, until everyone is on experimental - // audio and has auto speaker volume on this will make you very loud - // compared to other speakers. - "experimental.auto_microphone_volume": false, - // Requires 'rodio_audio: true' - // - // Automatically increate or decrease the volume of other call members. - // This only affects how things sound for you. - "experimental.auto_speaker_volume": true, + // Use the new audio systems automatic gain control for your microphone. + // This affects how loud you sound to others. + "experimental.control_input_volume": false, // Requires 'rodio_audio: true' // - // Remove background noises. Works great for typing, cars, dogs, AC. Does - // not work well on music. - "experimental.denoise": true, - // Requires 'rodio_audio: true' - // - // Use audio parameters compatible with the previous versions of - // experimental audio and non-experimental audio. When this is false you - // will sound strange to anyone not on the latest experimental audio. In - // the future we will migrate by setting this to false - // - // You need to rejoin a call for this setting to apply - "experimental.legacy_audio_compatible": true + // Use the new audio systems automatic gain control on everyone in the + // call. This makes call members who are too quite louder and those who are + // too loud quieter. This only affects how things sound for you. + "experimental.control_output_volume": false }, // Scrollbar related settings "scrollbar": { diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 7f2fed80e2315e51fca7d8477b04885998336632..c083c9a659e50aef37acc2cdfc239696bd469c1e 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -18,7 +18,6 @@ async-tar.workspace = true collections.workspace = true crossbeam.workspace = true gpui.workspace = true -denoise = { path = "../denoise" } log.workspace = true parking_lot.workspace = true rodio = { workspace = true, features = [ "wav", "playback", "wav_output" ] } diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs index dc4d97a8fa47f11f9120cf5144a37ae6fd94bc2a..f60ddb87b9615d2da9c2be248ab397c19a463616 100644 --- a/crates/audio/src/audio.rs +++ b/crates/audio/src/audio.rs @@ -9,7 +9,7 @@ mod non_windows_and_freebsd_deps { pub(super) use log::info; pub(super) use parking_lot::Mutex; pub(super) use rodio::cpal::Sample; - pub(super) use rodio::source::LimitSettings; + pub(super) use rodio::source::{LimitSettings, UniformSourceIterator}; pub(super) use std::sync::Arc; } @@ -31,20 +31,18 @@ pub use rodio_ext::RodioExt; use crate::audio_settings::LIVE_SETTINGS; -// We are migrating to 16kHz sample rate from 48kHz. In the future -// once we are reasonably sure most users have upgraded we will -// remove the LEGACY parameters. +// NOTE: We used to use WebRTC's mixer which only supported +// 16kHz, 32kHz and 48kHz. As 48 is the most common "next step up" +// for audio output devices like speakers/bluetooth, we just hard-code +// this; and downsample when we need to. // -// We migrate to 16kHz because it is sufficient for speech and required -// by the denoiser and future Speech to Text layers. -pub const SAMPLE_RATE: NonZero = nz!(16000); -pub const CHANNEL_COUNT: NonZero = nz!(1); +// Since most noise cancelling requires 16kHz we will move to +// that in the future. +pub const SAMPLE_RATE: NonZero = nz!(48000); +pub const CHANNEL_COUNT: NonZero = nz!(2); pub const BUFFER_SIZE: usize = // echo canceller and livekit want 10ms of audio (SAMPLE_RATE.get() as usize / 100) * CHANNEL_COUNT.get() as usize; -pub const LEGACY_SAMPLE_RATE: NonZero = nz!(48000); -pub const LEGACY_CHANNEL_COUNT: NonZero = nz!(2); - pub const REPLAY_DURATION: Duration = Duration::from_secs(30); pub fn init(cx: &mut App) { @@ -108,11 +106,6 @@ impl Global for Audio {} impl Audio { fn ensure_output_exists(&mut self) -> Result<&Mixer> { - #[cfg(debug_assertions)] - log::warn!( - "Audio does not sound correct without optimizations. Use a release build to debug audio issues" - ); - if self.output_handle.is_none() { self.output_handle = Some( OutputStreamBuilder::open_default_stream() @@ -167,20 +160,13 @@ impl Audio { let stream = rodio::microphone::MicrophoneBuilder::new() .default_device()? .default_config()? - .prefer_sample_rates([ - SAMPLE_RATE, // sample rates trivially resamplable to `SAMPLE_RATE` - SAMPLE_RATE.saturating_mul(nz!(2)), - SAMPLE_RATE.saturating_mul(nz!(3)), - SAMPLE_RATE.saturating_mul(nz!(4)), - ]) - .prefer_channel_counts([nz!(1), nz!(2), nz!(3), nz!(4)]) + .prefer_sample_rates([SAMPLE_RATE, SAMPLE_RATE.saturating_mul(nz!(2))]) + // .prefer_channel_counts([nz!(1), nz!(2)]) .prefer_buffer_sizes(512..) .open_stream()?; info!("Opened microphone: {:?}", stream.config()); - let (replay, stream) = stream - .possibly_disconnected_channels_to_mono() - .constant_samplerate(SAMPLE_RATE) + let (replay, stream) = UniformSourceIterator::new(stream, CHANNEL_COUNT, SAMPLE_RATE) .limit(LimitSettings::live_performance()) .process_buffer::(move |buffer| { let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample()); @@ -201,28 +187,15 @@ impl Audio { } } }) - .denoise() - .context("Could not set up denoiser")? - .periodic_access(Duration::from_millis(100), move |denoise| { - denoise.set_enabled(LIVE_SETTINGS.denoise.load(Ordering::Relaxed)); - }) - .automatic_gain_control(1.0, 2.0, 0.0, 5.0) + .automatic_gain_control(1.0, 4.0, 0.0, 5.0) .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source - .set_enabled(LIVE_SETTINGS.auto_microphone_volume.load(Ordering::Relaxed)); + agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); }) .replayable(REPLAY_DURATION)?; voip_parts .replays .add_voip_stream("local microphone".to_string(), replay); - - let stream = if voip_parts.legacy_audio_compatible { - stream.constant_params(LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE) - } else { - stream.constant_params(CHANNEL_COUNT, SAMPLE_RATE) - }; - Ok(stream) } @@ -233,10 +206,9 @@ impl Audio { cx: &mut App, ) -> anyhow::Result<()> { let (replay_source, source) = source - .constant_params(CHANNEL_COUNT, SAMPLE_RATE) - .automatic_gain_control(1.0, 2.0, 0.0, 5.0) + .automatic_gain_control(1.0, 4.0, 0.0, 5.0) .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source.set_enabled(LIVE_SETTINGS.auto_speaker_volume.load(Ordering::Relaxed)); + agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); }) .replayable(REPLAY_DURATION) .expect("REPLAY_DURATION is longer than 100ms"); @@ -297,7 +269,6 @@ impl Audio { pub struct VoipParts { echo_canceller: Arc>, replays: replays::Replays, - legacy_audio_compatible: bool, } #[cfg(not(any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")))] @@ -306,12 +277,8 @@ impl VoipParts { let (apm, replays) = cx.try_read_default_global::(|audio, _| { (Arc::clone(&audio.echo_canceller), audio.replays.clone()) })?; - let legacy_audio_compatible = - AudioSettings::try_read_global(cx, |settings| settings.legacy_audio_compatible) - .unwrap_or_default(); Ok(Self { - legacy_audio_compatible, echo_canceller: apm, replays, }) diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index cba7d45c31f4674be6a69c10ab34f00e0b8cbbd1..2c9db4989efa5edcf4ef84c4e3031b53980fad51 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -6,38 +6,18 @@ use settings::{Settings, SettingsStore}; #[derive(Clone, Debug)] pub struct AudioSettings { /// Opt into the new audio system. - /// - /// You need to rejoin a call for this setting to apply pub rodio_audio: bool, // default is false /// Requires 'rodio_audio: true' /// - /// Automatically increase or decrease you microphone's volume. This affects how - /// loud you sound to others. - /// - /// Recommended: off (default) - /// Microphones are too quite in zed, until everyone is on experimental - /// audio and has auto speaker volume on this will make you very loud - /// compared to other speakers. - pub auto_microphone_volume: bool, - /// Requires 'rodio_audio: true' - /// - /// Automatically increate or decrease the volume of other call members. - /// This only affects how things sound for you. - pub auto_speaker_volume: bool, - /// Requires 'rodio_audio: true' - /// - /// Remove background noises. Works great for typing, cars, dogs, AC. Does - /// not work well on music. - pub denoise: bool, + /// Use the new audio systems automatic gain control for your microphone. + /// This affects how loud you sound to others. + pub control_input_volume: bool, /// Requires 'rodio_audio: true' /// - /// Use audio parameters compatible with the previous versions of - /// experimental audio and non-experimental audio. When this is false you - /// will sound strange to anyone not on the latest experimental audio. In - /// the future we will migrate by setting this to false - /// - /// You need to rejoin a call for this setting to apply - pub legacy_audio_compatible: bool, + /// Use the new audio systems automatic gain control on everyone in the + /// call. This makes call members who are too quite louder and those who are + /// too loud quieter. This only affects how things sound for you. + pub control_output_volume: bool, } /// Configuration of audio in Zed @@ -45,66 +25,46 @@ impl Settings for AudioSettings { fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let audio = &content.audio.as_ref().unwrap(); AudioSettings { + control_input_volume: audio.control_input_volume.unwrap(), + control_output_volume: audio.control_output_volume.unwrap(), rodio_audio: audio.rodio_audio.unwrap(), - auto_microphone_volume: audio.auto_microphone_volume.unwrap(), - auto_speaker_volume: audio.auto_speaker_volume.unwrap(), - denoise: audio.denoise.unwrap(), - legacy_audio_compatible: audio.legacy_audio_compatible.unwrap(), } } + + fn import_from_vscode( + _vscode: &settings::VsCodeSettings, + _current: &mut settings::SettingsContent, + ) { + } } /// See docs on [LIVE_SETTINGS] pub(crate) struct LiveSettings { - pub(crate) auto_microphone_volume: AtomicBool, - pub(crate) auto_speaker_volume: AtomicBool, - pub(crate) denoise: AtomicBool, + pub(crate) control_input_volume: AtomicBool, + pub(crate) control_output_volume: AtomicBool, } impl LiveSettings { pub(crate) fn initialize(&self, cx: &mut App) { cx.observe_global::(move |cx| { - LIVE_SETTINGS.auto_microphone_volume.store( - AudioSettings::get_global(cx).auto_microphone_volume, + LIVE_SETTINGS.control_input_volume.store( + AudioSettings::get_global(cx).control_input_volume, Ordering::Relaxed, ); - LIVE_SETTINGS.auto_speaker_volume.store( - AudioSettings::get_global(cx).auto_speaker_volume, + LIVE_SETTINGS.control_output_volume.store( + AudioSettings::get_global(cx).control_output_volume, Ordering::Relaxed, ); - - let denoise_enabled = AudioSettings::get_global(cx).denoise; - #[cfg(debug_assertions)] - { - static DENOISE_WARNING_SEND: AtomicBool = AtomicBool::new(false); - if denoise_enabled && !DENOISE_WARNING_SEND.load(Ordering::Relaxed) { - DENOISE_WARNING_SEND.store(true, Ordering::Relaxed); - log::warn!("Denoise does not work on debug builds, not enabling") - } - } - #[cfg(not(debug_assertions))] - LIVE_SETTINGS - .denoise - .store(denoise_enabled, Ordering::Relaxed); }) .detach(); let init_settings = AudioSettings::get_global(cx); LIVE_SETTINGS - .auto_microphone_volume - .store(init_settings.auto_microphone_volume, Ordering::Relaxed); - LIVE_SETTINGS - .auto_speaker_volume - .store(init_settings.auto_speaker_volume, Ordering::Relaxed); - let denoise_enabled = AudioSettings::get_global(cx).denoise; - #[cfg(debug_assertions)] - if denoise_enabled { - log::warn!("Denoise does not work on debug builds, not enabling") - } - #[cfg(not(debug_assertions))] + .control_input_volume + .store(init_settings.control_input_volume, Ordering::Relaxed); LIVE_SETTINGS - .denoise - .store(denoise_enabled, Ordering::Relaxed); + .control_output_volume + .store(init_settings.control_output_volume, Ordering::Relaxed); } } @@ -113,7 +73,6 @@ impl LiveSettings { /// real time and must each run in a dedicated OS thread, therefore we can not /// use the background executor. pub(crate) static LIVE_SETTINGS: LiveSettings = LiveSettings { - auto_microphone_volume: AtomicBool::new(true), - auto_speaker_volume: AtomicBool::new(true), - denoise: AtomicBool::new(true), + control_input_volume: AtomicBool::new(true), + control_output_volume: AtomicBool::new(true), }; diff --git a/crates/audio/src/rodio_ext.rs b/crates/audio/src/rodio_ext.rs index af4cc89252dfdc1498471ec7ac09b56d59b62eca..e80b00e15a8fdbd3fc438b78a9ca45d0902dcef1 100644 --- a/crates/audio/src/rodio_ext.rs +++ b/crates/audio/src/rodio_ext.rs @@ -1,5 +1,4 @@ use std::{ - num::NonZero, sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, @@ -8,22 +7,12 @@ use std::{ }; use crossbeam::queue::ArrayQueue; -use denoise::{Denoiser, DenoiserError}; -use log::warn; -use rodio::{ - ChannelCount, Sample, SampleRate, Source, conversions::SampleRateConverter, nz, - source::UniformSourceIterator, -}; - -const MAX_CHANNELS: usize = 8; +use rodio::{ChannelCount, Sample, SampleRate, Source}; #[derive(Debug, thiserror::Error)] #[error("Replay duration is too short must be >= 100ms")] pub struct ReplayDurationTooShort; -// These all require constant sources (so the span is infinitely long) -// this is not guaranteed by rodio however we know it to be true in all our -// applications. Rodio desperately needs a constant source concept. pub trait RodioExt: Source + Sized { fn process_buffer(self, callback: F) -> ProcessBuffer where @@ -36,14 +25,6 @@ pub trait RodioExt: Source + Sized { duration: Duration, ) -> Result<(Replay, Replayable), ReplayDurationTooShort>; fn take_samples(self, n: usize) -> TakeSamples; - fn denoise(self) -> Result, DenoiserError>; - fn constant_params( - self, - channel_count: ChannelCount, - sample_rate: SampleRate, - ) -> UniformSourceIterator; - fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate; - fn possibly_disconnected_channels_to_mono(self) -> ToMono; } impl RodioExt for S { @@ -120,149 +101,8 @@ impl RodioExt for S { left_to_take: n, } } - fn denoise(self) -> Result, DenoiserError> { - let res = Denoiser::try_new(self); - res - } - fn constant_params( - self, - channel_count: ChannelCount, - sample_rate: SampleRate, - ) -> UniformSourceIterator { - UniformSourceIterator::new(self, channel_count, sample_rate) - } - fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate { - ConstantSampleRate::new(self, sample_rate) - } - fn possibly_disconnected_channels_to_mono(self) -> ToMono { - ToMono::new(self) - } -} - -pub struct ConstantSampleRate { - inner: SampleRateConverter, - channels: ChannelCount, - sample_rate: SampleRate, -} - -impl ConstantSampleRate { - fn new(source: S, target_rate: SampleRate) -> Self { - let input_sample_rate = source.sample_rate(); - let channels = source.channels(); - let inner = SampleRateConverter::new(source, input_sample_rate, target_rate, channels); - Self { - inner, - channels, - sample_rate: target_rate, - } - } -} - -impl Iterator for ConstantSampleRate { - type Item = rodio::Sample; - - fn next(&mut self) -> Option { - self.inner.next() - } - - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} - -impl Source for ConstantSampleRate { - fn current_span_len(&self) -> Option { - None - } - - fn channels(&self) -> ChannelCount { - self.channels - } - - fn sample_rate(&self) -> SampleRate { - self.sample_rate - } - - fn total_duration(&self) -> Option { - None // not supported (not used by us) - } -} - -const TYPICAL_NOISE_FLOOR: Sample = 1e-3; - -/// constant source, only works on a single span -pub struct ToMono { - inner: S, - input_channel_count: ChannelCount, - connected_channels: ChannelCount, - /// running mean of second channel 'volume' - means: [f32; MAX_CHANNELS], -} -impl ToMono { - fn new(input: S) -> Self { - let channels = input - .channels() - .min(const { NonZero::::new(MAX_CHANNELS as u16).unwrap() }); - if channels < input.channels() { - warn!("Ignoring input channels {}..", channels.get()); - } - - Self { - connected_channels: channels, - input_channel_count: channels, - inner: input, - means: [TYPICAL_NOISE_FLOOR; MAX_CHANNELS], - } - } -} - -impl Source for ToMono { - fn current_span_len(&self) -> Option { - None - } - - fn channels(&self) -> ChannelCount { - rodio::nz!(1) - } - - fn sample_rate(&self) -> SampleRate { - self.inner.sample_rate() - } - - fn total_duration(&self) -> Option { - self.inner.total_duration() - } -} - -fn update_mean(mean: &mut f32, sample: Sample) { - const HISTORY: f32 = 500.0; - *mean *= (HISTORY - 1.0) / HISTORY; - *mean += sample.abs() / HISTORY; -} - -impl Iterator for ToMono { - type Item = Sample; - - fn next(&mut self) -> Option { - let mut mono_sample = 0f32; - let mut active_channels = 0; - for channel in 0..self.input_channel_count.get() as usize { - let sample = self.inner.next()?; - mono_sample += sample; - - update_mean(&mut self.means[channel], sample); - if self.means[channel] > TYPICAL_NOISE_FLOOR / 10.0 { - active_channels += 1; - } - } - mono_sample /= self.connected_channels.get() as f32; - self.connected_channels = NonZero::new(active_channels).unwrap_or(nz!(1)); - - Some(mono_sample) - } } -/// constant source, only works on a single span pub struct TakeSamples { inner: S, left_to_take: usize, @@ -307,7 +147,6 @@ impl Source for TakeSamples { } } -/// constant source, only works on a single span #[derive(Debug)] struct ReplayQueue { inner: ArrayQueue>, @@ -354,7 +193,6 @@ impl ReplayQueue { } } -/// constant source, only works on a single span pub struct ProcessBuffer where S: Source + Sized, @@ -422,7 +260,6 @@ where } } -/// constant source, only works on a single span pub struct InspectBuffer where S: Source + Sized, @@ -487,7 +324,6 @@ where } } -/// constant source, only works on a single span #[derive(Debug)] pub struct Replayable { inner: S, @@ -539,7 +375,6 @@ impl Source for Replayable { } } -/// constant source, only works on a single span #[derive(Debug)] pub struct Replay { rx: Arc, diff --git a/crates/denoise/src/engine.rs b/crates/denoise/src/engine.rs index be0548c689e3b902342cd1cb6d6d8e29351e8be4..5196b70b5ba02f665385c022a0dfa9cd22c1db9c 100644 --- a/crates/denoise/src/engine.rs +++ b/crates/denoise/src/engine.rs @@ -138,13 +138,13 @@ impl Engine { const SPECTRUM_INPUT: &str = "input_2"; const MEMORY_INPUT: &str = "input_3"; - let spectrum = + let memory_input = Tensor::from_slice::<_, f32>(&self.in_magnitude, (1, 1, FFT_OUT_SIZE), &Device::Cpu) .expect("the in magnitude has enough elements to fill the Tensor"); let inputs = HashMap::from([ - (SPECTRUM_INPUT.to_string(), spectrum), - (MEMORY_INPUT.to_string(), self.spectral_memory.clone()), + (MEMORY_INPUT.to_string(), memory_input), + (SPECTRUM_INPUT.to_string(), self.spectral_memory.clone()), ]); inputs } diff --git a/crates/denoise/src/lib.rs b/crates/denoise/src/lib.rs index f6cbf0fadf1f216cc6168c2b249f807b557869af..1422c81a4b915d571d35585447165c04d3695b73 100644 --- a/crates/denoise/src/lib.rs +++ b/crates/denoise/src/lib.rs @@ -84,7 +84,7 @@ impl Denoiser { .spawn(move || { run_neural_denoiser(denoised_tx, input_rx); }) - .expect("Should be ablet to spawn threads"); + .unwrap(); Ok(Self { inner: source, diff --git a/crates/livekit_client/src/livekit_client.rs b/crates/livekit_client/src/livekit_client.rs index 04e669869ddbf64ffd92cbcad4bf927bfec55cb5..45e929cb2ec0bebf054497632d614af1975f6397 100644 --- a/crates/livekit_client/src/livekit_client.rs +++ b/crates/livekit_client/src/livekit_client.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Context as _, Result}; use audio::AudioSettings; use collections::HashMap; use futures::{SinkExt, channel::mpsc}; @@ -12,10 +12,7 @@ use settings::Settings; mod playback; -use crate::{ - LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication, - livekit_client::playback::Speaker, -}; +use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication}; pub use playback::AudioStream; pub(crate) use playback::{RemoteVideoFrame, play_remote_video_track}; @@ -135,20 +132,11 @@ impl Room { track: &RemoteAudioTrack, cx: &mut App, ) -> Result { - let speaker: Speaker = - serde_urlencoded::from_str(&track.0.name()).unwrap_or_else(|_| Speaker { - name: track.0.name(), - is_staff: false, - legacy_audio_compatible: true, - }); - if AudioSettings::get_global(cx).rodio_audio { info!("Using experimental.rodio_audio audio pipeline for output"); - playback::play_remote_audio_track(&track.0, speaker, cx) - } else if speaker.legacy_audio_compatible { - Ok(self.playback.play_remote_audio_track(&track.0)) + playback::play_remote_audio_track(&track.0, cx) } else { - Err(anyhow!("Client version too old to play audio in call")) + Ok(self.playback.play_remote_audio_track(&track.0)) } } } diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index b4cd68e08e4a88f9cb248e3b7ac64fbfca4c39de..df8b5ea54fb1ce11bf871faa912757bbff1fd7f9 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; -use audio::{AudioSettings, CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE, SAMPLE_RATE}; +use audio::{AudioSettings, CHANNEL_COUNT, SAMPLE_RATE}; use cpal::traits::{DeviceTrait, StreamTrait as _}; use futures::channel::mpsc::UnboundedSender; use futures::{Stream, StreamExt as _}; @@ -43,17 +43,12 @@ pub(crate) struct AudioStack { pub(crate) fn play_remote_audio_track( track: &livekit::track::RemoteAudioTrack, - speaker: Speaker, cx: &mut gpui::App, ) -> Result { - let stream = source::LiveKitStream::new( - cx.background_executor(), - track, - speaker.legacy_audio_compatible, - ); - let stop_handle = Arc::new(AtomicBool::new(false)); let stop_handle_clone = stop_handle.clone(); + let stream = source::LiveKitStream::new(cx.background_executor(), track); + let stream = stream .stoppable() .periodic_access(Duration::from_millis(50), move |s| { @@ -62,6 +57,10 @@ pub(crate) fn play_remote_audio_track( } }); + let speaker: Speaker = serde_urlencoded::from_str(&track.name()).unwrap_or_else(|_| Speaker { + name: track.name(), + is_staff: false, + }); audio::Audio::play_voip_stream(stream, speaker.name, speaker.is_staff, cx) .context("Could not play audio")?; @@ -153,32 +152,17 @@ impl AudioStack { is_staff: bool, cx: &AsyncApp, ) -> Result<(crate::LocalAudioTrack, AudioStream)> { - let legacy_audio_compatible = - AudioSettings::try_read_global(cx, |setting| setting.legacy_audio_compatible) - .unwrap_or_default(); - - let source = if legacy_audio_compatible { - NativeAudioSource::new( - // n.b. this struct's options are always ignored, noise cancellation is provided by apm. - AudioSourceOptions::default(), - LEGACY_SAMPLE_RATE.get(), - LEGACY_CHANNEL_COUNT.get().into(), - 10, - ) - } else { - NativeAudioSource::new( - // n.b. this struct's options are always ignored, noise cancellation is provided by apm. - AudioSourceOptions::default(), - SAMPLE_RATE.get(), - CHANNEL_COUNT.get().into(), - 10, - ) - }; + let source = NativeAudioSource::new( + // n.b. this struct's options are always ignored, noise cancellation is provided by apm. + AudioSourceOptions::default(), + SAMPLE_RATE.get(), + CHANNEL_COUNT.get().into(), + 10, + ); let track_name = serde_urlencoded::to_string(Speaker { name: user_name, is_staff, - legacy_audio_compatible, }) .context("Could not encode user information in track name")?; @@ -202,32 +186,22 @@ impl AudioStack { let capture_task = if rodio_pipeline { info!("Using experimental.rodio_audio audio pipeline"); let voip_parts = audio::VoipParts::new(cx)?; - // Audio needs to run real-time and should never be paused. That is - // why we are using a normal std::thread and not a background task + // Audio needs to run real-time and should never be paused. That is why we are using a + // normal std::thread and not a background task thread::Builder::new() - .name("MicrophoneToLivekit".to_string()) + .name("AudioCapture".to_string()) .spawn(move || { // microphone is non send on mac - let microphone = match audio::Audio::open_microphone(voip_parts) { - Ok(m) => m, - Err(e) => { - log::error!("Could not open microphone: {e}"); - return; - } - }; + let microphone = audio::Audio::open_microphone(voip_parts)?; send_to_livekit(frame_tx, microphone); + Ok::<(), anyhow::Error>(()) }) - .expect("should be able to spawn threads"); + .unwrap(); Task::ready(Ok(())) } else { self.executor.spawn(async move { - Self::capture_input( - apm, - frame_tx, - LEGACY_SAMPLE_RATE.get(), - LEGACY_CHANNEL_COUNT.get().into(), - ) - .await + Self::capture_input(apm, frame_tx, SAMPLE_RATE.get(), CHANNEL_COUNT.get().into()) + .await }) }; @@ -415,30 +389,25 @@ impl AudioStack { } #[derive(Serialize, Deserialize)] -pub struct Speaker { - pub name: String, - pub is_staff: bool, - pub legacy_audio_compatible: bool, +struct Speaker { + name: String, + is_staff: bool, } fn send_to_livekit(frame_tx: UnboundedSender>, mut microphone: impl Source) { use cpal::Sample; - let sample_rate = microphone.sample_rate().get(); - let num_channels = microphone.channels().get() as u32; - let buffer_size = sample_rate / 100 * num_channels; - loop { let sampled: Vec<_> = microphone .by_ref() - .take(buffer_size as usize) + .take(audio::BUFFER_SIZE) .map(|s| s.to_sample()) .collect(); if frame_tx .unbounded_send(AudioFrame { - sample_rate, - num_channels, - samples_per_channel: sampled.len() as u32 / num_channels, + sample_rate: SAMPLE_RATE.get(), + num_channels: CHANNEL_COUNT.get() as u32, + samples_per_channel: sampled.len() as u32 / CHANNEL_COUNT.get() as u32, data: Cow::Owned(sampled), }) .is_err() diff --git a/crates/livekit_client/src/livekit_client/playback/source.rs b/crates/livekit_client/src/livekit_client/playback/source.rs index cde4b19fda2e053346ad535e7c75b2abda60431a..f605b3d517cd816491f0eceadce5ac778ef75d21 100644 --- a/crates/livekit_client/src/livekit_client/playback/source.rs +++ b/crates/livekit_client/src/livekit_client/playback/source.rs @@ -3,19 +3,17 @@ use std::num::NonZero; use futures::StreamExt; use libwebrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame}; use livekit::track::RemoteAudioTrack; -use rodio::{ - ChannelCount, SampleRate, Source, buffer::SamplesBuffer, conversions::SampleTypeConverter, -}; +use rodio::{Source, buffer::SamplesBuffer, conversions::SampleTypeConverter, nz}; -use audio::{CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE, SAMPLE_RATE}; +use audio::{CHANNEL_COUNT, SAMPLE_RATE}; fn frame_to_samplesbuffer(frame: AudioFrame) -> SamplesBuffer { let samples = frame.data.iter().copied(); let samples = SampleTypeConverter::<_, _>::new(samples); let samples: Vec = samples.collect(); SamplesBuffer::new( - NonZero::new(frame.num_channels as u16).expect("zero channels is nonsense"), - NonZero::new(frame.sample_rate).expect("samplerate zero is nonsense"), + nz!(2), // frame always has two channels + NonZero::new(frame.sample_rate).expect("audio frame sample rate is nonzero"), samples, ) } @@ -24,26 +22,14 @@ pub struct LiveKitStream { // shared_buffer: SharedBuffer, inner: rodio::queue::SourcesQueueOutput, _receiver_task: gpui::Task<()>, - channel_count: ChannelCount, - sample_rate: SampleRate, } impl LiveKitStream { - pub fn new( - executor: &gpui::BackgroundExecutor, - track: &RemoteAudioTrack, - legacy: bool, - ) -> Self { - let (channel_count, sample_rate) = if legacy { - (LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE) - } else { - (CHANNEL_COUNT, SAMPLE_RATE) - }; - + pub fn new(executor: &gpui::BackgroundExecutor, track: &RemoteAudioTrack) -> Self { let mut stream = NativeAudioStream::new( track.rtc_track(), - sample_rate.get() as i32, - channel_count.get().into(), + SAMPLE_RATE.get() as i32, + CHANNEL_COUNT.get().into(), ); let (queue_input, queue_output) = rodio::queue::queue(true); // spawn rtc stream @@ -59,8 +45,6 @@ impl LiveKitStream { LiveKitStream { _receiver_task: receiver_task, inner: queue_output, - sample_rate, - channel_count, } } } @@ -79,11 +63,17 @@ impl Source for LiveKitStream { } fn channels(&self) -> rodio::ChannelCount { - self.channel_count + // This must be hardcoded because the playback source assumes constant + // sample rate and channel count. The queue upon which this is build + // will however report different counts and rates. Even though we put in + // only items with our (constant) CHANNEL_COUNT & SAMPLE_RATE this will + // play silence on one channel and at 44100 which is not what our + // constants are. + CHANNEL_COUNT } fn sample_rate(&self) -> rodio::SampleRate { - self.sample_rate + SAMPLE_RATE // see comment on channels } fn total_duration(&self) -> Option { diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index b47755be58445e8ba335c6ea64416265d176fc17..43402cae0e6c723b4cc2e94f28c1ba7d0c61c928 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -291,43 +291,21 @@ pub enum TitleBarVisibility { #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct AudioSettingsContent { /// Opt into the new audio system. - /// - /// You need to rejoin a call for this setting to apply - #[serde(rename = "experimental.rodio_audio")] - pub rodio_audio: Option, // default is false - /// Requires 'rodio_audio: true' - /// - /// Automatically increase or decrease you microphone's volume. This affects how - /// loud you sound to others. - /// - /// Recommended: off (default) - /// Microphones are too quite in zed, until everyone is on experimental - /// audio and has auto speaker volume on this will make you very loud - /// compared to other speakers. - #[serde(rename = "experimental.auto_microphone_volume")] - pub auto_microphone_volume: Option, + #[serde(rename = "experimental.rodio_audio", default)] + pub rodio_audio: Option, /// Requires 'rodio_audio: true' /// - /// Automatically increate or decrease the volume of other call members. - /// This only affects how things sound for you. - #[serde(rename = "experimental.auto_speaker_volume")] - pub auto_speaker_volume: Option, + /// Use the new audio systems automatic gain control for your microphone. + /// This affects how loud you sound to others. + #[serde(rename = "experimental.control_input_volume", default)] + pub control_input_volume: Option, /// Requires 'rodio_audio: true' /// - /// Remove background noises. Works great for typing, cars, dogs, AC. Does - /// not work well on music. - #[serde(rename = "experimental.denoise")] - pub denoise: Option, - /// Requires 'rodio_audio: true' - /// - /// Use audio parameters compatible with the previous versions of - /// experimental audio and non-experimental audio. When this is false you - /// will sound strange to anyone not on the latest experimental audio. In - /// the future we will migrate by setting this to false - /// - /// You need to rejoin a call for this setting to apply - #[serde(rename = "experimental.legacy_audio_compatible")] - pub legacy_audio_compatible: Option, + /// Use the new audio systems automatic gain control on everyone in the + /// call. This makes call members who are too quite louder and those who are + /// too loud quieter. This only affects how things sound for you. + #[serde(rename = "experimental.control_output_volume", default)] + pub control_output_volume: Option, } /// Control what info is collected by Zed. diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index b50854abd55af883af1e97eac4afd51dbb31df3b..ec9629685d8366864b92a6160ece623450f72b0c 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -90,6 +90,7 @@ mime_guess = { version = "2" } miniz_oxide = { version = "0.8", features = ["simd"] } nom = { version = "7" } num-bigint = { version = "0.4" } +num-complex = { version = "0.4", features = ["bytemuck"] } num-integer = { version = "0.1", features = ["i128"] } num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] } num-rational = { version = "0.4", features = ["num-bigint-std"] } @@ -228,6 +229,7 @@ mime_guess = { version = "2" } miniz_oxide = { version = "0.8", features = ["simd"] } nom = { version = "7" } num-bigint = { version = "0.4" } +num-complex = { version = "0.4", features = ["bytemuck"] } num-integer = { version = "0.1", features = ["i128"] } num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] } num-rational = { version = "0.4", features = ["num-bigint-std"] } @@ -306,6 +308,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -335,6 +338,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -365,6 +369,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -394,6 +399,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1" livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } +num = { version = "0.4" } objc2 = { version = "0.6" } objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] } objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] } @@ -436,7 +442,6 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } -num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -478,7 +483,6 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } -num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -518,7 +522,6 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } -num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -560,7 +563,6 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } -num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -585,6 +587,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } +num = { version = "0.4" } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "fs", "net"] } @@ -610,6 +613,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } +num = { version = "0.4" } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } @@ -651,7 +655,6 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } -num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } @@ -693,7 +696,6 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] } num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] } -num-complex = { version = "0.4", features = ["bytemuck"] } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } From 94fcbb400b875aaaccab5e730c3a7c72dd660ef5 Mon Sep 17 00:00:00 2001 From: Dimas Ari <_@inchidi.dev> Date: Fri, 19 Sep 2025 21:36:36 +0700 Subject: [PATCH 146/721] docs: Update invalid property in a configuration example (#38466) Just install Zed for the first time and got a warning from the first config example i copied from docs. Great design btw, immediately able to see that this is a well thought out app. seems like i'll stick with zed and make it my new dev 'sanctuary'. Release Notes: - N/A --- docs/src/visual-customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index 55f2dfe9b4d40d46a640520a99952964712c640e..6798542d14e448feec86cdd8cb8e6a8f61a4cc78 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -227,7 +227,7 @@ TBD: Centered layout related settings "git": { "inline_blame": { "enabled": true, // Show/hide inline blame - "delay": 0, // Show after delay (ms) + "delay_ms": 0, // Show after delay (ms) "min_column": 0, // Minimum column to inline display blame "padding": 7, // Padding between code and inline blame (em) "show_commit_summary": false // Show/hide commit summary From b6944d0bae3c53bff64ce31d2609135eb0807eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=80=83=E7=94=9F=E8=88=B1?= Date: Fri, 19 Sep 2025 22:43:25 +0800 Subject: [PATCH 147/721] docs: Fix duplicate postgresql package and punctuation error (#38478) Found duplicate `postgresql` package in installation command. Uncertain whether it should be `postgresql-contrib` or `postgresql-client`, but neither appears necessary. Release Notes: - N/A --- docs/src/development/local-collaboration.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/development/local-collaboration.md b/docs/src/development/local-collaboration.md index eb7f3dfc43dc29ee3d25de3dbc373f5f925ba2af..87363a4269ac32ac39598efef640b80384d1f44a 100644 --- a/docs/src/development/local-collaboration.md +++ b/docs/src/development/local-collaboration.md @@ -48,17 +48,17 @@ You can install these dependencies natively or run them under Docker. - Follow the steps in the [collab README](https://github.com/zed-industries/zed/blob/main/crates/collab/README.md) to configure the Postgres database for integration tests -Alternatively, if you have [Docker](https://www.docker.com/) installed you can bring up all the `collab` dependencies using Docker Compose: +Alternatively, if you have [Docker](https://www.docker.com/) installed you can bring up all the `collab` dependencies using Docker Compose. ### Linux 1. Install [Postgres](https://www.postgresql.org/download/linux/) ```sh - sudo apt-get install postgresql postgresql # Ubuntu/Debian - sudo pacman -S postgresql # Arch Linux - sudo dnf install postgresql postgresql-server # RHEL/Fedora - sudo zypper install postgresql postgresql-server # OpenSUSE + sudo apt-get install postgresql # Ubuntu/Debian + sudo pacman -S postgresql # Arch Linux + sudo dnf install postgresql postgresql-server # RHEL/Fedora + sudo zypper install postgresql postgresql-server # OpenSUSE ``` 2. Install [Livekit](https://github.com/livekit/livekit-cli) From 154b01c5fe55192d5f319c1858556f3246f49952 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Fri, 19 Sep 2025 17:05:39 +0200 Subject: [PATCH 148/721] Dismiss agent panel when `disable_ai` is toggled to `true` (#38461) Closes https://github.com/zed-industries/zed/issues/38331 This fixes an issue where we would not dismiss the panel once the user toggled the setting, leaving them in an awkward state where closing the panel would become hard. Also takes care of one more check for the `Fix with assistant` action and consolidates some of the `AgentSettings` and `DisableAiSetting` checks into one method to make the code more readable. Release Notes: - N/A --- Cargo.lock | 1 + crates/agent_settings/Cargo.toml | 1 + crates/agent_settings/src/agent_settings.rs | 5 +++ crates/agent_ui/src/agent_panel.rs | 44 ++++++++++++++++++--- crates/agent_ui/src/inline_assistant.rs | 8 ++-- crates/git_ui/src/git_panel.rs | 22 ++++------- crates/zed/src/zed/quick_action_bar.rs | 18 +++------ 7 files changed, 61 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3acfed9bd7cfa8bc2742bb4f006c38a4f65a1f0e..ed1e9bef3f1797201dd791a7b4616509bbbc5036 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,7 @@ dependencies = [ "gpui", "language_model", "paths", + "project", "schemars 1.0.1", "serde", "serde_json", diff --git a/crates/agent_settings/Cargo.toml b/crates/agent_settings/Cargo.toml index 8af76053c2aabead30413c98e482ed97dbdbc361..a8b457a9dddb1f8932d015f895e6d2064944bfe9 100644 --- a/crates/agent_settings/Cargo.toml +++ b/crates/agent_settings/Cargo.toml @@ -19,6 +19,7 @@ convert_case.workspace = true fs.workspace = true gpui.workspace = true language_model.workspace = true +project.workspace = true schemars.workspace = true serde.workspace = true settings.workspace = true diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index e416ce73e5451e840af8c36e8ffee301bacc79b3..d862cacee18ea53f81cdc91981b22f5531f2d75e 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use collections::IndexMap; use gpui::{App, Pixels, px}; use language_model::LanguageModel; +use project::DisableAiSettings; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{ @@ -53,6 +54,10 @@ pub struct AgentSettings { } impl AgentSettings { + pub fn enabled(&self, cx: &App) -> bool { + self.enabled && !DisableAiSettings::get_global(cx).disable_ai + } + pub fn temperature_for_model(model: &Arc, cx: &App) -> Option { let settings = Self::get_global(cx); for setting in settings.model_parameters.iter().rev() { diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index ee9ac73b2ee23a8b4326ddbc4e60c345ef4a3526..ba71fd84ab5b9d666256afeb0a2c5677aac9adb1 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -1,4 +1,4 @@ -use std::ops::{Not, Range}; +use std::ops::Range; use std::path::Path; use std::rc::Rc; use std::sync::Arc; @@ -662,6 +662,43 @@ impl AgentPanel { ) }); + let mut old_disable_ai = false; + cx.observe_global_in::(window, move |panel, window, cx| { + let disable_ai = DisableAiSettings::get_global(cx).disable_ai; + if old_disable_ai != disable_ai { + let agent_panel_id = cx.entity_id(); + let agent_panel_visible = panel + .workspace + .update(cx, |workspace, cx| { + let agent_dock_position = panel.position(window, cx); + let agent_dock = workspace.dock_at_position(agent_dock_position); + let agent_panel_focused = agent_dock + .read(cx) + .active_panel() + .is_some_and(|panel| panel.panel_id() == agent_panel_id); + + let active_panel_visible = agent_dock + .read(cx) + .visible_panel() + .is_some_and(|panel| panel.panel_id() == agent_panel_id); + + if agent_panel_focused { + cx.dispatch_action(&ToggleFocus); + } + + active_panel_visible + }) + .unwrap_or_default(); + + if agent_panel_visible { + cx.emit(PanelEvent::Close); + } + + old_disable_ai = disable_ai; + } + }) + .detach(); + Self { active_view, workspace, @@ -674,11 +711,9 @@ impl AgentPanel { prompt_store, configuration: None, configuration_subscription: None, - inline_assist_context_store, previous_view: None, history_store: history_store.clone(), - new_thread_menu_handle: PopoverMenuHandle::default(), agent_panel_menu_handle: PopoverMenuHandle::default(), assistant_navigation_menu_handle: PopoverMenuHandle::default(), @@ -703,7 +738,6 @@ impl AgentPanel { if workspace .panel::(cx) .is_some_and(|panel| panel.read(cx).enabled(cx)) - && !DisableAiSettings::get_global(cx).disable_ai { workspace.toggle_panel_focus::(window, cx); } @@ -1499,7 +1533,7 @@ impl Panel for AgentPanel { } fn enabled(&self, cx: &App) -> bool { - DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled + AgentSettings::get_global(cx).enabled(cx) } fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool { diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 79e092b709dd2778c89a79e1d6ce36802c853eb6..98e7276dc4fd3f94b01df82219f116a07cafa304 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -144,8 +144,7 @@ impl InlineAssistant { let Some(terminal_panel) = workspace.read(cx).panel::(cx) else { return; }; - let enabled = !DisableAiSettings::get_global(cx).disable_ai - && AgentSettings::get_global(cx).enabled; + let enabled = AgentSettings::get_global(cx).enabled(cx); terminal_panel.update(cx, |terminal_panel, cx| { terminal_panel.set_assistant_enabled(enabled, cx) }); @@ -257,8 +256,7 @@ impl InlineAssistant { window: &mut Window, cx: &mut Context, ) { - let settings = AgentSettings::get_global(cx); - if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai { + if !AgentSettings::get_global(cx).enabled(cx) { return; } @@ -1788,7 +1786,7 @@ impl CodeActionProvider for AssistantCodeActionProvider { _: &mut Window, cx: &mut App, ) -> Task>> { - if !AgentSettings::get_global(cx).enabled { + if !AgentSettings::get_global(cx).enabled(cx) { return Task::ready(Ok(Vec::new())); } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 76671eba7b577e86d5049add743e965d11acd6c4..47dcc68d2137f75666ae04d2b8ffe4e87cb478f8 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -46,7 +46,7 @@ use panel::{ panel_icon_button, }; use project::{ - DisableAiSettings, Fs, Project, ProjectPath, + Fs, Project, ProjectPath, git_store::{GitStoreEvent, Repository, RepositoryEvent, RepositoryId}, }; use serde::{Deserialize, Serialize}; @@ -405,15 +405,11 @@ impl GitPanel { let scroll_handle = UniformListScrollHandle::new(); - let mut assistant_enabled = AgentSettings::get_global(cx).enabled; - let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai; + let mut was_ai_enabled = AgentSettings::get_global(cx).enabled(cx); let _settings_subscription = cx.observe_global::(move |_, cx| { - let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai; - if assistant_enabled != AgentSettings::get_global(cx).enabled - || was_ai_disabled != is_ai_disabled - { - assistant_enabled = AgentSettings::get_global(cx).enabled; - was_ai_disabled = is_ai_disabled; + let is_ai_enabled = AgentSettings::get_global(cx).enabled(cx); + if was_ai_enabled != is_ai_enabled { + was_ai_enabled = is_ai_enabled; cx.notify(); } }); @@ -1739,10 +1735,7 @@ impl GitPanel { /// Generates a commit message using an LLM. pub fn generate_commit_message(&mut self, cx: &mut Context) { - if !self.can_commit() - || DisableAiSettings::get_global(cx).disable_ai - || !agent_settings::AgentSettings::get_global(cx).enabled - { + if !self.can_commit() || !AgentSettings::get_global(cx).enabled(cx) { return; } @@ -2996,8 +2989,7 @@ impl GitPanel { &self, cx: &Context, ) -> Option { - if !agent_settings::AgentSettings::get_global(cx).enabled - || DisableAiSettings::get_global(cx).disable_ai + if !agent_settings::AgentSettings::get_global(cx).enabled(cx) || LanguageModelRegistry::read_global(cx) .commit_message_model() .is_none() diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index a6f85000e9b2fd2b853880a9045984938b6a7445..df1a417f5815753698a18b077d69c81c5b7ba3ed 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -15,7 +15,6 @@ use gpui::{ FocusHandle, Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, anchored, deferred, point, }; -use project::DisableAiSettings; use project::project_settings::DiagnosticSeverity; use search::{BufferSearchBar, buffer_search}; use settings::{Settings, SettingsStore}; @@ -48,20 +47,15 @@ impl QuickActionBar { workspace: &Workspace, cx: &mut Context, ) -> Self { - let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai; - let mut was_agent_enabled = AgentSettings::get_global(cx).enabled; + let mut was_agent_enabled = AgentSettings::get_global(cx).enabled(cx); let mut was_agent_button = AgentSettings::get_global(cx).button; let ai_settings_subscription = cx.observe_global::(move |_, cx| { - let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai; let agent_settings = AgentSettings::get_global(cx); + let is_agent_enabled = agent_settings.enabled(cx); - if was_ai_disabled != is_ai_disabled - || was_agent_enabled != agent_settings.enabled - || was_agent_button != agent_settings.button - { - was_ai_disabled = is_ai_disabled; - was_agent_enabled = agent_settings.enabled; + if was_agent_enabled != is_agent_enabled || was_agent_button != agent_settings.button { + was_agent_enabled = is_agent_enabled; was_agent_button = agent_settings.button; cx.notify(); } @@ -597,9 +591,7 @@ impl Render for QuickActionBar { .children(self.render_preview_button(self.workspace.clone(), cx)) .children(search_button) .when( - AgentSettings::get_global(cx).enabled - && AgentSettings::get_global(cx).button - && !DisableAiSettings::get_global(cx).disable_ai, + AgentSettings::get_global(cx).enabled(cx) && AgentSettings::get_global(cx).button, |bar| bar.child(assistant_button), ) .children(code_actions_dropdown) From 0f4bdca9e9742bc2acea8b72497e55f3deb4805a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Ce=C5=A1pivo?= Date: Fri, 19 Sep 2025 17:17:35 +0200 Subject: [PATCH 149/721] Update icon theme fallback to use default theme (#38485) https://github.com/zed-industries/zed/pull/38367 introduced panic: ``` thread 'main' panicked at crates/theme/src/settings.rs:812:18: called `Option::unwrap()` on a `None` value ``` In this PR I restored fallback logic from the original code - before settings refactor. Release Notes: - N/A --- crates/theme/src/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index e8ae1eed3cd7ca49ec946645160b98732be83884..b2c19ae3ed0d6e3dbf622c9e125bad7eeccf0a6e 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -808,7 +808,7 @@ impl settings::Settings for ThemeSettings { theme_overrides: HashMap::default(), active_icon_theme: themes .get_icon_theme(icon_theme_selection.icon_theme(*system_appearance)) - .ok() + .or_else(|_| themes.default_icon_theme()) .unwrap(), icon_theme_selection: Some(icon_theme_selection), ui_density: content.ui_density.unwrap_or_default().into(), From 4743fe84151f8a014591f65a60da06529245abbc Mon Sep 17 00:00:00 2001 From: Dino Date: Fri, 19 Sep 2025 16:50:33 +0100 Subject: [PATCH 150/721] vim: Fix regression in surround behavior (#38344) Fix an issue introduced in https://github.com/zed-industries/zed/pull/37321 where vim's surround wouldn't work as expected when replacing quotes with non-quotes, with whitespace always being added, regardless of whether the opening or closing bracket was used. This is not the intended, or previous, behavior, where only the opening bracket would trigger whitespace to be added. Closes #38169 Release Notes: - Fixed regression in vim's surround plugin that ignored whether the opening or closing bracket was being used when replacing quotes, so space would always be added --- crates/vim/src/surrounds.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 8b3359c8f08046cf995db077a9a5ff0d36a97b95..5e25b08dd8656887b2013df52a5e7d62fce5dbe0 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -241,21 +241,15 @@ impl Vim { }, }; - // Determines whether space should be added/removed after + // Determines whether space should be added after // and before the surround pairs. - // For example, using `cs{[` will add a space before and - // after the pair, while using `cs{]` will not, notice the - // use of the closing bracket instead of the opening bracket - // on the target object. - // In the case of quotes, the opening and closing is the - // same, so no space will ever be added or removed. - let surround = match target { - Object::Quotes - | Object::BackQuotes - | Object::AnyQuotes - | Object::MiniQuotes - | Object::DoubleQuotes => true, - _ => pair.end != surround_alias((*text).as_ref()), + // Space is only added in the following cases: + // - new surround is not quote and is opening bracket (({[<) + // - new surround is quote and original was also quote + let surround = if pair.start != pair.end { + pair.end != surround_alias((*text).as_ref()) + } else { + will_replace_pair.start == will_replace_pair.end }; let (display_map, selections) = editor.selections.all_adjusted_display(cx); @@ -1241,6 +1235,15 @@ mod test { "}, Mode::Normal, ); + + // test quote to bracket spacing. + cx.set_state(indoc! {"'ˇfoobar'"}, Mode::Normal); + cx.simulate_keystrokes("c s ' {"); + cx.assert_state(indoc! {"ˇ{ foobar }"}, Mode::Normal); + + cx.set_state(indoc! {"'ˇfoobar'"}, Mode::Normal); + cx.simulate_keystrokes("c s ' }"); + cx.assert_state(indoc! {"ˇ{foobar}"}, Mode::Normal); } #[gpui::test] From df6f0bc2a7b768b16c7c02c5e6effeed4145ac44 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Fri, 19 Sep 2025 18:11:19 +0200 Subject: [PATCH 151/721] Fix markdown list in `bump-zed-minor-versions` (#38515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a small markdown issue in the `bump-zed-minor-versions` script that bugged me for too long 😅 Release Notes: - N/A --- script/bump-zed-minor-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/bump-zed-minor-versions b/script/bump-zed-minor-versions index 10535ce79b12f1820986fcbaa4062def0c9ec856..536dbb6244c7e9f99c4085ca95f667e43dd67ac3 100755 --- a/script/bump-zed-minor-versions +++ b/script/bump-zed-minor-versions @@ -104,7 +104,7 @@ Prepared new Zed versions locally. You will need to push the branches and open a ${prev_minor_branch_name} \\ ${bump_main_branch_name} - echo -e "Release Notes:\n\n-N/A" | gh pr create \\ + echo -e "Release Notes:\n\n- N/A" | gh pr create \\ --title "Bump Zed to v${major}.${next_minor}" \\ --body-file "-" \\ --base main \\ From b9188e0fd38a9738b4891b4e414cd41c77d6fa35 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 19 Sep 2025 18:38:22 +0200 Subject: [PATCH 152/721] collab: Fix screen share aspect ratio on non-Mac platforms (#38517) It was just a bunch of finnickery around UI layout. It affected Linux too. Release Notes: * Fixed aspect ratio of peer screen share when using Linux/Windows builds. --- Cargo.lock | 1 + crates/livekit_client/Cargo.toml | 1 + crates/livekit_client/src/remote_video_track_view.rs | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ed1e9bef3f1797201dd791a7b4616509bbbc5036..8fd76300f7507a284375e12e1275724972bebe7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10163,6 +10163,7 @@ dependencies = [ "simplelog", "smallvec", "tokio-tungstenite 0.26.2", + "ui", "util", "workspace-hack", ] diff --git a/crates/livekit_client/Cargo.toml b/crates/livekit_client/Cargo.toml index 80e4960c0df31f6a3d8115bd4bd66c0de09b76f0..2400092c1c154b8d6a4ee24f43c0556a26dc532e 100644 --- a/crates/livekit_client/Cargo.toml +++ b/crates/livekit_client/Cargo.toml @@ -41,6 +41,7 @@ serde_urlencoded.workspace = true settings.workspace = true smallvec.workspace = true tokio-tungstenite.workspace = true +ui.workspace = true util.workspace = true workspace-hack.workspace = true diff --git a/crates/livekit_client/src/remote_video_track_view.rs b/crates/livekit_client/src/remote_video_track_view.rs index 9073b8729a1d72ef59fe6ed77fd727cdf6acae00..189806f2138e401e62ad46336e95d8468e3b3732 100644 --- a/crates/livekit_client/src/remote_video_track_view.rs +++ b/crates/livekit_client/src/remote_video_track_view.rs @@ -97,8 +97,10 @@ impl Render for RemoteVideoTrackView { self.previous_rendered_frame = Some(current_rendered_frame) } self.current_rendered_frame = Some(latest_frame.clone()); - return gpui::img(latest_frame.clone()) + use gpui::ParentElement; + return ui::h_flex() .size_full() + .child(gpui::img(latest_frame.clone()).size_full()) .into_any_element(); } From 30a29ab34ea7ca2c03f2d6e4f35596d01c7cf3e7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 19 Sep 2025 10:38:39 -0600 Subject: [PATCH 153/721] Fix server settings (#38477) In the settings refactor I'd assumed server settings were like project settings. This is not the case, they are in fact the normal user settings; but just read from the server. Release Notes: - N/A --- Cargo.lock | 1 + crates/editor/Cargo.toml | 1 + crates/editor/src/editor.rs | 8 ++-- crates/project/src/project_settings.rs | 5 +-- .../remote_server/src/remote_editing_tests.rs | 7 +-- crates/settings/src/settings_content.rs | 7 --- crates/settings/src/settings_store.rs | 44 ++----------------- 7 files changed, 13 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fd76300f7507a284375e12e1275724972bebe7f..f38ea8f740e87643b063ecc358899e6ba0b0fd10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5295,6 +5295,7 @@ dependencies = [ "url", "util", "uuid", + "vim_mode_setting", "workspace", "workspace-hack", "zed_actions", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index be06cc04dfc7ee3f080e8d995783abb819e95842..52b3fa2affeca1ceb87485fb1242fe40b34f8f57 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -89,6 +89,7 @@ ui.workspace = true url.workspace = true util.workspace = true uuid.workspace = true +vim_mode_setting.workspace = true workspace.workspace = true zed_actions.workspace = true workspace-hack.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d8501f2104a00183be68dc461f9128a600227aa6..4084f61bb4a44d591aa544a622fa8888f56a5c57 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -21678,12 +21678,10 @@ impl Editor { } } -// todo(settings_refactor) this should not be! fn vim_enabled(cx: &App) -> bool { - cx.global::() - .raw_user_settings() - .and_then(|settings| settings.content.vim_mode) - == Some(true) + vim_mode_setting::VimModeSetting::try_get(cx) + .map(|vim_mode| vim_mode.0) + .unwrap_or(false) } fn process_completion_for_edit( diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index c95c20a3352bb067e492874f6f650d38f04671b2..369d445b2051f463e862483f4afd1b8c444bb9ea 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -770,12 +770,9 @@ impl SettingsObserver { envelope: TypedEnvelope, cx: AsyncApp, ) -> anyhow::Result<()> { - let new_settings = serde_json::from_str(&envelope.payload.contents).with_context(|| { - format!("deserializing {} user settings", envelope.payload.contents) - })?; cx.update_global(|settings_store: &mut SettingsStore, cx| { settings_store - .set_raw_user_settings(new_settings, cx) + .set_user_settings(&envelope.payload.contents, cx) .context("setting new user settings")?; anyhow::Ok(()) })??; diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index cb486732c0a0a63e7f6d5d5aed7fe0499ef98b80..16b6e49063ad091967d88943024167bb246f8e2c 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -1797,8 +1797,8 @@ async fn test_remote_external_agent_server( pretty_assertions::assert_eq!(names, ["gemini", "claude"]); server_cx.update_global::(|settings_store, cx| { settings_store - .set_raw_server_settings( - Some(json!({ + .set_server_settings( + &json!({ "agent_servers": { "foo": { "command": "foo-cli", @@ -1808,7 +1808,8 @@ async fn test_remote_external_agent_server( } } } - })), + }) + .to_string(), cx, ) .unwrap(); diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 43402cae0e6c723b4cc2e94f28c1ba7d0c61c928..38bff4d6a1428f017bcd65be3d27e945aebccabd 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -166,13 +166,6 @@ impl SettingsContent { } } -#[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] -pub struct ServerSettingsContent { - #[serde(flatten)] - pub project: ProjectSettingsContent, -} - #[skip_serializing_none] #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct UserSettingsContent { diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index dc703e50f1de43aee8059e144dc4cb0815b3472d..a575182a4144d99bf3c3c7f29f649735ea8b8891 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -36,8 +36,7 @@ use crate::{ merge_from::MergeFrom, parse_json_with_comments, replace_value_in_json_text, settings_content::{ - ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent, - UserSettingsContent, + ExtensionsSettingsContent, ProjectSettingsContent, SettingsContent, UserSettingsContent, }, update_value_in_json_text, }; @@ -327,33 +326,6 @@ impl SettingsStore { self.user_settings.as_ref() } - /// Replaces current settings with the values from the given JSON. - pub fn set_raw_user_settings( - &mut self, - new_settings: UserSettingsContent, - cx: &mut App, - ) -> Result<()> { - self.user_settings = Some(new_settings); - self.recompute_values(None, cx)?; - Ok(()) - } - - /// Replaces current settings with the values from the given JSON. - pub fn set_raw_server_settings( - &mut self, - new_settings: Option, - cx: &mut App, - ) -> Result<()> { - // Rewrite the server settings into a content type - self.server_settings = new_settings - .map(|settings| settings.to_string()) - .and_then(|str| parse_json_with_comments::(&str).ok()) - .map(Box::new); - - self.recompute_values(None, cx)?; - Ok(()) - } - /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator { self.user_settings @@ -361,11 +333,6 @@ impl SettingsStore { .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str())) } - /// Access the raw JSON value of the default settings. - pub fn raw_default_settings(&self) -> &SettingsContent { - &self.default_settings - } - #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut App) -> Self { Self::new(cx, &crate::test_settings()) @@ -621,19 +588,14 @@ impl SettingsStore { server_settings_content: &str, cx: &mut App, ) -> Result<()> { - let settings: Option = if server_settings_content.is_empty() { + let settings: Option = if server_settings_content.is_empty() { None } else { parse_json_with_comments(server_settings_content)? }; // Rewrite the server settings into a content type - self.server_settings = settings.map(|settings| { - Box::new(SettingsContent { - project: settings.project, - ..Default::default() - }) - }); + self.server_settings = settings.map(|settings| Box::new(settings)); self.recompute_values(None, cx)?; Ok(()) From be7575536ea72342ce4792e62c32c81e62377df4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 19 Sep 2025 10:51:21 -0600 Subject: [PATCH 154/721] Fix theme overrides (#38512) Release Notes: - N/A --- crates/theme/src/settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index b2c19ae3ed0d6e3dbf622c9e125bad7eeccf0a6e..04b8bd3dd7c597b8730c03f1ed8ac9fdb83929d6 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -804,8 +804,8 @@ impl settings::Settings for ThemeSettings { .or(themes.get(&zed_default_dark().name)) .unwrap(), theme_selection: Some(theme_selection), - experimental_theme_overrides: None, - theme_overrides: HashMap::default(), + experimental_theme_overrides: content.experimental_theme_overrides.clone(), + theme_overrides: content.theme_overrides.clone(), active_icon_theme: themes .get_icon_theme(icon_theme_selection.icon_theme(*system_appearance)) .or_else(|_| themes.default_icon_theme()) From 1afbfcb832496061d77c50280953e669dbdbcfc0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 19 Sep 2025 11:14:31 -0600 Subject: [PATCH 155/721] git: Docs-based workaround for GitHub/git auth confusion (#38479) Closes #ISSUE Release Notes: - git: Added a link to Github's authentication help if you end up in Zed trying to type a password in for https auth --- crates/git_ui/src/askpass_modal.rs | 43 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/crates/git_ui/src/askpass_modal.rs b/crates/git_ui/src/askpass_modal.rs index 149833ad3535bb69ba35e199ece5166e194745a9..1705ad6732ef57095a7e550a6c27978596a6b11e 100644 --- a/crates/git_ui/src/askpass_modal.rs +++ b/crates/git_ui/src/askpass_modal.rs @@ -2,9 +2,10 @@ use editor::Editor; use futures::channel::oneshot; use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Styled}; use ui::{ - ActiveTheme, App, Context, DynamicSpacing, Headline, HeadlineSize, Icon, IconName, IconSize, - InteractiveElement, IntoElement, ParentElement, Render, SharedString, StyledExt, - StyledTypography, Window, div, h_flex, v_flex, + ActiveTheme, AnyElement, App, Button, Clickable, Color, Context, DynamicSpacing, Headline, + HeadlineSize, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, + LabelSize, ParentElement, Render, SharedString, StyledExt, StyledTypography, Window, div, + h_flex, v_flex, }; use workspace::ModalView; @@ -33,7 +34,7 @@ impl AskPassModal { ) -> Self { let editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); - if prompt.contains("yes/no") { + if prompt.contains("yes/no") || prompt.contains("Username") { editor.set_masked(false, cx); } else { editor.set_masked(true, cx); @@ -58,6 +59,36 @@ impl AskPassModal { } cx.emit(DismissEvent); } + + fn render_hint(&mut self, cx: &mut Context) -> Option { + let color = cx.theme().status().info_background; + if (self.prompt.contains("Password") || self.prompt.contains("Username")) + && self.prompt.contains("github.com") + { + return Some( + div() + .p_2() + .bg(color) + .border_t_1() + .border_color(cx.theme().status().info_border) + .child( + h_flex().gap_2() + .child( + Icon::new(IconName::Github).size(IconSize::Small) + ) + .child( + Label::new("You may need to configure git for Github.") + .size(LabelSize::Small), + ) + .child(Button::new("learn-more", "Learn more").color(Color::Accent).label_size(LabelSize::Small).on_click(|_, _, cx| { + cx.open_url("https://docs.github.com/en/get-started/git-basics/set-up-git#authenticating-with-github-from-git") + })), + ) + .into_any_element(), + ); + } + None + } } impl Render for AskPassModal { @@ -68,9 +99,9 @@ impl Render for AskPassModal { .on_action(cx.listener(Self::confirm)) .elevation_2(cx) .size_full() - .font_buffer(cx) .child( h_flex() + .font_buffer(cx) .px(DynamicSpacing::Base12.rems(cx)) .pt(DynamicSpacing::Base08.rems(cx)) .pb(DynamicSpacing::Base04.rems(cx)) @@ -86,6 +117,7 @@ impl Render for AskPassModal { ) .child( div() + .font_buffer(cx) .text_buffer(cx) .py_2() .px_3() @@ -97,5 +129,6 @@ impl Render for AskPassModal { .child(self.prompt.clone()) .child(self.editor.clone()), ) + .children(self.render_hint(cx)) } } From 4e316c683bc31794833ce64a706b18319371f72f Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Fri, 19 Sep 2025 14:07:02 -0500 Subject: [PATCH 156/721] macos: Fix panic when `NSWindow::screen` returns `nil` (#38524) Closes #ISSUE Release Notes: - mac: Fixed an issue where Zed would panic if the workspace window was previously off screen --- crates/gpui/src/platform/mac/window.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 1230a704062ba835bceb5db5d2ecf05b688e34df..e8b42c57b8239f53118487f51bd194178c3c21c0 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -513,10 +513,11 @@ impl MacWindowState { fn bounds(&self) -> Bounds { let mut window_frame = unsafe { NSWindow::frame(self.native_window) }; - let screen_frame = unsafe { - let screen = NSWindow::screen(self.native_window); - NSScreen::frame(screen) - }; + let screen = unsafe { NSWindow::screen(self.native_window) }; + if screen == nil { + return Bounds::new(point(px(0.), px(0.)), crate::DEFAULT_WINDOW_SIZE); + } + let screen_frame = unsafe { NSScreen::frame(screen) }; // Flip the y coordinate to be top-left origin window_frame.origin.y = From de75e2d9f6142b6e667dff357de0444ff56e326b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 19 Sep 2025 16:48:52 -0400 Subject: [PATCH 157/721] extension_host: Expand supported extension API range to include v0.7.0 (#38529) This PR updates the version range for v0.6.0 of the extension API to include v0.7.0. Since we bumped the `zed_extension_api` crate's version to v0.7.0, we need to expand this range in order for Zed clients to be able to install extensions built against v0.7.0 of `zed_extension_api`. Currently no extensions that target `zed_extension_api@0.7.0` can be installed. Release Notes: - N/A --- crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index e879ed0cb01f70f24a9b2b52438e1ff7d405f2d6..be4f3ca71a3f392965488bd2d30eab556d8fb300 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -35,7 +35,7 @@ use util::{archive::extract_zip, fs::make_file_executable, maybe}; use wasmtime::component::{Linker, Resource}; pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0); -pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0); +pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 7, 0); wasmtime::component::bindgen!({ async: true, From 89520ea2216e7a148e1d6c280a0a0cd8daf52648 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 20 Sep 2025 00:15:01 +0200 Subject: [PATCH 158/721] chore: Bump alacritty_terminal to 0.25.1-rc1 (#38505) Release Notes: - N/A --------- Co-authored-by: Dave Waggoner --- Cargo.lock | 41 +-- Cargo.toml | 2 +- crates/terminal/src/terminal.rs | 2 + crates/terminal/src/terminal_hyperlinks.rs | 294 ++++++--------------- tooling/workspace-hack/Cargo.toml | 40 +-- 5 files changed, 123 insertions(+), 256 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f38ea8f740e87643b063ecc358899e6ba0b0fd10..548ff152066745344b65c75b0be80db71c6f7f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,8 +498,9 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.25.1-dev" -source = "git+https://github.com/zed-industries/alacritty.git?branch=add-hush-login-flag#828457c9ff1f7ea0a0469337cc8a37ee3a1b0590" +version = "0.25.1-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb5f4f1ef69bdb8b2095ddd14b09dd74ee0303aae8bd5372667a54cff689a1b" dependencies = [ "base64 0.22.1", "bitflags 2.9.0", @@ -511,10 +512,11 @@ dependencies = [ "piper", "polling", "regex-automata", + "rustix 1.0.7", "rustix-openpty", "serde", "signal-hook", - "unicode-width 0.1.14", + "unicode-width 0.2.0", "vte", "windows-sys 0.59.0", ] @@ -8221,12 +8223,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "hermit-abi" version = "0.5.0" @@ -12822,17 +12818,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi 0.5.0", "pin-project-lite", - "rustix 0.38.44", - "tracing", - "windows-sys 0.59.0", + "rustix 1.0.7", + "windows-sys 0.61.0", ] [[package]] @@ -14679,7 +14674,6 @@ checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.0", "errno 0.3.11", - "itoa", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", @@ -14710,13 +14704,13 @@ dependencies = [ [[package]] name = "rustix-openpty" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" +checksum = "1de16c7c59892b870a6336f185dc10943517f1327447096bbb7bb32cd85e2393" dependencies = [ "errno 0.3.11", "libc", - "rustix 0.38.44", + "rustix 1.0.7", ] [[package]] @@ -20039,6 +20033,15 @@ dependencies = [ "windows-targets 0.53.2", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index 6939fb4dd60cd443e07d16988f187d7074535de7..aa95b1f4a78fe2599bcccd3036c2ebb65761ada3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -439,7 +439,7 @@ zlog_settings = { path = "crates/zlog_settings" } agent-client-protocol = { version = "0.4.0", features = ["unstable"] } aho-corasick = "1.1" -alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" } +alacritty_terminal = "0.25.1-rc1" any_vec = "0.14" anyhow = "1.0.86" arrayvec = { version = "0.7.4", features = ["serde"] } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 6bdeb9638a329c2384e538e27e13c21f02df7284..d1a4c8af9687c87a8c63b598262d0bdf797fada4 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -427,6 +427,8 @@ impl TerminalBuilder { working_directory: working_directory.clone(), drain_on_exit: true, env: env.clone().into_iter().collect(), + #[cfg(windows)] + escape_args: false, } }; diff --git a/crates/terminal/src/terminal_hyperlinks.rs b/crates/terminal/src/terminal_hyperlinks.rs index 2d3d356b4663a8aa271dda8d36d5fab720228527..25db02c5e84f692622a1c97ed891c886b02b26a9 100644 --- a/crates/terminal/src/terminal_hyperlinks.rs +++ b/crates/terminal/src/terminal_hyperlinks.rs @@ -79,8 +79,7 @@ pub(super) fn find_from_grid_point( Some((url, true, url_match)) } else if let Some(url_match) = regex_match_at(term, point, &mut regex_searches.url_regex) { let url = term.bounds_to_string(*url_match.start(), *url_match.end()); - let (sanitized_url, sanitized_match) = sanitize_url_punctuation(url, url_match, term); - Some((sanitized_url, true, sanitized_match)) + Some((url, true, url_match)) } else if let Some(python_match) = regex_match_at(term, point, &mut regex_searches.python_file_line_regex) { @@ -165,63 +164,6 @@ pub(super) fn find_from_grid_point( }) } -fn sanitize_url_punctuation( - url: String, - url_match: Match, - term: &Term, -) -> (String, Match) { - let mut sanitized_url = url; - let mut chars_trimmed = 0; - - // First, handle parentheses balancing using single traversal - let (open_parens, close_parens) = - sanitized_url - .chars() - .fold((0, 0), |(opens, closes), c| match c { - '(' => (opens + 1, closes), - ')' => (opens, closes + 1), - _ => (opens, closes), - }); - - // Trim unbalanced closing parentheses - if close_parens > open_parens { - let mut remaining_close = close_parens; - while sanitized_url.ends_with(')') && remaining_close > open_parens { - sanitized_url.pop(); - chars_trimmed += 1; - remaining_close -= 1; - } - } - - // Handle trailing periods - if sanitized_url.ends_with('.') { - let trailing_periods = sanitized_url - .chars() - .rev() - .take_while(|&c| c == '.') - .count(); - - if trailing_periods > 1 { - sanitized_url.truncate(sanitized_url.len() - trailing_periods); - chars_trimmed += trailing_periods; - } else if trailing_periods == 1 - && let Some(second_last_char) = sanitized_url.chars().rev().nth(1) - && (second_last_char.is_alphanumeric() || second_last_char == '/') - { - sanitized_url.pop(); - chars_trimmed += 1; - } - } - - if chars_trimmed > 0 { - let new_end = url_match.end().sub(term, Boundary::Grid, chars_trimmed); - let sanitized_match = Match::new(*url_match.start(), new_end); - (sanitized_url, sanitized_match) - } else { - (sanitized_url, url_match) - } -} - fn is_path_surrounded_by_common_symbols(path: &str) -> bool { // Avoid detecting `[]` or `()` strings as paths, surrounded by common symbols path.len() > 2 @@ -261,8 +203,8 @@ mod tests { use super::*; use alacritty_terminal::{ event::VoidListener, - index::{Boundary, Column, Line, Point as AlacPoint}, - term::{Config, cell::Flags, search::Match, test::TermSize}, + index::{Boundary, Point as AlacPoint}, + term::{Config, cell::Flags, test::TermSize}, vte::ansi::Handler, }; use std::{cell::RefCell, ops::RangeInclusive, path::PathBuf}; @@ -291,91 +233,6 @@ mod tests { ); } - #[test] - fn test_url_parentheses_sanitization() { - // Test our sanitize_url_parentheses function directly - let test_cases = vec![ - // Cases that should be sanitized (unbalanced parentheses) - ("https://www.google.com/)", "https://www.google.com/"), - ("https://example.com/path)", "https://example.com/path"), - ("https://test.com/))", "https://test.com/"), - // Cases that should NOT be sanitized (balanced parentheses) - ( - "https://en.wikipedia.org/wiki/Example_(disambiguation)", - "https://en.wikipedia.org/wiki/Example_(disambiguation)", - ), - ("https://test.com/(hello)", "https://test.com/(hello)"), - ( - "https://example.com/path(1)(2)", - "https://example.com/path(1)(2)", - ), - // Edge cases - ("https://test.com/", "https://test.com/"), - ("https://example.com", "https://example.com"), - ]; - - for (input, expected) in test_cases { - // Create a minimal terminal for testing - let term = Term::new(Config::default(), &TermSize::new(80, 24), VoidListener); - - // Create a dummy match that spans the entire input - let start_point = AlacPoint::new(Line(0), Column(0)); - let end_point = AlacPoint::new(Line(0), Column(input.len())); - let dummy_match = Match::new(start_point, end_point); - - let (result, _) = sanitize_url_punctuation(input.to_string(), dummy_match, &term); - assert_eq!(result, expected, "Failed for input: {}", input); - } - } - - #[test] - fn test_url_periods_sanitization() { - // Test URLs with trailing periods (sentence punctuation) - let test_cases = vec![ - // Cases that should be sanitized (trailing periods likely punctuation) - ("https://example.com.", "https://example.com"), - ( - "https://github.com/zed-industries/zed.", - "https://github.com/zed-industries/zed", - ), - ( - "https://example.com/path/file.html.", - "https://example.com/path/file.html", - ), - ( - "https://example.com/file.pdf.", - "https://example.com/file.pdf", - ), - ("https://example.com:8080.", "https://example.com:8080"), - ("https://example.com..", "https://example.com"), - ( - "https://en.wikipedia.org/wiki/C.E.O.", - "https://en.wikipedia.org/wiki/C.E.O", - ), - // Cases that should NOT be sanitized (periods are part of URL structure) - ( - "https://example.com/v1.0/api", - "https://example.com/v1.0/api", - ), - ("https://192.168.1.1", "https://192.168.1.1"), - ("https://sub.domain.com", "https://sub.domain.com"), - ]; - - for (input, expected) in test_cases { - // Create a minimal terminal for testing - let term = Term::new(Config::default(), &TermSize::new(80, 24), VoidListener); - - // Create a dummy match that spans the entire input - let start_point = AlacPoint::new(Line(0), Column(0)); - let end_point = AlacPoint::new(Line(0), Column(input.len())); - let dummy_match = Match::new(start_point, end_point); - - // This test should initially fail since we haven't implemented period sanitization yet - let (result, _) = sanitize_url_punctuation(input.to_string(), dummy_match, &term); - assert_eq!(result, expected, "Failed for input: {}", input); - } - } - #[test] fn test_word_regex() { re_test( @@ -468,17 +325,6 @@ mod tests { ) } }; - ($($columns:literal),+; $($lines:expr),+; $hyperlink_kind:ident) => { { - use crate::terminal_hyperlinks::tests::line_cells_count; - - let test_lines = vec![$($lines),+]; - let total_cells = test_lines.iter().copied().map(line_cells_count).sum(); - - test_hyperlink!( - [ $($columns),+ ]; total_cells; test_lines.iter().copied(); $hyperlink_kind - ) - } }; - ([ $($columns:expr),+ ]; $total_cells:expr; $lines:expr; $hyperlink_kind:ident) => { { use crate::terminal_hyperlinks::tests::{ test_hyperlink, HyperlinkKind }; @@ -504,9 +350,6 @@ mod tests { /// macro_rules! test_path { ($($lines:literal),+) => { test_hyperlink!($($lines),+; Path) }; - ($($columns:literal),+; $($lines:literal),+) => { - test_hyperlink!($($columns),+; $($lines),+; Path) - }; } #[test] @@ -572,39 +415,52 @@ mod tests { test_path!("‹«/test/co👉ol.rs»(«1»,«618»)›::"); } + #[test] + fn quotes_and_brackets() { + test_path!("\"‹«/test/co👉ol.rs»:«4»›\""); + test_path!("'‹«/test/co👉ol.rs»:«4»›'"); + test_path!("`‹«/test/co👉ol.rs»:«4»›`"); + + test_path!("[‹«/test/co👉ol.rs»:«4»›]"); + test_path!("(‹«/test/co👉ol.rs»:«4»›)"); + test_path!("{‹«/test/co👉ol.rs»:«4»›}"); + test_path!("<‹«/test/co👉ol.rs»:«4»›>"); + + test_path!("[\"‹«/test/co👉ol.rs»:«4»›\"]"); + test_path!("'(‹«/test/co👉ol.rs»:«4»›)'"); + } + #[test] fn word_wide_chars() { // Rust paths - test_path!(4, 6, 12; "‹«/👉例/cool.rs»›"); - test_path!(4, 6, 12; "‹«/例👈/cool.rs»›"); - test_path!(4, 8, 16; "‹«/例/cool.rs»:«👉4»›"); - test_path!(4, 8, 16; "‹«/例/cool.rs»:«4»:«👉2»›"); + test_path!("‹«/👉例/cool.rs»›"); + test_path!("‹«/例👈/cool.rs»›"); + test_path!("‹«/例/cool.rs»:«👉4»›"); + test_path!("‹«/例/cool.rs»:«4»:«👉2»›"); // Cargo output - test_path!(4, 27, 30; " Compiling Cool (‹«/👉例/Cool»›)"); - test_path!(4, 27, 30; " Compiling Cool (‹«/例👈/Cool»›)"); + test_path!(" Compiling Cool (‹«/👉例/Cool»›)"); + test_path!(" Compiling Cool (‹«/例👈/Cool»›)"); // Python - test_path!(4, 11; "‹«👉例wesome.py»›"); - test_path!(4, 11; "‹«例👈wesome.py»›"); - test_path!(6, 17, 40; " ‹File \"«/👉例wesome.py»\", line «42»›: Wat?"); - test_path!(6, 17, 40; " ‹File \"«/例👈wesome.py»\", line «42»›: Wat?"); + test_path!("‹«👉例wesome.py»›"); + test_path!("‹«例👈wesome.py»›"); + test_path!(" ‹File \"«/👉例wesome.py»\", line «42»›: Wat?"); + test_path!(" ‹File \"«/例👈wesome.py»\", line «42»›: Wat?"); } #[test] fn non_word_wide_chars() { // Mojo diagnostic message - test_path!(4, 18, 38; " ‹File \"«/awe👉some.🔥»\", line «42»›: Wat?"); - test_path!(4, 18, 38; " ‹File \"«/awesome👉.🔥»\", line «42»›: Wat?"); - test_path!(4, 18, 38; " ‹File \"«/awesome.👉🔥»\", line «42»›: Wat?"); - test_path!(4, 18, 38; " ‹File \"«/awesome.🔥👈»\", line «42»›: Wat?"); + test_path!(" ‹File \"«/awe👉some.🔥»\", line «42»›: Wat?"); + test_path!(" ‹File \"«/awesome👉.🔥»\", line «42»›: Wat?"); + test_path!(" ‹File \"«/awesome.👉🔥»\", line «42»›: Wat?"); + test_path!(" ‹File \"«/awesome.🔥👈»\", line «42»›: Wat?"); } /// These likely rise to the level of being worth fixing. mod issues { #[test] - #[cfg_attr(not(target_os = "windows"), should_panic(expected = "Path = «例»"))] - #[cfg_attr(target_os = "windows", should_panic(expected = r#"Path = «C:\\例»"#))] // fn issue_alacritty_8586() { // Rust paths @@ -689,21 +545,13 @@ mod tests { /// Minor issues arguably not important enough to fix/workaround... mod nits { #[test] - #[cfg_attr( - not(target_os = "windows"), - should_panic(expected = "Path = «/test/cool.rs(4»") - )] - #[cfg_attr( - target_os = "windows", - should_panic(expected = r#"Path = «C:\\test\\cool.rs(4»"#) - )] fn alacritty_bugs_with_two_columns() { - test_path!(2; "‹«/👉test/cool.rs»(«4»)›"); - test_path!(2; "‹«/test/cool.rs»(«👉4»)›"); - test_path!(2; "‹«/test/cool.rs»(«4»,«👉2»)›"); + test_path!("‹«/👉test/cool.rs»(«4»)›"); + test_path!("‹«/test/cool.rs»(«👉4»)›"); + test_path!("‹«/test/cool.rs»(«4»,«👉2»)›"); // Python - test_path!(2; "‹«awe👉some.py»›"); + test_path!("‹«awe👉some.py»›"); } #[test] @@ -791,9 +639,6 @@ mod tests { /// macro_rules! test_file_iri { ($file_iri:literal) => { { test_hyperlink!(concat!("‹«👉", $file_iri, "»›"); FileIri) } }; - ($($columns:literal),+; $file_iri:literal) => { { - test_hyperlink!($($columns),+; concat!("‹«👉", $file_iri, "»›"); FileIri) - } }; } #[cfg(not(target_os = "windows"))] @@ -865,9 +710,6 @@ mod tests { /// macro_rules! test_iri { ($iri:literal) => { { test_hyperlink!(concat!("‹«👉", $iri, "»›"); Iri) } }; - ($($columns:literal),+; $iri:literal) => { { - test_hyperlink!($($columns),+; concat!("‹«👉", $iri, "»›"); Iri) - } }; } #[test] @@ -898,26 +740,26 @@ mod tests { #[test] fn wide_chars() { // In the order they appear in URL_REGEX, except 'file://' which is treated as a path - test_iri!(4, 20; "ipfs://例🏃🦀/cool.ipfs"); - test_iri!(4, 20; "ipns://例🏃🦀/cool.ipns"); - test_iri!(6, 20; "magnet://例🏃🦀/cool.git"); - test_iri!(4, 20; "mailto:someone@somewhere.here"); - test_iri!(4, 20; "gemini://somewhere.here"); - test_iri!(4, 20; "gopher://somewhere.here"); - test_iri!(4, 20; "http://例🏃🦀/cool/index.html"); - test_iri!(4, 20; "http://10.10.10.10:1111/cool.html"); - test_iri!(4, 20; "http://例🏃🦀/cool/index.html?amazing=1"); - test_iri!(4, 20; "http://例🏃🦀/cool/index.html#right%20here"); - test_iri!(4, 20; "http://例🏃🦀/cool/index.html?amazing=1#right%20here"); - test_iri!(4, 20; "https://例🏃🦀/cool/index.html"); - test_iri!(4, 20; "https://10.10.10.10:1111/cool.html"); - test_iri!(4, 20; "https://例🏃🦀/cool/index.html?amazing=1"); - test_iri!(4, 20; "https://例🏃🦀/cool/index.html#right%20here"); - test_iri!(4, 20; "https://例🏃🦀/cool/index.html?amazing=1#right%20here"); - test_iri!(4, 20; "news://例🏃🦀/cool.news"); - test_iri!(5, 20; "git://例/cool.git"); - test_iri!(5, 20; "ssh://user@somewhere.over.here:12345/例🏃🦀/cool.git"); - test_iri!(7, 20; "ftp://例🏃🦀/cool.ftp"); + test_iri!("ipfs://例🏃🦀/cool.ipfs"); + test_iri!("ipns://例🏃🦀/cool.ipns"); + test_iri!("magnet://例🏃🦀/cool.git"); + test_iri!("mailto:someone@somewhere.here"); + test_iri!("gemini://somewhere.here"); + test_iri!("gopher://somewhere.here"); + test_iri!("http://例🏃🦀/cool/index.html"); + test_iri!("http://10.10.10.10:1111/cool.html"); + test_iri!("http://例🏃🦀/cool/index.html?amazing=1"); + test_iri!("http://例🏃🦀/cool/index.html#right%20here"); + test_iri!("http://例🏃🦀/cool/index.html?amazing=1#right%20here"); + test_iri!("https://例🏃🦀/cool/index.html"); + test_iri!("https://10.10.10.10:1111/cool.html"); + test_iri!("https://例🏃🦀/cool/index.html?amazing=1"); + test_iri!("https://例🏃🦀/cool/index.html#right%20here"); + test_iri!("https://例🏃🦀/cool/index.html?amazing=1#right%20here"); + test_iri!("news://例🏃🦀/cool.news"); + test_iri!("git://例/cool.git"); + test_iri!("ssh://user@somewhere.over.here:12345/例🏃🦀/cool.git"); + test_iri!("ftp://例🏃🦀/cool.ftp"); } // There are likely more tests needed for IRI vs URI @@ -1006,6 +848,22 @@ mod tests { point } + fn end_point_from_prev_input_point( + term: &Term, + prev_input_point: AlacPoint, + ) -> AlacPoint { + if term + .grid() + .index(prev_input_point) + .flags + .contains(Flags::WIDE_CHAR) + { + prev_input_point.add(term, Boundary::Grid, 1) + } else { + prev_input_point + } + } + let mut hovered_grid_point: Option = None; let mut hyperlink_match = AlacPoint::default()..=AlacPoint::default(); let mut iri_or_path = String::default(); @@ -1040,7 +898,10 @@ mod tests { panic!("Should have been handled by char input") } CapturesState::Path(start_point) => { - iri_or_path = term.bounds_to_string(start_point, prev_input_point); + iri_or_path = term.bounds_to_string( + start_point, + end_point_from_prev_input_point(&term, prev_input_point), + ); CapturesState::RowScan } CapturesState::RowScan => CapturesState::Row(String::new()), @@ -1065,7 +926,8 @@ mod tests { panic!("Should have been handled by char input") } MatchState::Match(start_point) => { - hyperlink_match = start_point..=prev_input_point; + hyperlink_match = start_point + ..=end_point_from_prev_input_point(&term, prev_input_point); MatchState::Done } MatchState::Done => { diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index ec9629685d8366864b92a6160ece623450f72b0c..68fd84b32b64e15b0ea63ef851ec5aac457179c2 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -316,8 +316,8 @@ objc2-metal = { version = "0.3" } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "process"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "termios", "time"] } scopeguard = { version = "1" } security-framework = { version = "3", features = ["OSX_10_14"] } security-framework-sys = { version = "2", features = ["OSX_10_14"] } @@ -347,8 +347,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "process"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "termios", "time"] } scopeguard = { version = "1" } security-framework = { version = "3", features = ["OSX_10_14"] } security-framework-sys = { version = "2", features = ["OSX_10_14"] } @@ -377,8 +377,8 @@ objc2-metal = { version = "0.3" } object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "process"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "termios", "time"] } scopeguard = { version = "1" } security-framework = { version = "3", features = ["OSX_10_14"] } security-framework-sys = { version = "2", features = ["OSX_10_14"] } @@ -408,8 +408,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "process"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "termios", "time"] } scopeguard = { version = "1" } security-framework = { version = "3", features = ["OSX_10_14"] } security-framework-sys = { version = "2", features = ["OSX_10_14"] } @@ -448,8 +448,8 @@ prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["pro quote = { version = "1" } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } @@ -488,8 +488,8 @@ proc-macro2 = { version = "1", default-features = false, features = ["span-locat prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } @@ -528,8 +528,8 @@ prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["pro quote = { version = "1" } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } @@ -568,8 +568,8 @@ proc-macro2 = { version = "1", default-features = false, features = ["span-locat prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } @@ -661,8 +661,8 @@ prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["pro quote = { version = "1" } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } @@ -701,8 +701,8 @@ proc-macro2 = { version = "1", default-features = false, features = ["span-locat prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] } rand-274715c4dabd11b0 = { package = "rand", version = "0.9" } ring = { version = "0.17", features = ["std"] } -rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] } -rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["process", "termios", "time"] } +rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } +rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } From 8df616e28bb34e8dab899747acae118788af4c1b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Sep 2025 15:55:32 -0700 Subject: [PATCH 159/721] Suppress the 'Agent Thread Started' event when initializing the panel (#38535) Release Notes: - N/A --- crates/agent_ui/src/agent_panel.rs | 9 ++++++++- crates/gpui/src/app.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index ba71fd84ab5b9d666256afeb0a2c5677aac9adb1..ca6a5fb2f6c216e7886394da069c93e5029a5ed0 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -408,6 +408,7 @@ impl ActiveView { pub struct AgentPanel { workspace: WeakEntity, + loading: bool, user_store: Entity, project: Entity, fs: Arc, @@ -513,6 +514,7 @@ impl AgentPanel { cx, ) }); + panel.as_mut(cx).loading = true; if let Some(serialized_panel) = serialized_panel { panel.update(cx, |panel, cx| { panel.width = serialized_panel.width.map(|w| w.round()); @@ -527,6 +529,7 @@ impl AgentPanel { panel.new_agent_thread(AgentType::NativeAgent, window, cx); }); } + panel.as_mut(cx).loading = false; panel })?; @@ -726,6 +729,7 @@ impl AgentPanel { acp_history, acp_history_store, selected_agent: AgentType::default(), + loading: false, } } @@ -857,6 +861,7 @@ impl AgentPanel { agent: crate::ExternalAgent, } + let loading = self.loading; let history = self.acp_history_store.clone(); cx.spawn_in(window, async move |this, cx| { @@ -898,7 +903,9 @@ impl AgentPanel { } }; - telemetry::event!("Agent Thread Started", agent = ext_agent.name()); + if !loading { + telemetry::event!("Agent Thread Started", agent = ext_agent.name()); + } let server = ext_agent.server(fs, history); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index e6c3e3b8deea9b82514b5ac932c4f204fa081e14..07ff04e32abc19dbe681ab6214d06469fe7917ff 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2401,6 +2401,20 @@ impl<'a, T: 'static> std::borrow::BorrowMut for GpuiBorrow<'a, T> { } } +impl<'a, T: 'static> std::ops::Deref for GpuiBorrow<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.as_ref().unwrap() + } +} + +impl<'a, T: 'static> std::ops::DerefMut for GpuiBorrow<'a, T> { + fn deref_mut(&mut self) -> &mut T { + self.inner.as_mut().unwrap() + } +} + impl<'a, T> Drop for GpuiBorrow<'a, T> { fn drop(&mut self) { let lease = self.inner.take().unwrap(); From be77682a3fd3ace184a9059cc7b212e3ea4891d3 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Sat, 20 Sep 2025 04:40:22 +0530 Subject: [PATCH 160/721] editor: Fix adding extraneous closing tags within TSX (#38534) --- .../src/session/running/console.rs | 6 +- crates/editor/src/editor.rs | 60 ++++----- crates/editor/src/editor_tests.rs | 116 +++++++++++++++--- crates/editor/src/hover_links.rs | 2 +- crates/editor/src/items.rs | 7 +- crates/language/src/buffer.rs | 39 ++++-- crates/language/src/language.rs | 15 +++ crates/language/src/text_diff.rs | 5 +- crates/languages/src/javascript/config.toml | 3 + crates/languages/src/tsx/config.toml | 3 + crates/multi_buffer/src/multi_buffer.rs | 28 ++--- crates/project/src/lsp_command.rs | 11 +- crates/vim/src/vim.rs | 7 +- 13 files changed, 218 insertions(+), 84 deletions(-) diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index 92c5ace8f0128e47db08c6b772376679213ffbe1..cf7b59f2fe96bb031fc1ed1a5d7ae4005dd37eb9 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -12,7 +12,7 @@ use gpui::{ Action as _, AppContext, Context, Corner, Entity, FocusHandle, Focusable, HighlightStyle, Hsla, Render, Subscription, Task, TextStyle, WeakEntity, actions, }; -use language::{Anchor, Buffer, CodeLabel, TextBufferSnapshot, ToOffset}; +use language::{Anchor, Buffer, CharScopeContext, CodeLabel, TextBufferSnapshot, ToOffset}; use menu::{Confirm, SelectNext, SelectPrevious}; use project::{ Completion, CompletionDisplayOptions, CompletionResponse, @@ -575,7 +575,9 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider { return false; } - let classifier = snapshot.char_classifier_at(position).for_completion(true); + let classifier = snapshot + .char_classifier_at(position) + .scope_context(Some(CharScopeContext::Completion)); if trigger_in_words && classifier.is_word(char) { return true; } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4084f61bb4a44d591aa544a622fa8888f56a5c57..8b0fc5512731eff70b1e9ac41b6bfe16a65babfa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -121,10 +121,10 @@ use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; use itertools::{Either, Itertools}; use language::{ AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow, - BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry, - DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, - Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject, - TransactionId, TreeSitterOptions, WordsQuery, + BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape, + DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, + IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, + TextObject, TransactionId, TreeSitterOptions, WordsQuery, language_settings::{ self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings, language_settings, @@ -3123,7 +3123,8 @@ impl Editor { 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) + buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion)) + == 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 @@ -3551,7 +3552,7 @@ impl Editor { let position = display_map .clip_point(position, Bias::Left) .to_offset(&display_map, Bias::Left); - let (range, _) = buffer.surrounding_word(position, false); + let (range, _) = buffer.surrounding_word(position, None); start = buffer.anchor_before(range.start); end = buffer.anchor_before(range.end); mode = SelectMode::Word(start..end); @@ -3711,10 +3712,10 @@ impl Editor { .to_offset(&display_map, Bias::Left); let original_range = original_range.to_offset(buffer); - let head_offset = if buffer.is_inside_word(offset, false) + let head_offset = if buffer.is_inside_word(offset, None) || original_range.contains(&offset) { - let (word_range, _) = buffer.surrounding_word(offset, false); + let (word_range, _) = buffer.surrounding_word(offset, None); if word_range.start < original_range.start { word_range.start } else { @@ -4244,7 +4245,7 @@ impl Editor { 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); + .scope_context(Some(CharScopeContext::LinkedEdit)); classifier.is_word(char) }); @@ -5101,7 +5102,8 @@ impl Editor { fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option { let offset = position.to_offset(buffer); - let (word_range, kind) = buffer.surrounding_word(offset, true); + let (word_range, kind) = + buffer.surrounding_word(offset, Some(CharScopeContext::Completion)); if offset > word_range.start && kind == Some(CharKind::Word) { Some( buffer @@ -5571,7 +5573,7 @@ impl Editor { } = buffer_position; let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) = - buffer_snapshot.surrounding_word(buffer_position, false) + buffer_snapshot.surrounding_word(buffer_position, None) { let word_to_exclude = buffer_snapshot .text_for_range(word_range.clone()) @@ -6787,8 +6789,8 @@ impl Editor { } let snapshot = cursor_buffer.read(cx).snapshot(); - let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false); - let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false); + let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None); + let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None); if start_word_range != end_word_range { self.document_highlights_task.take(); self.clear_background_highlights::(cx); @@ -11440,7 +11442,7 @@ impl Editor { let selection_is_empty = selection.is_empty(); let (start, end) = if selection_is_empty { - let (word_range, _) = buffer.surrounding_word(selection.start, false); + let (word_range, _) = buffer.surrounding_word(selection.start, None); (word_range.start, word_range.end) } else { ( @@ -14206,8 +14208,8 @@ impl Editor { start_offset + query_match.start()..start_offset + query_match.end(); if !select_next_state.wordwise - || (!buffer.is_inside_word(offset_range.start, false) - && !buffer.is_inside_word(offset_range.end, false)) + || (!buffer.is_inside_word(offset_range.start, None) + && !buffer.is_inside_word(offset_range.end, None)) { // TODO: This is n^2, because we might check all the selections if !selections @@ -14271,7 +14273,7 @@ impl Editor { if only_carets { for selection in &mut selections { - let (word_range, _) = buffer.surrounding_word(selection.start, false); + let (word_range, _) = buffer.surrounding_word(selection.start, None); selection.start = word_range.start; selection.end = word_range.end; selection.goal = SelectionGoal::None; @@ -14356,8 +14358,8 @@ impl Editor { }; if !select_next_state.wordwise - || (!buffer.is_inside_word(offset_range.start, false) - && !buffer.is_inside_word(offset_range.end, false)) + || (!buffer.is_inside_word(offset_range.start, None) + && !buffer.is_inside_word(offset_range.end, None)) { new_selections.push(offset_range.start..offset_range.end); } @@ -14431,8 +14433,8 @@ impl Editor { end_offset - query_match.end()..end_offset - query_match.start(); if !select_prev_state.wordwise - || (!buffer.is_inside_word(offset_range.start, false) - && !buffer.is_inside_word(offset_range.end, false)) + || (!buffer.is_inside_word(offset_range.start, None) + && !buffer.is_inside_word(offset_range.end, None)) { next_selected_range = Some(offset_range); break; @@ -14490,7 +14492,7 @@ impl Editor { if only_carets { for selection in &mut selections { - let (word_range, _) = buffer.surrounding_word(selection.start, false); + let (word_range, _) = buffer.surrounding_word(selection.start, None); selection.start = word_range.start; selection.end = word_range.end; selection.goal = SelectionGoal::None; @@ -14968,11 +14970,10 @@ impl Editor { if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) { // manually select word at selection if ["string_content", "inline"].contains(&node.kind()) { - let (word_range, _) = buffer.surrounding_word(old_range.start, false); + let (word_range, _) = buffer.surrounding_word(old_range.start, None); // ignore if word is already selected if !word_range.is_empty() && old_range != word_range { - let (last_word_range, _) = - buffer.surrounding_word(old_range.end, false); + let (last_word_range, _) = buffer.surrounding_word(old_range.end, None); // only select word if start and end point belongs to same word if word_range == last_word_range { selected_larger_node = true; @@ -22545,7 +22546,8 @@ fn snippet_completions( let mut is_incomplete = false; let mut completions: Vec = Vec::new(); for (scope, snippets) in scopes.into_iter() { - let classifier = CharClassifier::new(Some(scope)).for_completion(true); + let classifier = + CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion)); let mut last_word = chars .chars() .take_while(|c| classifier.is_word(*c)) @@ -22766,7 +22768,9 @@ impl CompletionProvider for Entity { if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input { return false; } - let classifier = snapshot.char_classifier_at(position).for_completion(true); + let classifier = snapshot + .char_classifier_at(position) + .scope_context(Some(CharScopeContext::Completion)); if trigger_in_words && classifier.is_word(char) { return true; } @@ -22879,7 +22883,7 @@ impl SemanticsProvider for Entity { // Fallback on using TreeSitter info to determine identifier range buffer.read_with(cx, |buffer, _| { let snapshot = buffer.snapshot(); - let (range, kind) = snapshot.surrounding_word(position, false); + let (range, kind) = snapshot.surrounding_word(position, None); if kind != Some(CharKind::Word) { return None; } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index f18187d558f1cb90e137d06591ec5b2ecb7b1654..05742cd00bb834550ee20377ff46da6649272f43 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -13,6 +13,7 @@ use crate::{ }, }; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind}; +use collections::HashMap; use futures::StreamExt; use gpui::{ BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal, @@ -23773,6 +23774,28 @@ async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) { }); } +fn set_linked_edit_ranges( + opening: (Point, Point), + closing: (Point, Point), + editor: &mut Editor, + cx: &mut Context, +) { + let Some((buffer, _)) = editor + .buffer + .read(cx) + .text_anchor_for_position(editor.selections.newest_anchor().start, cx) + else { + panic!("Failed to get buffer for selection position"); + }; + let buffer = buffer.read(cx); + let buffer_id = buffer.remote_id(); + let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1); + let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1); + let mut linked_ranges = HashMap::default(); + linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]); + editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges); +} + #[gpui::test] async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -23851,22 +23874,12 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]); }); - let Some((buffer, _)) = editor - .buffer - .read(cx) - .text_anchor_for_position(editor.selections.newest_anchor().start, cx) - else { - panic!("Failed to get buffer for selection position"); - }; - let buffer = buffer.read(cx); - let buffer_id = buffer.remote_id(); - let opening_range = - buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3)); - let closing_range = - buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8)); - let mut linked_ranges = HashMap::default(); - linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]); - editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges); + set_linked_edit_ranges( + (Point::new(0, 1), Point::new(0, 3)), + (Point::new(0, 6), Point::new(0, 8)), + editor, + cx, + ); }); let mut completion_handle = fake_server.set_request_handler::(move |_, _| async move { @@ -23910,6 +23923,77 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + let language = Arc::new(Language::new( + LanguageConfig { + name: "TSX".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["tsx".to_string()], + ..LanguageMatcher::default() + }, + brackets: BracketPairConfig { + pairs: vec![BracketPair { + start: "<".into(), + end: ">".into(), + close: true, + ..Default::default() + }], + ..Default::default() + }, + linked_edit_characters: HashSet::from_iter(['.']), + ..Default::default() + }, + Some(tree_sitter_typescript::LANGUAGE_TSX.into()), + )); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + + // Test typing > does not extend linked pair + cx.set_state("

    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 z z%5xp?;CSAtm{5bJac` zg6mx}Xwml+3w$cE^R^<2PZEcq$+Gx%zcJN*yTK+m+6))Jw-MCNA7r0L-=W(I^Z8nd zP4sSb8nyJ^iZPQNNko zGs%Q^_gg?ss*N>LHip4huh}IM@}RXt9^@Uf$j^b>n4s0ni@cYM!n4XyGv_qv)tG5} z`PxMG+r9>vH`Ry@o;nO_YUiLszKj$~E{AxxThyk-A18ZAgUuHe)Uc3cYM0%>%ejpt zo6Dn2o)c_LKmb8s7T*o~0(nHCzKuBvCCpb3#5&b@+gr6(ElnVXv zf?Dt8q&S)Dp{`KE1^kD^VcJpR{_G-sU^SPoa<7f*C)A>{z6R`@+ruANupdQTpF*}) zE5`X*G3}PZux(cysHPaukJeXcgoPO@mVcltI=Re8V=G!lM75}=MA&2APAS3m$GfjE`s~Jcj0Ye zUtG2OVw>0tN2DQ_Ss!vZSY&QclYO1LIG!ZDiS*m?ImnVz3ARMN>3pE}GVmu}7{ zms%Cc*VPkYOY~lvR;P>=Pp3ifJ{OoN*h*$_yZrSFJ$QzjFJkttO~n45B;+>;fwFT3 z6sMhM{+!zZvu`{=8%fEwjO-p*^2H9umjp6*x!FNP@BsNKoeqC*eIrpjS;&k?rdLzb zpl7Za<4_d|^wksQXytf%#2jFbfdp=QQHY`C86<3e6-_-_Nt^!N#Y@iOXqUR3bUA#- zBhGqoLEZ=h?|9QNbYqGjHYJQ+IQE-X{)R-TFW1FU8eBoBkul*h!!WYnt_E@ZmU&CElra)6& z9Er<2fM>EjF!f#-==3V0zS4SHA!)@((zlqotb$#(&C?9wwx^7ikx>l=eEM?}ie28_ z7V~QYSyrzOrCc82^S!%#hYx$xkise6n+3kox)|x#*&8 zd-d-M&~kr-#~;}8#1pmn>`)I?ew)PJY*EAZnFnZD^9X(7eurc{zd;`b@X;nC4upE6 zxINgA&21?mTZwKddhC7@wM&t80o%%AQGxszK++9yxE-w;5yJ?Udb_D{5}+ug*h zS(OUss$5L!R?(Ys0Shi}9Y5IX@#qo79hF(9@sucvpVcz#`r{da2u*@e>+i z4=GjfP5)RD!_Kv6T^`PAD$TZC+OQF&Q|>}Q>EX6OFAE&CNnmeQKfp>>Q#kiVFwr8;JWGQUSHibL;Igb2acT@=vVZIoj zBNvj+@O$k_*vzaGByhpIOJhFztWfw@)$wt~GK9dYSGX$NM#}K(e zAASui=VyIOQJOE$y)#xC;m!)}gwlt=%2 zJA>$lE1~`1A*!~K4|j4S$iskpV6MFaTeeq%e@7af61tck>WbwzDDWW4vkRjnbn#FB z5n44`fL+qZ=&~c*;P08^w8PCAyYUoV!8TK&t!6lsxE_K$3uxya&b!g{l|Aw~0L@R- zkYzD*5VqU^t$okon?p6tNIOk$|4f5y33Z(OQUVH=-s3K(DdfK8Rvgb|ZS6Dfz?w!O zfo07rc4FdeXjq%cUVinR%GT@Rc=1vi^mz%j%qe5bP1DJz!5A=BbixjoTi_->hq^3S zLe6E_F;knmnMhU`emX~JiC)#^B9L>DFU|omAasE_GIwQ>tkXA z4R|Mo?_<*b1kkP+4^#8(_=mMUnIzGvFd`KQ`zHJYN!wO<+9%Z(IcpoV2-(pjv|TCAK@FzHvinohXw)2geB>30!*9M3#oA&x zpSJ<6-(5i7l|j}atAJQ;m@oKRCe3kpbuqbE7Tbh4#$<>$SVm95?CX=+zj4`YM@Bp8 z@5pD&-l@T2wR7~PC@}Y>CeU+Edi>`GYYxg{ zgZ4Q@g)X*oYXLkSEu&|RKVde9@9!-L;LUCPIDMnA<4s!bwX>qUy%y&5q%73M~_xV*QSt||q z6V2!9-u}e1w~!&?OVTl1C6z{Jj)OWn2Q>TC1bd$pz=%j8UAj6BPFV_=mK7GTw_gG8 z##>UgS(9ytcb`G$>B<=DDNj}vDYDuFxzMdsk2}*|(`@Nj*qqjhy`wd(myQS~d`kd1 zi3GAWUj|aE4Dry+>s$|PHjH!>V5{qFvP#(y!$0rC(VrJtp)?K1+OZ9o#Xew2G^yW@ z6Clpz5Eech!ibk!@sHI#l6A(Lp7eh}d2Qj4WztNhmYxL9`CP}3>tF;iLUiela10L9 zx9J^r0g>Jh%+yvRC}=C8oo0J5w}P9pB2EsmYz?u09DM9>Kx z6?h|S0ql`>0r{!Bs1;up&R&uuSB82aAw!%nfuE^yizhiAk;yAo;r9Q(J@ELax3u7K z0{QN~1Zs+QqP&(1EYGO1iPfAvpq639t)JZsPMaa#3DdgkQpXVY6DNwl3I6Lo&FCt!)|vmPJh_OGpm#p zvYCn&$BdZ&e(E4qJO-0>mEhElYPvP_6RSOP0>+$TuH*pvlQ^21aBZoQ5R^pkjtz>hI z14#+oiGCCOaPdX|)~k;#U_ziecBo%x#0mh@<5fVfOAkX7tJpJ-kMLAVVo|VG7?yo1 zA#;vu;U1SMH12N)C3oiF7v2S`cYhc>>tktFn**K{yN;VjWBGOIFL0sLBa9j+!sKLY z+6LeB!S&TGZ2O^SbTFSYy_mS+<5&W=gSJe|-*(^)t>tN(nKN0>`0#hP9SHGlFf!T> z^myWI%l?DdGJOqY8j9)U_*KL!k#nxb#lglag-q+I)2uc-j-K(Ki4IwBFjL)!g959Z8n4$%&NJlXY zG(1Jc1*<^GcL}rZM+m7N?!bo{pGf>mTiE0r2BZ2fA%)8abs0I6Thkm-oa1)h+x7x= z*DKRU|2BYm$s~cZ%L(@MzW=E6iCZ`v{*-L4Hnt5e3nK3I$Cybgp3?Xh4Z(F2X-F8? z4HEV&#@Kmr=h#vDqpS{3Y3=7l8?S(f7fLw&k`d9634;?w{pdOA9eKG`S`f52gN9U( z5b4+lWaH>-ay(rO^LIWV55FEI?;8r3b(j39sliTMP^?L|!W1+-QOI#*Wzkw*1N=JF zxE$RoD3od8`j~of^4?W2d;E+;~;Ls$bYa z_pepr&)vBny2Yw##lIb3r}Krp_BVpFduyS#Cyi>Iya}CN;Y9fCBerMQ1S|4o*gYA~ zKJUgG5j6zQb{xnu4ywF`dgcqwilcC$EXw?m8)h{=L(v=KillYxWv?FfNYH_%a>! z^AGd1_mtr?**XZxY2`hvXyD78n@iXE-G?UUB5EPB5=SzNsY3oX$hxD5=D$=0mWx*7 z&!4W)HL{I=>NiETX+mt^?Aa{4$(o+MNzq$gl-N5hgU<%TbmG~Y5L@4d!YO~K)IT#U z4QfRz=ZVbT1>BzUX&bY0!b84m5XX24%7H!8W63Sm0oV{8hE=Wta$9Q^%k90GntPvN z<_alz(V~iqbxsf-IFGx(>p@nYyg*cDF1w>&k6qAxg?^goN_XXH;@a?1dZ!|vHEPX6 zE!P+P^t{zD;qVGPWAKdL@5&-sK3taEC5`j&tg%_&bdfsE5ykLpIz;MiD6tLR!vwBP zf}2Nf!R#Vw-X9}p@HsL>D)(OE-ksb#c(YGi1LvK8`)C%L+)st}8xf>K_Xh5^K1TSn z){*avH}D+xwv+oOqPg6s0bO)wjv#MznEso0gdTP`fE1}RT5jM@5-P;t6r(Okdtpvv z9p93J5=u;UejHoa9R$+jLZRSvBBpNkgNN1?I7N!$YEnu^yzl$ix*nuUKhfpj~8M1{%dk4 zpbO17e&5f^dT=k+#U`mfDEyud(uV1*rxRr?9qmx&Tny;gnZmwCd6M^*`>$RWfq74M z6aQRpcib@^`BRfX>p~MWdMv^hf@9>q(@GMQVt`v_IdC0DFGx%7B5VB);<-F|kSkC{ z-#u@TzubX{$%cWFfe1E5Mlp|PPZPv)Gv#u3nSVB*RFE}Eu`EG}Iai?lkq))IbqjX}@Y@vs^M~B71U&Papx>JN zplg2~&({iAE3>EI;w=N$hUIX>#3)$%_60=ceW2Ob{neOT=cP9=V#*$y4a-n8cBTb*D#6M0n*u{YY?CE>Tt~r{_Pk1-4zGl8>p%h}O#M>Git;4%Rx*=bb^af5>+-rThWnwx@fEHV^tTsYhoV)lp5 z3RQsnMI5vITrRO#9S?PQmFWFe0FmC1BR6U8JPyi){bTf`W> zf+g6HY>IXvIZ%1*FmCPM4|;9$85_ekvh?T!(2Os_UpcANB0Y(EycA$#*bB(=KS^^w z#K6837ktd^J#JW~@W}#DY__Vp9T#=Q`0@C5tZcY|n%5oR7J zKuy02E>rh|Y6T~Q-McOu&v%I!K3#)!oV<_IK4(JG3t7}`y$6b2tKjCrO`!8@4x4=@ zhCQdw?PkaQAUz@Vdt^g^7ZXkzxSr7F>?V-$H3Y|BuJAEei`FlU!mu?0 zethsYSXVpBaW#LCC#S?=fy-qyxww73)T%eR=d|6UN=IfYR9Mhw@7w_|>E z9jV(KMs%JR(&27vDtWn+GpN?n$QcIt(^1QS#v#kZV%8Oh3tWG;B(*@N$;pZ19g^Fv^Zg- z9h^t+39n%EZ;aBQ8Iy7O1TVO1BcQA2e8;C?jGuEa;05a{e3metSlGl5$}&~YROZmJCxjds*mPT1nfi`oS{mFKdx&3c>dt!xXKZ_<(u9bi6I+9h90SsQ!4I)Hwep z!6lPzWx3tob+0|pdZ-bGMu+I)iJi<~;{_62|A@KT^_(s})`hA`zD&Z#N&GX<_#`dt zE!`C*0i#C2jL&Pt6A!F8-@!S$?N=tXRLw>M;V0~gIz^j-=_z!h_;!dBDyIJuFCs_b zM3QF)VY+)rU|BP>SMw}$+hGkAJABhd?2!?QIM?FZML(&QsWDITDR&Qjk_R6v{NSyE z94t6_3}z0wk$bDu$w<8<&VAs9HYTQmGBaHi*|LawROR8%hr?t}wF4e{avbY5N{DTm zH;yeBgZbzCXhgwm;^lsc>E-gWenZFTH17)X-(6dN`mJiXye@~aco+-eo1I9_@nd}B zKm*Wy;|y27U&de6pUJ!PUQFvB12*N4KYL3fhA5UN!p+i0D6z#D^yjloOy>ha%3fiJ zml2u!CJ1(vEke<}ujKghSMvt;Bs{969qfpZqiNWTdT1vAku@}C z`c4E5a16o3kO@Rxd`s(bzaX+-bDZrCb2pN1&atcdmAToTHLS@^*YrJuwyl8f4vXxbd-Uff-4-`m4X-loMyu1CSrTd1CpH3Nn~G)klh2E z%k%PiV$t7)gAe{AqDmgvZTx($TRE<$3f2n?VbaNIZCTg8&=Dbddf{9Xm3NyBYu-p>;%_%<%pM~m zzb6Qiol3Dzx()@}mx%j?7R-@sWVf7O#(e*Bko_6>5`CnSQRlh>&duS&4AHCXkn4Wx zk1L_ujdOvv9z^kpyLs<5jA^XOc9Nd*n%Pu28D50wp;1dFFZs6vTwl8zZ5FcZcANIO zvQKj{OluKX?~B6->33v*(;W8TAFfw2rVr)glaW=*|F(!+ z+Qe(?js1h~o=LKL%|7&W+dR&jH$gxz7}6U5CAe^QF@9=H;7@-W53gh#VZ(fJ_VPtb z*sNGbJBM7M)R5!o8oBbb%MBq@R0kbqm61K#eEK50jZ8i~l?q)B#5s*z&ouP|mQU=# z$GLg9Xl66~kD@aT$Lj0iuqg9XrbG&95J^-xXRjwxM1xRh79}MW%^J)JnKG3mQKpn3 zRf7tP`H$Gh)rWzuLgzT=0}zn#P|85_8N z>Jsl2oJiJkQ^1e^Hru%*T8U+h6x>+uMf4tPfynz9ymd5y8qe>r{qR1t+{X7f_X{BV zmL*NwR?j-#*W~*<&D3tQI2bSioc0zO82+{rohy%!J0nfuN68r460Jddr;oskE@QyP zXaqDRx{<&Z@tf&`_Ctq-6Lwe)~|!m zPr78r`L|@RWe@xvwT)vL{`=zkk@ns>gXUz4A}+n=Y%nQKqU zjh^jrP{IH9H;SUY2wgo|y3h!sEC z#RlS6xD>an+lQUe5|E=PAxIHiAqTH$L0A4vcItR#9QVZu zTqkd(SGU|`lZkys7Vhb~s`62(YMn zDzrbVj0sI*c%DP<%SuJ2#yPncNQ+JcMTes66QAlNzhMLO!y8yex871t`#6eH za)kBcBT$&*hX2MsWKR|b!i^2f(CUZ;+(`XcE$wfGy~?}j#l@;1x12?-^0j2;K6#KI zer|b4O-=M+z8QHsR~rT-{t}hxHk_$xHhGuKbHbKvh9#pm(bxx3;IwrqtkBEn`|Ba( zll&QG`q>`%=X?yLOumzOD%y}Zt4XBq|AJX4T>$;dbEwY!+vMUr2RzcThkRD|$CJxA zoVIH^5jR!Bam;pD^<7G^D|0K#nBSnPvtrPB^K;npNRxUFD5I|QJ-XfKIr-dYfaJ|RS-!*_Kc2gaAEt|74BGIid+= zKlP&5V`*ZtnCCqtMq6bk7NKyK95=Czp(H93!WVhN&5~q(2F+)#j7;#(?H6#Q|1kI{ z{(^ByEUlswpu|*KlymSeao^}pOf+YpcgJQLHY%O;-O~i&RwH=Zk}0bCtc7DA{H6o) zHRRL&jpWrpG;O?C!PQwwBlSFn3zKitOl}pu_i7(GJN7WQb9EFq{AU&(@Qs88XNB~5 zOcA5ql}Gj{5Oq%%i&hQ#z! z;TRwFg}vc^8D1&z`2+CceAYIgyZ3x%&AdH$e3u7~zi=3T-2RSHQcF<6RRT)ZS6s`s zy@~%m1j5MCd@f0TkRItvgrEXz%nvvVu04%HyC}(;iH$c!d3=`pMSEK1t-uWwKKYRJ zwAt`s;u%gVP=o`)^>qAkW7PG}g@QIER%cN-(JKE#{}h;FiB3H}Oql^*sdw0bv+}_8 zWRa4uCitOK3v*^wk{#~hqU*lKXf&>iO%HnnE&irBat?!B_fDJyN6~ie3gj9BsMiV) zs`6l3&DZwZGzvm-beJqUUHndy1@8Fly%LzNNrL+OohVG41WoH7l4%zH#PjAcv`GI% zmUtTqWsl6^JH^&ya*~@BAG3hKTfAeiP6ztFjwd^B{l^~sBMYS|qaej0k6Zdu8ya>+ zlDsudESg8rIGt%Q{;&g9=oHhh3)|4n{1E8P$-%e1|8cUj7<%MO1t|1o;xZYdnlq|< zsY-SRS-WW@_eo!wzH0YF&0k5_!(5^CWjeD>Lz){CnGQcEsMJ`+=7=;dexQ#d%|O@C zlzW;H3{9`nP=C=BI3X@u^I=aFyw8lJharVt9Q447ojGurn&TM9XN>9bcDhwY8h_`9#}i3hBKMl@{-`9>>qKBIz|6ZEW3=X~Er zvx;*P$OCByC^k-HO2unoL5HlM;z$IEpBlsHEE>d2qZ{{1!tk;? zaPwdqv{zZ;=VvlFMf8rY=oQC?)n(+*O8y-8eUFK;|NnCeiYcg|){o`X6T=yWQE}X+ zj}O@qi!xxbQ5@Ks$}v8F?l6`H8xW0VW2=$_`S&we_&w^5P~Yz|-8K3Q#P?)j7yo^9 zWK>0G&fUSiKj(8kiWSuK_mlx=;Q$z(*J(vQ`J~FTKkH~RY2dBl@qx0$* z5;p27?#>qAx&;BSYjk$?TJr#Gm9gh$@Uznv)pTyUR~hM*Sb^(I*VD!g+b#N{3W;Ap zD17)M39~})G7U;etYvr>k*L2%dwuOeaUc}}<>N4LgbVFyQG(QyXPNQ*PT~7nH&`>U z8Mj0Ql62*6I%z-Om0dj_oVEJcGZQF#$mt`AZd(hlp4Y>(y- zu3m#d8h!Nc;4N4(k^#fXmtg1HEA0N`YjJhK8gBZ^oscOx3H&nanC7SQf^U0_tP(uk zp!cyo{#2X=&DX!uFo`N!K4&dnx}D6`F58cjcNT)d$U=U0o3TLz~qD zMBRx|xZ+js4qdQC;Sv6?Zxc&&zbP&NtkkwLFzA7u;osync|~Q;Lc6>I2-I z(eLPzu(R|*BVr~e1q~KLoS2nEYUoUeN^GVd_zbbqE(f%%nM7PJ$Ff&Gt2BJ+nEMOi zrnn^O|7|8Z;Hf0KWfn?{`;vjK)P=$|N}w6UiL{q?k)PKj$r?r8w>?3Vne+Q0Jj!b) zN2hAiR<}-w3U3mfNl%6JV_LY^eGvrO7g^=oi9vyn10J5V9j0_Df(@#J&D&u*Dm|0) zc{oClzGx%N*q@L2-gBv(**rMisxJyWS1LMj=m)1$d>HGxduX@eY#4mq+rv>chcfy(lhh#cpi*STdM^gWXB^>8EZm1FNoenT zhdRb?7A-iNz?#mV#Vzrc#u+j$s2d#tC*UZ&ZZg5hqvyaj`=FJ7(;E77VKO1!-^rZ3 zP;4^U0uhp<*;zLxqf^XHVSi&AJbU_x?sS%cUG>-5Z^t#DvN(@*?N7E^B(WABhsn~l zU((nyDqC=e!Wr(1$}>jYQ3LeWwUdtx7q}Zb8}R-uH&)rSpRVBs$=exosonBCaI>`| z8qa)b&dT-hMwQPjc~*$bCOu}SUYH2|tHeRxY$JB&`qSy}O`zkI9!;AXO%>jHuuB?t zVCM%Vl5WOxZ+Bw%dSOQ8h6w{XnMLzoMblm!U{!D?U5^hwF0OjvD44>A9>1*k^GY zPna#^Gr&XiT-g;GA7X_8yTaj`d;^J%$>&CTBw=!x1^H5Y2dh=KLTU4HSify_jYHf^ z?&$6?tUNakC2m%e!U>uDthZ0(&~_9~Pn^ef*e`(8H9Ujax`>`Jz0URD&*9$3)v?o> z_7IXo_v#=77mzRHNi63)QyI0Jj#0i zTM5&j2NQ*2u^N=kg4wMDWaSkb-0xmP)w|V5i#pGC+m#547ti9--}gXQQXe$=?2kr* z9$so0Vz-@(A*Xpq*y5`9RAG}c=)AULEgG#+JM1g*4V;Cx|J;duw==A0EF;@RDUfkX z4{YA^Z{Ro6u~Fy&zCP-3bX_;f$8O@PAO0l8o!4ml{aoN8!sxHr8Q^HwYc*V|M;FLQ z;RYie_&fRnME>bvs?3$p^Uzo}Dn*6-d{D@lAI`vyisTgMmTGllnL z?8zoGgfe*2yMWf3_~E<{emGMugjHfZ;bi+|JiK2I(|aWaItjBeCAN>Vxdr%!??WYj z&KK4UPlKng2SsW>`1x|$q8bZ#dDc{GEUUrq+|S#}bGydH(53m#=-B&(Q+76^ZkM!B z>-lJq4$x*wpWdeXijB~CMJcsfu$k@=?!hFzICks02hhK;v%10RKK*?lk&e+*1gilH zvTalhIp?7WhZmb8nZJY)e;q+otvaEI8;_;G{?W$ysm$QUb8OfMDO$mECeM6Mf!Q)2 z$yL!R%vQU{X9!y`qF4%|9=KCksV{U&O9pqKAdfCQ7iU?>&V#M(cCducMzQKS`0Tm@ z=$YIi_B_?g9h2$M1m3rC@E2A7ZiEsuKhj4JBv9P&2en_9iz}yoCa#Y^aN-F2XhSG}XXJBm6*Ixs!4Qt~E`TBR5={KBoEgdUda7+I zX}j(%Zso#gxIcKDs?KE~eUdV`KaGYMzO(o2{0%y(xtY38wI{hB#-LzKGLdqXMv>QA z*jzYB+T=a(&-ox6b+(r**W(?>rcqG$aTAWM{YIC)SE!lx*Pl!ICq(;R&+kE%I;#|Bk4 z&qEiTY|+z>N_NH?A^oY|Q0=_+8_73(2r9>>(GK-&S`=`dsu)e6nX6af&PB*3e71)C z$DycJVu8~>HnVyGvtaGwInaD;HRNv%A-TWC3T*WGP7f~@aeAyxEN}luwsw|6f$jxj zqIRDQBy{2VqhUnqpcSae^wOQ@%<ZdTs>OnsE=(?b59zYfrPg86z~R--K1xopkd0c4lbL11@5#Cl+}vWao!!Frr(d zIgg?|@N13(%Z&qgaSiX%UjK}#v)zMNPbgueQ70MDeJ&cwvuRq3C&T-1pUIK24~Z}$ zO>{9i8~jrud0xUgQF!}U=5&W$jp}ND#%U|>bT?m$Z63kg?;Z!7H$obB&k7_t8b)MX zP%)=2)=&G3;vr|xe#S5F0DF1y1QP0E2xF{Q5Y>;qXnNltPDCo!=(HY(g%S^Nqfmn+ zG&z#etDl6EY&H4Uts=Y=52xu7t+e9&d9rlw6uc>r0f|GNd}hrEOI#A!@SKy7L|REb z8!D2BO9#JjK~1%E65m((MP2>6*yAx;BBK>0#K74Wmx&*y7DF3p;JQm>EECJBDJwui zPPu5uhYup_8n<`E$mtjIO`8+3L^tQSf4sfgmV+3IvI@a@VeY#0%^? zXt_U%KHs)S#=Tcz#C%&LIQM=W==t-!;v9a@XwgE(`(34X zXL&=tlE3H@yAN!3hZ4#5UGQtX5qeYz`FypAv`<*We9uaxSN5*OaclMPgYs*}FsPPp z+VGJ!?$1H{K^uB4s}`cfzM;WFGx%sajz_Y`6YUxNxtF{N69c3~hXv_m(?BM8?@|VC zNg+*b(uGH(#-jgUIl;oc=SgqK85o|Si)s&(xVC3saPj*nEKu0ZzPMZs@78vcj*CZ# z;ffeKxX>2Qya>TSgC2ER|dLa&VvMWdA>2$Blnxg5%Lji&)- zhy&GxI&3+;rQpG)yOL3F* zUVe_L1&@?;@cF&nq+<-bl9=T|@vX$PCyG>bz zvrK=*K6-YA7OK|n5pFefX3kiKaHo^cfNT3n7aByuyVV4K{}nRXes4vAo}($ci3z3`?*M_ctR}GCp>TG$2*-KuC){D)4Q>n= zyl68HwXG#}KSr~468`WyT}UM*`q}yfM;J;|q#Y8S+{p9|sP<lBx$p(o0yV zv5U@^af6fcF|cFLLb7ci<)#^Fvs=5BP*x=!T`C1|cwGqR@#-?Uw~_bbJU+#7pMr2~ zLpIlyp$GQj>tM=7{^zpzINAO&hAYXpgyfg9Fe5aK(^R!W=9d6^bS}Z;@iB1EE1SM~ z_K?)h4#g8&#qothDjnNhK;^cH2?k2fVfl+6?%%`Jv=u$@aLAdOlA)kHUD;j(l1WPPS@y_ossvSH)1oBzR&5&dFHzbmlxbQ~)Pm#u_9lDyAZ#*nU!H)W?^ z_aWPtEy54QNM~-lg2f@1nLXhw?lv5S^hp?Z|Cl73as4uv<=M|Jx*H2;;|4a66#f#89vlO!HHyib5y2R$<;2V$ z2F&-jft-z$B$oQp0-Yy#eDO)rGJS|GUu6Z4Rn~(3`_Huguq{12T~(l&)Fb+GFO<#V zGX{rjr=oGddbo5NX^7q#oLqDRn+pc7kFXz5)qY^rFd-9HDJ?5|pIbl>#qY(qy*JLMGa$vR6aS84&3A}BR_ z0E0?nglp=AWLicaDSo5DXJs#dt!^{Xbh;;^vkYOQ!DZ2;kz(lc*%CD;cd}W6&(tF2 z1D(1>3Scf{WofsXtoCtZA3mFh*UVHvb8r%RKDa~dH5y@icaLzSd)L2!q7-_|M=%kalxq@pn2IE_roho-4RDjC zXmhfh$acgNe=!B9iX9CTrwHlAAakm&^Oxj>4&c}+^<4k2d*op*@0RwvOV^D(Lxt;N z!0OH=E=g|}Ca_YF}er2WRx)L|d(t(s+ z1=u0xLuN%h1IsU=w7g(~(147`iFaON`1U?J%h?i`5+$(Z9bnBiesr(eNm0nhS9~__ zAaTh($2m#J^7Su6SeL?ghm;M;!xVk!%Rd9|R=_H#yeH(_S)#C%cP-O6yy2NgT1073 zREubTn%|o?q@l!!>p;vRsN%~A7@EiPTE?A0;`S1CPVhZ8<9^chM4#GhxQQ+bzNogg zU6eX?6wROIN_W5GJ2E$Jpm4`@*r;}qWWH+R^rrqtLsphytBO5Lv}LIFq?NcRc`E(- zTpiWZ_MxMgJ0zTP6+J{Sj=ADC8z6*%#|nU2$PWbOM`klf!^#OtXp&%CS^nhlPi1$KNttV)l~S|eM-w)=`^tV)1{ z2Ns;4St0(od)-QVWGCERy#%}$TZ$HUg#ofGk-0n)oTGC<{(=m6`Z}_K_Paq%zMM61 zErPgzj@+R!?yS%$hOC#?4U(=?NoWx;j_q9g9;DUlxS(0%VXXNT z4A`?9zMhW5B}*E?K(B|WFG|Dguo`+IX#iF#+@U82*1;m@Xd2VTQ7KtZXqx?*vy!Z0 z8_L&Wf1?~sGR{TuZ{ftRZX)e}bA>INA0t{BV#-RLxy}tr93lnp8_C`=&v+kH1!}+c zfQYisWXfhY5}U1v()-JWM}7V(z?X+%Tb!pA4_88 zoiJ#$22OWcN%A7Y*uAqopqB5@GPYqP`@=r`9hXll>n_om6SJt$>>)Z&%|oxI2F|}H zmA1wi(YC=Wpt(F16RJ9x@yUKTv|oYR4;=uR*Z0BNeJ4&2G{xI*r&Ect_v~wo#Y)pQ zy32TkV1+r)KD{;yPpp_ku1Ck=@as6JYwol>lrKk&M>o^4dQ(tCE0tZ?-~rl;3YbN2 zo7sV#d$>ItbusgRBiVG|CVl$0kBnrhVBerGw_kS)h>Kt2Uc1CV(dtexQMLjP+3~Rb zuQhHrJ&2VopY7ji0z*L|_-KVSY^g5%~CPE0c#%M*gx)22aWT&ON4cevvHn$1*Z*oT?^@IAG@XbaPh zn$q(hoJCPrfJ<5+#FV`;FlP4vd+5tNE;D-pTQO8if83FPX)$3qK*iZJzUg3V8x4OR zT%bYc3h;=oBWM38g5(O8K!>_Knx(ige_C?!q1qa<`m7T+e^0_An#+Kd2&WgN!_Z+X z&tdOsBqNtE1`CCW>|j_VY?i;o*_!+lN~F#r%O>x`r=3IG11~M%t(yeH)V3;DEjz86G-Z4EOwY3D+zTZM*BbL!u>L)s`ePxECH#n zsD=Du50gmaqpXusrSMZsCYRrOffNO=BaX?ugU|jB7v$_@RjV*aOu`hY{O&mZj`@b( zsp*6ulT2=TUltAg?-E#@>c$tbIV^Up0cP$WqS(C?vV_mb-ZkgZ+EO3HN@^@QomMu7 z+QPk|Zc-K*PnPZ902ikaRMO}3RPjCZrTY%<<60w<|2LjzgDSx|nSSc^NfSp*^n!_x z)3~pWEPOTKyWZwYz#bNX;ZqILd@PkUI&uQi$F-60*)r_Cenxb-yu(V6-b){UIz!tu z0>C`k6D&0r!}1^3=y~m1#AH^q=TK3dSaf(N3%Jg7l$vmg7t=rZwE=w;>j zyw}T}SvV%Of~nu6fpb-|N$rN^)cWa6hMOG4<&JM8Hhiz`hWcn6aV3_{8_s02JtVN{ zp^Csr*_b>X_>ZZRD!_JruW-o51h??B5dGyPqG4kT5hqtiC$%PHVv-%Rz`zL{UIo(4 z!&(scjpy$em5?faUw2>UGgT|jghRXTQpNXXSU=_;3H+c4A~kMzf|9rvn;^>W^y z^?nnXKe3$lJ@p{3Ze_s>+0k&gpp^ENhQhY75|Aa{%ZyVDK?Q#AwIKTzv?z?mb>>Tiq{ zU7lRe8a=SaNOK3Ai$3&oQWiMKC{guco+UZ*j^zlyTV!V2B;5bs6l|0|4c;26^h{eW zMy?*iB%0Tf2|guk``Qh3r63A^U(H5sLr4`}x1^D%A^FJ(#>s18zV>q^m3hAFf;i~v6ag75u0$z_Rks53Ew z%um?L-`%#-HF9mjS=$@Pk0CjTnEj2~h-GnwL#Ij7;V#^xwg!Ap*pXm`O{iU*3v=b9 zxtW$htmV*rXqs>j|MW_*Z|k@7SHKlw-BU_;)%*eah}0%M}8o3|=5)G>|hB%4&YQ%NHX3#6JhAgG2_-kh> z>#U&#N!jMqv`a*$t+*g`TBuhuK24f*{+*Ax+YPY$WGOnfEW($3_CStjKGo03gN?Ui zaK9)OuNSU?Z<`5o#X-$#k5vU}Fn&Uu?-sGk%qN5W+j;obeG(LhO(GIK&qzsU1ASZ4 z2a0+I@OtP0Eq*Ugb~J2&(y8(EFyAS-Vpz%U7+wr+^JD3&`daM@-9L6ajPL9|p)s7bTU^Zf>YQci`Cyo~x`p&fUtkhK^Kp5^O4ymammYhQR6RJ=n=^Qjh5;8& zK=2E1j4WS=dmYxZ*<~kj+93^MujmExu#JtS<#0i`>*G~&z3B*?J+Dqe^Sz+>zl+?h zbG;C0pu%(pr*NZd>PS!RMS9ic0-5=A0@RIJg-L7P68mitOh=X>U8Lm&SH|+0t6fEw zXrP47z%ydMgwbas`3(G&b~^Tw3r5F3<|<4Vp`M`{YM;y!er>g&^S21_SX3AUpHqRz zMaif@ZKs(qw^|H5UHuX4yFD3QnHy**&{I4P*J9+-)aWfo{1gY^rx6%|)@yfz4gS z*LDHK?r|q_FN5&lwLErpl{Z)lKhYx|-?&REeWba3JU3!I0g022Jm0C7KPR@+v?t^7 z+Qx(Ayy8^+7;qDxYyQVA-Zu?ayon$+)B35&L?4(6a_Cv0P10qPIm@LIXcGO1GI=Ug z{KF_L`SFU5`x{Gw=rVBjygf{7D9g1(s$bm28~qF@bu{&@-R%m{>t;ER@N8F`$hVmWTs_CY&O8@ewfn8Z(B zhL%ciY4h(=YG6JMP1E?_DRWnp@;8C;!m}7Kbp&qs;|MRrkJ78xC5TdvMNRw<0>{7k zz=LW0-VY$T;N& z34e4}ba4Gjo?UN<3l}}4b>iX0CN0NJf) z$nu#E>iCFw)^ zF`)J-i)MVC&R}&Jx6*PR-0utn3*G7D!>(RtpT!0C>KJh>uMcHc-Iz}f?s4H9?gkSt z+h-7)bDJ(KQGg#S3q^l-t7F84GdR{{4mHdeQ=Op4qV!B5QR;n2V#Xmf&9cJ7<8oDn0viHa>}tBtEK) z)lIv3#AdjJOq7r&mLt09&ttJTw=owWE)$jAf3osrPehHU^XRZiDESm3&mF$YCr$bJ zn9u1x&M>5hliKAVJfRg!X8xOsN}JYz)Y+@_N2#@FRLFdM`#Y1_+w@7K;rfU)9h(Oe z&&9#ZnOETE>MAy60YkIjUbNgP8Be3VhgeT{S@`Rs1;<9{!6%L(JB^#6fHCSSD0DW>=nAtp6HDcLIvS^_*MBdP#=9K}Yf%nHmth52MD?|8ZOAFEeX$!)l zA+p*67SL&n)>x6CvUI@H7|>!c;06EAiI)iCN^+#;j>Y$Lk!0ot|RX8(zvp! zn>a*iiRON4p>I}yrgKIOlJ2}O&_pum%&8${S62udvt=(iEq<4kl~~PA6RB|_7aox( zCy&9R<^Hs|b~1Z~&+S`(al;pmdU$P1A^j0lj(Q&Iuv;sb^EC>>PmClh^?NH0wG`u% z1GzN!#3;eF-`V8qmn@KL5n;l)6YNpfeAwlfk1juxsp=)(t8LW}9!ttN$v1_t>h%bm z&&|UNm#2|I@88rV{RDiIyTqlmI#c%hB8)JVfm`2}!R!N}ujQNR3ws}CpsCsa?P(t^ePNpL#hQr3IO87vt47g8An7l53 zdT68?b(|UxhrB0a%{U8qqa4nAVe|RyaUqq8WFT+iNO+U%3KK8P1jWTs5M&&N;VUiS zz`jr#ecy~2aK3+{Xcf%mNQJ^+^3|h@fv)&&37WMY+xkEgmdRgGBL;Q1ay?xkhBZ2RLMDi%y}*t;^Kb_bb(RS_W;A+H~utLsnz{vts?)0?3T~ z7MxpEP0n4I4(azB$ej&VblP@1=;A^#D?pOF>u*L^PgN1l&X)m`{j)*#LKfDJc85$I zW7co4GLDIPfFq=*(pTaE%p_ujXIqP<;g19_>zk&Aur7dg(3--c*4&fBr2gH3si_ zW#n;x3{K9Jfbg5LH6a#Zv?%l?S?#-+%s)~_lO$@GjtUA<6LjIs8YyBS#=zT@*L<&J z0%R*aAtxjWzcac`K3qBpBzbgAt6vTEe*G6`r5~WZiUrgfa!{(Oh>G2lr#2NaIPZu* zdEdH(ZXPL#vrZnv&t8Qz^k^OJem$N$m9(4A-kb|vhKf`)$An#VQ~{0!{$;(=_F~G$ zEktW`7-{QCCxt%4qMqreNY>qrRyPWEvO#^p82l=R)g?Ko+U+Knn*jn7@|Aq$6 z6LB)aza%5Qm1wSrVD4TfmP!?t*uZl%&+a)xRM(5w_@zWynR`l-D5Ypfl9wTms^#$d zicpjaGa@4Y(J;BC2uqq}(7bOn99-grj~y~W%V`X%9OOB$<9BnNOS5U@J~0dmRfn5* zb4bA_1F%opLqq4A;M4DasaeZK{4FeDve~t??bv0Eu}i`f?NN~ZBoJ|(B-nJ!fhj3V zIJuj-qFbk*Lab_yNcT}89Y10`F1Y4J(s&=X+NXcQ*v>*M{Tqx0Gs4M=e-?b+-J9n# z?BOo_w-79cE@PImEY7Vn1bN=K`JkC45z#$-FPBG;s{}#s3th;s9D`8>A(*uI1pT}> z6Rv&pNAu!^sGRFVKOGKbM^?@x68oZnW%6nEyg$Suzm*J}ZxA({4&~%u8^fl=RJv)3 z8kp}YVfB6XK&5{<%9$*py7`IRJMk#AzgYqjZ$lt-=4LwZRu^TR#6aQSdK{OgO?9{Z z$KDT#!vn3uY)r#l7B+uiZ?DTHerDZt*?JLk!+9_0%@U^u&+pP4;TI-1|0LJpb)WYD zoPcT9!(snAH?rDwH|ucu2bDZ}2vhO`S-5atsFq#@GaS00TXYO>PskOmy&l25m>iCb z7k@6cnG3!eZK{r!eoBs=l!JAt4a}5-A@Jv;KQyn=#Nb*B;_CZ_#Lw5LS@^J(n%|!Z zNiX_E@2j0f7E0&X$qgR+L1J?z(ZU~| zBJ!KEb2nSS-^g${^`RZSZZr{Vt-o~p6Jz%FKLftXYzYdrL9F3lA0*m7pf+tkR(OjG zHuzmb^-~ME4uyrNXr_bL+d4#>0@vfayO}7p{TF9zRtkOZCqc{-BkuJx9U=qwgqvpF z;v20v~Dq`=2@!im%RwG;K?87_Usnj(NrNe@~JXHrcq|AcW5F-wCBPX!%SQFNr-MPXFAv9h+<+PJ_QjO~1`eBpDFU z5l3{sjwO0-6TsJam{EH4kS4F*MusPb@SRa#@ct#n9mw~@f2-cJYTfsEk47z38xtT} zxylSXKl|`=4NvH4D?^3SN;rM?ljYJC%h{_3^l zMw;Em6$|{apfv_EtlrVda|%&aSs7Nu+~;J(2k1V%S#ViO31`HGvf|3KKt=I9JK|Ox zPVg+}B4ct$c2l!3W_cC!ma8N&p|8l(gE4Sv;u`vT%RBh6w3j@Z(GO~IV`#AdaV&~` zOO`Fu!?RzVFlMqE?_~*waNj>1-P6r}%O2)jo?L(xSu+@wZ%?RBb|uR1HRHQj$Ep13 z>Ch6`fDhN-Vu$sO*$0nP;J2h5X!bjj=LamIvu7Ht=DU^|@27zC4F3ObyCXb|iNQEi z2RNG&NMS$U{}zP7TC>S)YxqMNtD7$h9Tkq7Kb$}ad@8b?Wke(gbgfr#*=HCS^a3LA9j)1(h~R8Mk5`<4 zc116&m@Hjmm}drNn{)BL(q1y?@sNFUaut4`$@7qnUJ2Wt*|Ij@n^5CBKPQ*{#oW@= zhwq#&|Nj3@cMF@Dmf}Lr*2s(RJVb+mh7hftOhnESEPH{UX{~;zg@X7lrb@Yxo3w6# zEH;=8uA^Ub|5=5SYGy36x%M_{(gXBYlA@qeeXx3)|B%RT-U=>f#7La+z7iU5%F=0{ zIhtqt1h)3{qwIzS{H*OV)hy(#%>{D!^0X8izv>;Ex1*d?|IDIicQ?b4GCxrHa~a;) zn*cYh4o;7~&7Brgh5OrXaFgX7M1x#j##S7Vw%3<|KI(v^8$FK3py*m)rmxf#B%teb3 z5quh4fFZ_naUJhxG)g;3pGWJFyp&9`$b`koh6+%7E(DzS=-}){{>*q*0nx38GJ75p zwE`*FHFSfWd3F-}~W-?@ut_o$X;%?mqI^>H+o|{Gq8H??j!R z-^tS*)u>;*hrnVRjQu);&p=$GA1xkpUrZPb`)~}mx75&wOspv9lQmwwG=&z8a==Wr zccM|(@1yCpoAg(&9K1EF#jL;WRDQlaxsvC}Mcf^Qee2ukggzI7i63$CLo3kx?-})C zw6XEw zK6v{|3dmm>A_;E};pm);q%%(o-z5a0&8ZZ=PuU1I;*aS8jh%R+<2=og&VVUPLM`pm zKhW3!WBho#f?Xi1Ot)z6r4OxUQs2_eMCaIZ+GO<$hi)#!;~7)%P__wtQVzgP|Bk_3 zJuT4Tc~&czE<>A*k#t-AB#`?ZfitFcGAmjI^mfZLnjbGiTs+07`L<3hEJ)#SW zX;S?v^E{6wq9Vyu3gvyzUMOWK5>lFI7E1G=={w&);BvVxXYak%^V~Nz*l0+sw985I za2mc+s$g#)G$tJhE%c7P0tvJ=;8vXrf*Jk%e&ygO%RiErnf>}%RNJkFUTzkm>fjX2OnCLt+_^7)lBMof`U!*7S7`}&+rOyfTv-6o^973 za2EtoEYu9DTO8tKN&?}6**>Hr!Z23*{>&^(z zY&4mR7JaiW%1t;i~#!MVSG6Y)?*{K?-@ zC+ZmSyJ$;%9DIfN%$tP8x-Ve#u5?rr)5g7`yJ@A+6Y7RP)a3Fr2KO^b^!9@`dVUMf zm|pgpPJ6G8@5TJFD|8{Z^x-_v>NpMa`WX2B-Wt&CEb4C#X8ZZKn_pxWyY$O#a5s7Y z*;IvR+E0QA<$WMMbuTZNehoeI4}fF60eaqlgT?{@w$^o|!Rye&+hT0s?l`m-OW{bn7`9EVd=&%hdgQOwlPLy5Emq81@Wdc;!jaatMq z`X&mze9n_QmJ;w~@mV_TwGU5C9#=cxOPj0`=Al~fEF!{63B?{jS8Y<%6uZ{dc-hKZbp0UBzy&E93^6@8G7NMNp?U2RZi_ zxWQ``CY*W7_k5RPY2`vR?Uw>T^9rj1#%pHMrV+2`wa2v+$QlP$h`QMhOhGb8RZ z*Yt8cmH2dxq|WxiD+zM&ykj1Cf0x1B(^mw`zB^!r*%!XQ`I%f^VF_QE6)5d*LgE)c z;Qme@q4rbfQ>{nsG)Vm-JlZo0_PeUEAD=kWGtQ!x=bN<7&!>^yffxb^%ehdT@De0woG@cBFq zyQq#uueAA@wHwBXjHaK}bcpLnDEW8NjeLsE2mir~bZ2H1R5s*rvTMCy#4(xlZXTj1 zY=((fRfy1cdLjL~Y9g#S5=~R=lIh2eNSbm;AFm&r!KKN#!yzjpdhbIXE;K5wX*v3x zKC|^f@tawsaj`U6J|%}dF-&Initgu{Q|;N-6{kV%mo02&JmBDBYnXh@0mYKEfs}}$ zUhf~`zE*-;-7^FA#LlDqwjI>AgU_4RWzh*6k8>A)-^0e zI4jJB`b=4Y0|n7w5~f?5pkzk&XuYEw)b9&px4a>fU*wRlWmzD%Yog`mFS20Z9FN~F zEd%rT5|BvmW?o%Y#o}Hws&>qQ_8LtfrStu8O+z8+G>-wZW94+$=W&)R83`H)vM7~j z0uzUM_Qh~J_-S{N19tOh!eIlf8NZBswnQ291J7aLC^x*XnF@vr__yVZWn`h9JbWAZ zhM8XVcslnynfpNsUY2E%zV-Dre_Ge$`t|F`;krY_usTMtv;Q>r^_V_x(@J9lL_FY@ zODx@S*Von=HFeWFhx(8_zac zCx_c5#^d3HcNXSM8(vfNBd;DAgH=QWYFM8|*JyX}%;EXZ0V2@Cw9w!ptSG4mm)PkLT-$`MQ#tlD|K8HljfV17Rb<&$XJYY}d5tDIZ+J~lUI-;`ceK%$gNxu@{x3E~JPG&3%0Svp0DLeLl>bEY z4zx0MaiJl`^L(FYE1Cpf0y>DsjAABjr7W@XIYZ|1xfE0DdTz>{$@H!MIW~$v=T_(r z(_v?8$X<4jTjQKbJo8tBLAWuK`{_G=Z8n6Fuzl!r;v5!8Cx8d=^R*P@UdY}Nl*Qom!(V%Tzo1B7{>{a%vfa44`ydAP z1j7}PYpA{{9yuoaavA!Iczx?@SyHCU;T^kxTT}fBw|*tsv3{}-gi zKNxPvNWz9wbBL)olGhiCab=@E-VdySGc!!_QM(z|If&M(F5OGjRQX~@f)n@Sp$8t# zjUpy01)S41Iau%To|xX{yJbXz#-8{|PK!~x!!Mooeb$1UTLu{5bD4~sRfQ{h-(XSv z2st`IoV-XFOE*bG;^gm{tZu|@a$&z2>$FQ71J;iR8Q(tgMRP3JNv%N($7tcN=tXz(gE4}Qk&+!_Z9 z|M4H;h%|w3e;Q^ws?xDV9dyZ>G0ecFYJ$s;F`YKaFyCjGSmY?;1JSvVkWmX$r6g#P z#x#7nR1{WkwgjbAFZ?-eC09^Sn2%GsI6dtQ@?7Ky>}tvdvk(tDkbi@2PM0E|-aewX z_u8m^+hdZt{MNjrUmU6U>H>v>wlMBqENHC0NHub*s6pZjQYUqmPF%GGavv#^wvD-T zrSdTM*m(wao44TUTj9`sXfm!I^vBN%%{VUUr$w#*Ao+QTXIe;IVvpq+qsp!um>6Qr zMLax4Pc56tN-CeH1?R6(t3C?3)05HnLoM02?H>(Z=*HadScFk7Ug&7-1#cJKKvgL{ z7nfQ$LVcTlFclSvD8vF+tv>fZa3*{^nts5RJ= zTh8r7D}s`{dpqG|^+qa}DImY>o>2Rs{p7;pWYQhuMp9)pQMqoM&_ZQAhVI#c1I$7! zG1vpaZ$8o4!5vJ-^I=LYwb9G6iA@RmM0P!wA>Fyru;9l-I`MfGY+fRXhqVXhE0xNUHi+j$w zL2dOzqP<%L@-5?F)RGF=+_(gw$^s^O#Dd!b)!JDFMtHEN8Q%=4fkhF3(_%TMGS!TV zoi8S9uLKd9#pQzYVOiwJuo!l|Qip$PCe+enD(m0dOl%}mDAYv|kpVAgIi8AXKC$#c zn;%rl4$%eC5>R#Q3h5sXgH6#xv=!pXk*{-b+5t0C9w$%7w@7h@AFb(uG5p(TcLXCl zEKY?k{2ublbab^e6m%_WV(KTQkfAZbkh?SoW@wxRsi1J~;*(i;`9L1H{PBmS;q?|3 zO0T%eO~EAObqM?{7R5AG3AV7ohySj`aAsGUaItB;MY(+-N{4p}vI@r4zIC`tfA{#I zcbzYIM@_(m)>=Tbq%B`R@`Y_u8KiI5Ht^rk$jvh|U;^EBG1jDv)AP`Q;?iqS;@yk4 z4{k>GdNuw%RYd-qy$lt8DMOk;D5KiNXL@9vp&-Kq=U#AQkEj7r317W0e@-kSfZ%A@vrEs;JJ&a2FKy9sT=s(9g;*hw5yk1j>m6OE< z0Sf=&d;#x3!4m8jmXZC1-{@$c6-iUO zw^R#67U+WQ$}G6!*~X5n?M2;Hk??r)as0$3q0I1nGS6i`Hk#kSrL`fTUM}5#CySKVf1#c`q{>dS^|kb-nXJSh9#` znUxxne48tvv1l^RUfoNN`J7=~Y<;0NM_|2a*bmlkmwRFu&A&0qZGD+|6}Dv%et4OJ#y!>xHS^p<)) zwLeH;y7@%xv7bRc1uNq$%dfQ9h7bvd5N6w+hr}^$7s>qok|ZcsVgsWF2PA^2aoI)o zuR<_X@~*SI_ZgU96h-|Uc7UpR1MB(x1FN9tUZH)&Gx9QQ8fw2gQq%vf zlgg+arb~=F*q6u0(@FR1IFE^sNXL*kh@Meo*{;196u%#I9!o*OAx)}u>7~FZ?61Y= zYCGn_`P-1In+rOx9?pj9bt9n|^6o3e zZSRSKG+H<;Os9g}PSjb{L=`R-Fvk{D;rnO7+_`J2==ouqz#}<{==Wt)|0olBEZ9Ie z`05?mpW^`ghAkOcXBQ}$nL$hDn&7*=Wym<%lJu@k&>dNen=1tt8I$rbtUg&7+Vhg6 z@3e!fQ$ul8{~Q{0QkA_CCxfS!r^4E!0d)AGC`}cfLNCoSi^A*PSV!!jY33=Mpps3K zm3P4L$>wy1qy$u!w{tg^_Oqi!uHwKHJ#15$3H7br7#7ceXOw>tx3QxwFFeU3la4$f zbwB2it9w79<9ScE!O9SmYTn}ChA6UP@EKiQphdQ>N+2ecmc&tZAHB0y3uf?n$8YXZ zsIl~4jYxDpe;-NZ`sRjG@uS~r@{Z{e$33E!r&hER^?{i?jih2)mXIKTxvv+sSb1|9UOVb9@22Ul)Rl>_ck* z@G}W2oB+edoowx0O6(Ld{3Z5brMu zWZ?`w7}q*T#yc01B)L!~c(@+i4y3``mv3;Lqh#%Co?TRN(T&;^h(YsK1ukaFA<#p0 z80nGbA}f_?KHUz{j#-?@iZ-0Y1pANXz-?orM07BW$YEsrf4gQ|96hG zN%@ib5uV3=ycTc$-0JICi=r$xK=ccJ{}}ieJXKZ6FU&s}AJ< zaqPM{7~MC7f+mqKdIHs=!>&xZ|DzFJcTc;KTi|3PQA&_ z6ODkTw!P32z~51mJ)lu@A&0e*Sam9b%(;=wEt}ZSJiBlfroPJ;zAeol=EoT@5gcUx zPBFvuUVzPNVp!C4-a_WH9(>NVBuiyd$qhM#wbumHdafw^X+K9LcAh}9r_nHfo+HlQ z@r`}=z?TFZjN|IoPvJ88j9+I?ARhb3GgGW*gR-PDD(!0~&((k7dY*amX+i{w{`$jy zRqz*jJa@ybAH892Yl`r0l?-Rue+roa-qcZ;0(T}n;a(aYqwdLy#9#j~@DfrO;yW|F zt^4VNEET9x;rq8TqtR^-i?tc!m|Ux3x+i2STuxR7o9avq`;v<1KC}n|Pe@^s;z{8r z&FQ55%{i*!R>bYvaE+c!?n;ZStpRV=8O4L zGilj#JrsMTXz8q}0mU!7$;_bb;Bpo*XVM$G*nc+OZ$8ZDdsWFHpJBMSV-}Zm3&73@f?x(FjH*anu;wCJ;}ALemI_lu`@-7$+m&9sLM=-XFG1g;ZzO=W0USv!)`6Q%*7Yuv;yG9S5@-M@j59VVlY&_f=;m4 zC9}n4SfijpF1q9tWcc4C_hU7%?MoS(9QBHt@6JJ4gEer|YZ6f`kHm?*OV*-gG0fHp z7L*^9!e^Fhn6zmnta?-iKKoCQRkDhtXD>^NN`A3Z+f^Y@IiASRm;}>4P2d^bYhdt} z0-<|tv19ilVNGKttLV@`OfGstVo*3NUiy;tUN@Q>XT`w4h9vNbVPRx_7`x$za&3p{ zLb~MPG8+By44oHc$qq!W!j&9o&UK5V^YvGI64`*#lSCE2OKCbb=gL)%jL_)#m9 zS}b9?Z^~R^G-d|XFyTAZtxJgW?rd6g?Iic7cPgv%rGS>lj)LFG+OV+ZHTwGT{n05> zWT$l>85lT6E}qL}qwk#ICZ|%t&A4`wskI7j|2aW(G)~j)qq@o9J^`(i3Sn1^Ph`$6 zy-Yk8P9&W^73|Je4qWt~Xjsm(sK!@BqFw{PKeL#MtrHXB`NJsYXvbJmFs6lW<~w^K z((9pbxCym0#Mx)PCO9x+A@u08=e;b!H0$dIj6e4ZZp|G7Y4haqxzuvz=9#B-3B*Cc zR*~8|^0um;Y0 zv*rER*kD6u)lcWM6-|Uc*XeT2cy`vP36{S!Hwdaz{Nd>*O}Js8hj+>|;Q8VC=vgLh zIiczwJ>N6|eXO*J?VKXg_>a&HHhNInR}bQoe~~Q@7T}CtK3B=-1U`iPCKr^qz|+=Ro+EW*R$L znfK-{$5RoJoT{Teyk=9tcKl7D!@Ws0&SV?bZ0-a19zEbA-uUtP6;f9DmhP|iXD8?1 zC;EHLh*?HGG`3%aHFtc_!*3C7dn6A|^C#5y3GHd!^m@Er{jBET&AFiEMgA>r2lkR(jMAS^jAJ*0PVqgiy>UM}uChWEnRr-rU7tAFzNhLxWl1I+-XJs%&X(G8`z^fXY0nDK3KNT zpS(Ig0i5_egP@e}w#((R=bJRK`n?u;TCEOmmE7Tuz!+rR??SxsXt12>kH$T>=t;9( zko#p7lcSzSZVk8K4RcwPnL*&}@i+M69nXo+smAGs_k@?mCz1-=aQZj*IQ_o7nBGyJ z2ou?E6qL#^XC6NwKgT=6qb22f%W+G<4bRef{o)bKtz^PvFp-O!d=?eFHxMQmezZZu>>!>o0 z9$JHar|t-5McZIFYmV~OThT~03buMLBD&YJNK9KdX77DQb?x_)Xypvz_rnm)ADPmN zt0j3>>R6H`W=|Ki`3RmLY=bfEY`E|@7gD!wqHAX^M@8){^mKA3N#S0w2GYoUzN=(P z)ybAF0h@G9j!Zrs2T|qknXm8W!}6)8_^f;jG5hbA(A?Dnx9n2Ff_(vK;r&+FnI6n` z{Z>H5v5pvL{}!q-f&^T0!FdNHAl3IPn-f-yLFrwzW{o|w>7N)5i8s*NBnfnujE6^W zACNKrAK_JVA5#{Pj5i<>(m$TSI|gm!juhaZ_Zkou9timc3*eu>F&XnH39qg@Pq{=j z(ih{XQB7jXTVu+H=;5*8qD2RgRoZ0 zvWoZijyo7i57&O6UVnYzmzWFeT+;{7t*3xO<^)W%(PvltWHbL2uz1v45vk0RG2nQ9nC`bxYC04Xp~C_m)ti zQMZ)dpKyZKzk5Lb_Dq3=Po$xG%Rh?=m!s(K=R>ILWdr%Tad1O#46do2Kop08SobM1 z-@esCA>D_M9=|4v4Mw0XxY#JcuM`3Zj4mxQM+JLueIGk7&>fL0bnvli(m$&-yU;EP2%m2_W? zw*x!j%k;x!k*y|Ow3+wZ#MaPyM*+1zZI7exePMOql?q~a|0MmltVu%FDbl4Gi7!@H zQ-`Z_=-`h?GLQ_I^SIQaW8+xZdq|sIFhtp@#v8RGJH#(q<;*D^9FC(+T1(LL%kp5t(`Sm>Zf5y;9Rg-UQZXtWRutnJPZ7` z1+AMrnadA4fw?O4N$m79@Xq}u2})|muFiFIW{U~6Ds{wG!)4e+b_nXCMyU0j{dCiI zCARrZFs@ACXS#jQ$oF|4Xw=;h;vWzQPuEh$EaEmO{cym~wFAT#+PT80-MDi6WR&Nr zDXvc=F+su;c{G|Ub&3Fa0EQ7OxpX2zA% zvvz4DV8t?U&@us4rx$`lb$J4`;gjgffa*`QYNJ?{xVgBaqpb z%=xs>!5PhOIW-?KOULzBxSpsTq=_4gpPc5B_do8MAD(}PEL*({9TLLeL(^2IcKMrm zOZ3ga;nNCSm0oADa4>_7UVV>0tCqlS_c4~<#!6^6U!Jr2Zw~r5s&RYmZqmeNT|w=E z6)56lNQFF$a*^LRmb1UbJSn_Mio@$^rQHnZHE9s+SZ<8@s|W_0x6#vHiDb^2VrqN| z=z4t<%N2tk>6-WtWUE&^E|vd5*6#d7-LzCJmX5RHmWm#yBN`|9*|j`cYb28Cg?B;W z{(UmFs+V0~-ayhkPl51*4!Vxp$@IUlz<{f+aO^-VX`7{oyXs$%q2+5SJ5d6smz$E! z7avd&Mw-kI`T&vtmEf3V>*xx{3aqWrrdZ%h%lcLmy%jyQ>Ubz)(zYBn{=P(yik#(+ zK4Qpx+YDT}$AR6{T16$|4p^kEx=%(WkE6+pB&Z;9Hf|S8<9Ua1D8su!f>~2~N&iA4FydzpnRD%-H0TnxUgGec z#7pM(-<9x?$t8)?l8{Kn3gNyv`}f=+D#cDfk!fS#v&KQ7Mwzh42&!^q$|*l>~!iULBsJ>zK3#oL>J2Zc}|w(ldyh+;)|%($avj6XmH&ga$lV+ReJmi50w6oXjS#@W$=i zBtYYOFCMGz=JpOxrX`CvLE^}3?wM5w`7tk-$<{v2Ek5B1hpW{d$v0M&K1PAs~-FnW}~BT@!Fgj*_L^(`GXC`#N4zHpi@Y zzi3h78SZFc9x0lx3n!Ff>ClIZIK2yku=$* zNlE&&bbPdAEO;DAWa8sk9OCD*y7uew?nf;~WPd(;{HPz^&|L+sf6O@1!js&xzfXnw zgQfJSxGTS-PG`$b&E}e?eg(a{1yug&1;W+J;gdfH7z;mjd^YO{84Oc|lezoMr=3uQ z?AQF?{lYGMI`%1jyY4MFc<&O?ki3Vn^L{bk#sy&8f+jNNi3n&hi*Z$`CcE^X4!2^j z7_JL2!=*JN1XF6P;i;1=8XkVZv-SK@Dsw`82$miOy_eB`3GX6mh1bDKCnnIx1 zCz7a(Pp7XWGw^mr<5bA>$_EDLkvx~f4o1P#t!0$si>8e$Vm3%l>&h@UAFvo0LozYJm@EFD{0 zv{$CKzUd#i=-SGK%IU!?ja=La$Aw~_U9fwB8cf?AOC{rq@%-T=PVM*#I`7&w8lDvj za;q9>OVK!NACMj4saJg~2fuIF!;0 zJEHcWZ3_$c)1`2@iD&D*(gDqV!Q8A(S-j(Uh*)}Fg*ua0w2kNODDAXj!W_nNjz>+f zaBCZ`S+avT?-Rl0t5eCa%26EsM1Z$;Wnz!GHwYV#psqp!9xC}qE*mRiz=cpczo3vy z+!sz}TPxMx=s@ye$#Hi5lCh-eKTCGgzH)T%eS*#>!*G20G$!Em3>4|vk+_1>!5=cgSjnGi19HVT;bE>7mpt<}k zDO%RYT))&sxfxodZgd(H?(?h;y|WOvUYmtIfw5FvT?Kur)IcPvhE}M`!|FGRUl@j5SjP;6}z)AkGq}} z&iVi0*pq9w3X>1brt;^9NL|z!dZxA*A_8w(j8Aw+a!RYokdrobZ||Y^f8}C&Ul)^B zcY;Vc3xtU~R7TsbW79< zzi+ot*j_+h9IvvFk$S;iX-uI3&tH&(`aGw`ijc0P65Kbbg)W_yfGuK?%-|erGI`D@ z)GU8P1SfV2zxQ7v8z#EL!Xw97!Ri<+kJt#~=1k|5OVwC^S5wPbK_bAcPA7Aw2I6b^ zc4Gf7pPeorPv>Gi>uG+AHAXi0ra88D%WW^%OjEDbUkgZa~Qm~ppn6HP7x{uXDG zl?i*``^AHJTKP8Fzbz8q{>a5mHX?Ywc|12+sh=(7ouuAVuhBED_h`0hyU-TIAov5n zhZw45_3nOvNrQ=4CqByZ@1a5JxAijT&9i-Yp&!<*k;k(}YjM490Xe5_hr;D;NCVar ziL?y>N=0f$3^V~Z%IRiO(|yonFB+Aev&S?M{Jr&4JupAyy`A(iO+3gFn*I2 z*cjzv%8eWvyS<%MNUbF&B_Fcc+v|v75#M`m@W9TA=45lEDjDEA)pmb%arvYJ)c16# zQ2OR2SfVM3c6U0caCIcOehNT?m09?7WfG^$^Bo_RrhuOI6nJkM%(QPv0UG!VZ9WG; z>g;j2-o>Ad2wBP%`Iic(T4mrDg-Ud`m`pnB7T~O_E~J7#m-eJ~Q1SWO&|2LF&E{`L zwf-pHWidp)uS?=N0RcozLzg5$6M5|4fXYuQ$hzN$P^mSG9s5Ut?%wx=F21*hOj=_? zqN-RXQznqsil*|sCm-y)X=2gy@FDwRzdC3(uK>F<-8654Hg12rkW*fL0!{^ZfciNR zcGLq+PIcTS*u{<`idla2F?WIFkJ-RVuW=x4u3w4Ge;OdVvfEt!YBcyY+fj){zu=DS z4wznX8_U9@p>^H}#$8#)XSXInVjuw`n+grX;dty}FWYnWgfL}yBmKQ77#CjKi`S^J zaOLJEA~v!Gb=r=BL|qVloY5m3a_Avm$?|m`8Is{7{!Kt zxJVScTyRE~DEzId#Hrsc;mu9t4&4o*(y`6Z-dBtYdAn$&YB>2)o=Gga0`Xmx1BuwZ z0TcJ_A!|$45)X~pC^7Jkwc!5`%uY=tdHDaX*;`tt$D-Pe$*47w%`mkWNN40)^5l;` z4%7rQcWz(dH` z;L7NCb;Di5liZjs8}R0@hvdua?HIq#6ypl+l9fsg&=g`v3QrW^!|hKvH}QA0`29S% ztY40W)}~DN;W^kG%A<-Zi%4_q9c0&c3u_xysQSXuwbM>)g!@LR?6HnUf!f6gh<)Zl zwltr`q;JDGUR@7O{7zE|uSjswEuf0J8o084x3IzYFG)(9Nvt+Jg{t&ay0S3{{>w|@ zeY;cOF|&in|4oKZU78Tb??`R<+;y7nDN=Z91(#8#A{=hqhRd!eqe1Ib^6v8JTGH!4 zGu5}!ugk7+R-ccPD{pR-_9Q1%$>FHeg}bEBmA~%>-XtIAwQ&1HrqGOMbIFaCWa40{ ziR=H0k&Xu+xWX@prcpcb?bu7~h3T%$fv^j_8?pqXsSiq=&t$|#GTGw4I@sF`u=n#9 zu#EafReyaJrk;rd`eGZ-9={h)*hh1^VL?zo_YU3m+?PH1{vB1k9m9T!)Wh5 z(Vi+u8$TpGW?0OmA5UlK==C7&JO>IG4RZWyG0xigj)v!+XUz?C;5BC^6rHySs^-cO z#mglS!^L3!#$|ALLJU!MH)ihD-zJ&CPGwjcP$XW@sKA7AFvMZ4b;l}1VQ3Q7m*XEF3i)33{_GliV>|aNW`{XiDe1*LHz8RYr`AtTBK9kS0FSJMpB{ zAL6Jg!TZ3T(N7D~A$UqGQF8Mz|M5G49NK=7dAaE+QMn=ozbA>&X?ydC+tI`LQAYwk zU%x~98x(O(??De(K&*%ag8N3p2)zf=7LR5OQlQHH@>wZL>`A z@TqqC?Qjx=IjyD%)8@cPzC852%HZnqA~4b`9rb=Y;`FhziTGbnuwQ8lyN4%Pp7^WI z=42*vIs>m^($u@uwU?ukw-1xX`<=LNj|S^|L!mai>oyj=2!)3qeo@J~aJ(6C8g_hH z1-6Gr)dsbfpbAb$IaNZ|fOH)vK4W_T%XO+q@zv$@ zuZBIIQc@=;QvOizQPjj@!4w2q%jyWCrxmdlOw{{W1up|C+@%J-n1bgo_?5aghf{tkXftO;z%Ax|19qN81l^}%ypJ`F?J+upIj-Mn zMUo^tC$0qMA1M&J1)pFK%$-8Ei`$~ns)xj5z#0brlc&xLPBYC{ZAhSW5PwGR#4hn& z!ub{X`0bm6t+D-`zW8$NOL8!=~BLl@QH$O4_Ma4S{komcBPN zphYCV-~?t|ohzy(4b6PUMN1 zI0-P(qFY-hQRT-6=}m7A@N(Hk4E5Q7m)2aQ0k$Tz;vOY2ybmiwgJJI(YB6wYLalns zOM2qZ28`sf2~Fw;@VTot*RNR3arOCR)RS`Z`;jevPX5Zinc>tPT{jEd%pLG| z;uN@Hagcari;(aO$+*mD32Pgn#LczOOJwX994Cfarx-b86RwBvn3;?2z=p z6CIXhzv~>B(=rE-?z6^srhG2b>m^q#o{W#43h<0b0ygD;Cnq{HA!tE3IXw0-j9f~9 zP=2QsJGcYxElwozPj8Sjw}v^thJyk>!p=(JikZ!Y?#1lSEi7&`I+4PiZ4)B6iJTvx}w^*YjpdGRAQ<#Oh487v1UulXiMBl zHg4%=(%@*1|03ovhxL!Ktpk*IWe1?!mvcM;2!xJ^pB}39+_RhojU6VKQu~cw>&`gni>*5GKD><{*>J5QG*+oMsepVu3H#{AsL(b zkxW0QBWN6DL+VC#;)e_4QMuw4SlxZgz0&ukd0H2Fj`aYw3edq%hl|*<-yIy2_JFx% zGz%CZf0iYc(X_1Tbmqn|Sbwhwc9kU)Q|=ADpkqvKy8OpPne}n+-_C`;Mx=cHg!J<+ z7OlDY{M-Ejja5^DCzqbncN4C%t71FI`#?zy9w%k_OgWU^6j%x0PKt!DAKTcPjB;9e z@F33u0XV+M0B0|s2o)lY7^jc9mi>~4u|M2FV&6op)ZY#J(u{EC zYe(GebdDRdZWX-k3dap{OF%0?kJ|r_qVw>_>V4yQ$R-(;h^(T_h~hc-B@LBGDh)&` z4VAu4E7>a<83~~zB9c*fo^xMlQe?EFq=__Cil~0)_Yd&AUYzGX_jP?f@Atancyg0~ z_KI(&yQ?V39ZSNlb*tdQE^ll(y^D%WtRhp~|B#!SK4=r7k7>ps_>dJ~WX==DL~$~B z^Y+2ET5V_>eFTl;Zc?wvW|~$yp3LX4NpBmc4V5P6 zRef-;ST0@ks*pUt!a~q3YsjtQ80jIhBsa~+G9cCp9ng-RH!p@wE4cpgz%)?X;X%YS zWb0V_RhThN8QY9)K;(QZS|2ro<&uY(9!)pApD)GxWYECm3o*FoxGA0V`Vjoy_Kkk7 zRj&K^KACabI|F<_oFEtC*MUTa5n4VHq4U2_Wq#c}#Ip{c0E_tX6!qRv8OJJ=>y5$K z+m)C;c7@bFor2|CMDf~cz(`~9I)&R4sLfFys$zQ+!K2TrvVVXo&Adoex|GP8v@2*9 zAO{>L1Bc5ATmSkzDrTjD&5ZS=-8}`~OWtP9Le8^q)I9Kwyced}1Tk%{tH_tHhUDSt zEjVHIHSF>oVh*R7(_#))6|$G*sM6!Pd1n&T*e5Xub#3A8$yo4lx8}OFrreHe99$6L z-a&<=x!G{7z}!uV!eI$4-5)`hR5lW$O3pVUGO14F&lM(r{Z@D)utAX6fQD8#Ns&h^ zfp>h&^3TVFC`sDk5sG7NH4yqfm>lJa&{Q`IM)Q;hFHh(U_Z^Z6@@GY`Ot-RT7RPfO zekg=Js^0iSw@k3#;|}x5XD{-a?Vwj!6`!X2LB(sE+KV$rXaIM&cF6lceH_!+<&9hD zmxCijx?l#RaI?2{5=lJE))^3;Z3m7dmuV`iv7Du!jse5ViIwg@2r%7%R6Y@pZ_5N4 ztcQ0SBk-Q>G5AsboSCLoPOB|Ksl)yajH&P`HvG8?6>Eydxqh4%eYrKgYBw1sYR7_Q z^jt97{+yd-Yw(0lYT$3}OkVy|eY7w%fSCIsh&k(-_ocU~+1?0x^_(7DGd6^ouh!B7 zW{a6U{69?H$Ej@7^D+$k7YSxciu6d>3Mf+yru#~-&}@@qJja=8kQ!_blY`}`yv48_$+3^soC&_ z-^AF>E9FIequ}Pe0(#b76u6xH0m7myiG6ASX*v7{o()*jzq>nW@4>TJGZC4cUu7xH zNTfcmIhVZi3)=WP59R#09pCd7(Q^ zVAAe*njoD8yG}i(_s@Bv-@0u${k0y6%?N>XpP9_C>UWx%)5xe@dw~fa4J5d1JhuHQ zU>5dpKX_~cdt%cJvMWfFzL)SO<34xL0I?Rh`*4H=iEhL24Mud~+UMlHRVR@jxC#;+ ziM_~fGvRxRV9@$Nw7MdKusMbKx?v%V3of7~dn}>9WIWbxN~C3D?id^JkN4R`KaCTlOS)Iapp&jFysoO4CJuxrh`UC0Uv^R;o`Pf3_MaR`0@IQi!UR}Ymu&F#} z*)kg8>I0Hx!jQJS2wAZf@;*QWy*Gl@*O2ah`2IpN%`3$RscI_zJo4P)H?G(t5R zQY%I3PNeID`r%8+{Zi^`^VmF1sed`I^7L z@!k@gD_H`yt=as{%qyT4&I5Cf(W-Uqogg!8km;`2#SZk?!k{d-m&lq3S?x7er}9MV z;v7vOe$FYBsVZc3qtCS9+--mTaXzYPGJ={ZegM}y0RLU+-}}F z9u6dN?1VHeoHp$&x(`;-yXHGs^_SlvbHZ^tt6Lhn41e&Rgty}5_0u3jDT=P@*^7QZ zzT>U#L)7s@AXEQt9^5bqrS{)1Fv{;Uv2E22R*zQ(&G$D@^^dC15QuP7cLBBN19X4l z#(S>g2^H-Y_W+$G~8k zF`2wg6y$Gblf)xyv0d>5kjFb{Yx;XsHBBH~rMON^bc9To*h1Id%_rRj`grj2ajf{Y zg=6h~B0ryU-Li0JOlZ@`tlPcVBBg-#8@=#lr5PATH8R_k?KubPb-MQFJ~FA|DXA8# zp|)GpQR{UF?|01y_!qL8WQF1R&|mRVoHyVm72X`8EmakD~S8Ho(be%W1*MFm{{pF>ypL`j|-cKxcdj zvFS9$Q7>nT70l>501qi=9_$b#YB&6z;?qq zFf}fW@iJu~xl5b*rQ`zbE3IKi+GFA%GQg&##8Lf-NTR!(vQGCGW1f^HZXj3)q0JbE!p-2E%p|;JHx^F)lM}0e20n9xyOWY^Xr=;I|1#@nYCbx zkLM}F4Mho%`4WJ_CROB0&tJAR#s~_Fd5~xLhy-3PW~y^mL10Y~WU7|ooWUCG(?={f znG5TVQYu-h2{WzZYmZgv66uG0n)NV{{CMhyySN?5JH0ZtA;zCdwWTnQOMftWC5hyd zycJHmF2;X+e+w+-9H;`*&%D}I2CyS07!7j9NCz9r^>l>M*RPradK262&H~=w5mumL z4$D*c#Foa9bE^~~c8fR2F5<&)p{clZe==ksqSPcsCAIrKfq>}Gfp>3T$ zSl%rqpF(oDJd!FHFStxzO8TPt`7C_r5Jix4Fq#6iJO5n z)m-$5=e~I+mOOb!JX|;@Z^afm?}QAKxZedD7UrSoi(xQp*hQNM?NEI*nVngk47aB8 z$s1=!n25KTw{^ON;r8(fDj5*9r2@X~G<(aj)*~*kOENF8A1$WSyb%+e_Tx2-3miqImgf0?Z{8!K@=8Q%t2gRh zO%+59-(VJ}q;i=u1L(Y^2U)jxZ0w70##3FN*j10m*Uyf@;Pdb77d0S~uM+94K^u@> zIYOM5>IsHBvY$adnX9^oVQ~z)&ndSr=mr{EuO!9EWPfxn@otE17C^)FsOG1?86+q z-F6D!>OJFM467ioY$s#uqjJ)lxtvUYsRUn_&IRlI0({XF2X$6iG-&%P%)O`wjn?Dw zUR;>qrRyVdz}Js4NZdwVHe}J)`;Fk8_~b~n(y7E9vyAevuhC=Eni5?d)-kiG7|URDn+U1!lXR7iP>4*B~UPT z2HB+pjL?rGWbTr^^verZ92{Fr4(nVdXL1|JP}C!upOJ@6vLU?tha=ghp*3K>O_(kj zrs!|I0HwpeQA2Zo*7>p#((RGls&y8G@4m(y_{q&RwH=7P+-xjLSp^bJ5~$p{3NN*s zq0PEjaFO3aKOZ4@M4_AUR|z4{PEDY{m-MrTE^MLSisv#*@7~3o^KQeW zW%jsBQ6AOwrgFO?88$L-8wfqCg9#i1sMy;MHdJLoIbU8Syy2IUlsZGqT3kZAgA#~}a66vLSceTu z1|euZ<-E3YF?`Wkczn{CY&#}Rs><&`lyna{`F0KVJefk<3g)rapZR!p#Rrmmpn!>d znqwJ$t`QdO>88=Mci_PJ{{&r+%b9mRH6S8q0@Zib;I5Jq_&T3u{*`Bv7Nxn6@<56h zEQ(?+(_*QR)K=F1+a8h=C=QREKJu5CmqDgeB52x%620mi-V}|QAQN?m_`R+t;n!y| z5Ss z_K$mnSK^C!1gWs(L-0Kr2H2KLv^;H@)-q%#sC^+p^Rop3zdW)1)f61{k7nZsGwEE3 zmEf^_8kozbFn7wGsLMKA+_WPb(=VQ(`DxredqXVwDJRLy^H~eLDt*CTHFe8W#kG!G5GP*IQ zh?d-Qh00r^b=Ov>!-Uz(QEpKS>F)02PnuXnty842?n4E~nc_2^^PS+xtU?HJudA6) zqG6G`61?=X!b}@ODrY8#+xm<6MvK?M?8!?ZbJB6VeDOZ@RB5D-7fc4>Yg%}Vo5wtq zom98nFqCTl0C+spfk=Ix$NtrN#BQ1u4QDvVV3Kn!K2cgsrDtpkF5*aar9Gx@+P&qW7m1?_`N$ zMIm>FcYUA-dc3JjfjgBv?M}QJ1f)M}KO?j37(ASi1BdeWu^rYT(5#a{`pe=_nyF#C z5)^USC_!y;$+`%gyHrEo1t;)JC~q?7JJ+M=_8sxE_C`1v|BKkrY1JZWuB-(GcqMs{! z*`>Skaj&!rZ0Gvfy9~4Wp>w<0H%jN}tEdeqb#xr7EZ54o1jeKFC3Waf_ynO74C=)H z%VR`thC$rctE^i}9{foZq5bHL=Bd>)mbta%`+{F_vq;U23L$bqMj$>tQ zp$-RBaOl@Nc9-vW+SR=pWO{#-iXZ0~XO~O#-E&KB&gBe`wiaW$;vPD`>MLV+dyFl3 z%lQ}m9R;@w=0I-fBkq=*Ox$JpOb?gW|I=8{?qBDG8Y26d(`XmR z4yrma7k7q{Jc+lcn&id2J0JtmCyII9dP7W!%u6cOEI@OOGlGLV=fP#MMq0QzkX}A^ z1Ll=R5~V9mWFN=x>ue~5m2dh11c#FE`^*@L@%wn;CSCqqh`wtOU(F zcuE{QoO!&O2w(U~%G=LDu#XblxH(1-#-x&@Zo@kH08p5^`Z9yth)KvO9Ocj|cYj4sV2{eEY`M`B;LoMJf5!$njg3KO}D|_F{?L z5fG?Kla1AzAJJa^TQw=OjWSdW7!JDKa+VHR4JsIaKE0Uf?PCk93vPpC_ee#7|?)8IfYm3J%T4IZHKFmbuZi~7Bs}-)1{@`$vI5fsIv&V{bkq2UMMevzfUUQnNgstJr z(z_79I1m#QPh#5IpH@DG)9A{THPDn9jo(iDpk`tZUHNMR*?l7mzAK!-WkW__ed9bN zckjWCwfg91UBGC>y0BWWYRHQo0X<(MMiW=P!?K@s%!8^aw7K#cbx#rpgLg8>*v}zL zZ_H=(st%LiJ1U6LpU3Qr5&{POU3kr6BO{rt2buos;8gf@@Glo)Ok-})u4w^q2u`B^ zdld{!A7nC4G~#BP@pRJQ9(viNR3Q4&3Vah9Fd>JMup)ZHbuXTCl*aIV6-?-wQW*DJz~prNB2(}gX)w{j({XiH zUX~}g-Gds(7XM2od&Q9Ew-@P~(bKeO3ZKp!j7FQYaX9?w6;1F8rtJ%gSmOh`$ZM`^ z@ytE~j?7CUQVlsYoAb+!^7g~u$Sh*CYyqg5_>da=b-eG#6mi}s2fQ?ADfR8xj30A< z(&O^pw9`Et&+)}jcta`mtKp&DEju<;bQ%t|%Cm=yw&Q%EXXvLC0taMU7@sZ0f;Pv8 z@LF3Gyo-}j>XZU~lzN87z2W|EMt`#nv*ywLH%x#Z)l4VSMk=dRPp)*AV9bRo;`cxs z4i2R=gVBl9^o0V*3hm)PSf2wAtBn}j{0=(*`b=8$I}lqM*2AWC!thC>0PNihh-cgp zc+K@FQvwX2q4y~{H55mqQz@NS(Id#uyNgdBM=?X$GkJF`&XEJ-U-D#%4ujQv9z9yd z@j1@jLQfxIT;^nsK71Qg*K)?M;p52zFID87vjrW=D`2S@gc?PsnTahCv|-_C)KA)k zWlK-OqD@)&s=t!|xK%*%i~KmAwSx7$>{m6ekJ9M*x?Rx0eJ_8CKT4!c7Q=q89`dYp zJ&dgWLf8G&X2(ywKnk1M$O_+sxFc#i=*0EGm~;SCRSuEV!`2vaC<9vy7vi69lc0G+ zDh*0k#_IFCNJ*m{Y9*Aw)QRiy+)^ao<8$zLiz}V`M~I9BRFFq{vSetJ6Zosnw~{r> zXPZtmu-pBYVw3zdcA3L-M*i1sc<46;VA2=HD<%~@?Vs_m@E;uPnQ1w+ClkVcNzpkC zrDOuf3V6S59^Ei_0^?HWu`>@wlQ);I(2aUER$BcVdBV|`SdqI1OwNkwxcamN*)8@T z#{$_w^S&&iB~v@ejkF~Eb8)|*&D{>e`^M;m3lU6v@K#pxGUr00r$N~?18j9|*-6eTOe?rN*HHl=ok~-1}t>o>3B)nbV&w9zPLi=r; z>+PyBW3$nQ2@KaJt)uO*>WUuhU4ICxlZS{^{2?5ZT0`%r8l(KyXn~L02cooA0C{pq zSLt2m+8I9g6Zhh~6CKt^;fV8N zBCecF_5`12!Y5s0o3izZ>iNmgA#O)j{{LBPcA_hiO*yCh5D6;WO76ZqPbcAVj#+xi z()*Dg?CR9#Jep=`6K;sZi*C}uwA*}zGp-OGozM0>?d6BGPl24Le0DA04~vZN(RGRO z)M|GT`Fn68j%G%~+>h;o2;--WIC)Rfhq#RGcVR9U@|8M1%Aon=F`HFWKz2xr;oDu0 z*qT2bbU^7IO-XJ7$zSHMFFYO2J+NVB5_MAX^DI`Grjk2`v$!0Q3JH2)kIwSu^oUUv zFk)R)S27O#bq4kg6&BvTNBag`=$30%*gtp_j5G@2 ztENAS`uS4J+Fra{CyCk8EAc-oFK<04^*?B zU^tO=|60X1oU}q4n1xO`X9M)PL_PPG^Ho6y}=7@QcOg$3`t#i4-dvC@D@C*Bb&)BGS`r0E&j#xlB~;N z^{-^=XMP-1rg0gzEPqn(>;bDkA17R8RE!nkv_7EE2Ueu%u3dMEn`$ zvhg!WR=+ms5TSH;=UUJUwgXS`qfC%E%ldwvg6;fIbX0Z~^EBxY*>61#|E_bOM zpp1~f;fXJ7XnIB4p^{x$ql=yD=eXypKw#(f0C`6pdH;o|6MF>>*f=ecEG?ai-u)i% zcH1sgKKz}h6EKTBO-q95#i1}OPYNdYpNCm*-;>3mMYOe8uh#l+H&HUpCLP?|JNlLi zwWT@KbMXO;PruI?aA*0Ee3s~}*#LFk``FB+F#1@vO3)oJ70qV+q$h`2wtZ(Ca zPAnWIC%8Gq`W#tsi7>{aj+QWG-8Gu<%7OF^8KCF!W`3ggH|lb5kT$4nrp1l{^ngSm zE_0QpcbIPI_KrjieI0Z<+Q8%m-z0e^G3?2eDztsw7C0WK4Te86sJ-4xTJUTh4C~&5 zh+yf7LYzPRQ81*S33qlXSdSdE;5^)o*l4=HwvS^0 zNwfWEA=-e=T9eS@`B5;?DW~Uq1$gXf_S{tqz0Z| z!;CgP$k6>Jm(otfrPvc8+B3hA;LC&tlsQxnW~Q9*}& zmr?!CEbJRMt1j@kKIfwk#O?xJ#_jP{dUnljs7RiRKN%f-EmKZM?z-a42j`ga_M^mW z?LsoMDFb&e%|(w#{|H$AC5oq_Y2F`M2v594^R@SY*QpMAamf<_oZST}$vTiXkb=kG zEyp`Q*TZM~3?{+tE{4Y5A~pN>(3JMAcj*&Q+OgMnX3hB(~x69x!HC^!X z>vhuq?i?on7fHX*y~*5|G}T%!SsJgE$ytA%G|y^AVHlP~D`E$K5iX$T1yeRYVOGlB zgZ12dcId$fnzXMQF2!6F$c4JY*&hn{Zlx=9cg~=RqZ`3TxQJ0WHA4K4s)BoU7*D(? zhWT*>KLC8nDKZ%!^4U&UdR|sdWmvTr5C#{Q+{Zw;o46pMrzeN0|$& z19{5BVQ^@_9Z62-oG3@j*_`PQ(Q4sGtK%n{aihpa@XboV#dhnND=mx2K{<8K-RKU! zlNC|0Hj~|0pN=jxi_ASN2fw#wT1`Vy=+NAQtGB+SDyct+yITzC`_=F#P#@eXaf`pl zO^0-SQi0Ry2VjcwQ*6KN0yY&&@U*OxcVt@~9G_E2N`6eocN;I#NB4F!R5uR-<_qZV zTU+@aIT4ie#$e5_MB;tToT8{QUEODiC&N<6Y6Ai8Eh)lrF-7d;ZNVs&XN96GVgwsF zuBwCaZt%`JK}{Z<#agoxbRW;TlO=t?CvpXTI`W;{k-VpOPi`SC*ST4J@d=tcK2IR) z#Nq}nGn;g5jNLP84f~9jprzF%8e$QHa)C*BU2TxMf7t@&L7jAV!&>&YXbjC@YrwG9 zj%HP-VW!bq=9Q);oULSG`pqD+Dq=fHaevRf?^p0t5?yhVqbNSc5_+hlla^&1r0@TG zPvx?YGCNNs;ugo<5OWz2G-YT}e+R9egX|sw1`>AQL(o7Hf%w3qS zg{EMivlCDGJ|n*qGRZ$P5i;>>9^6M&dOVWL*d{(CIb6Q8%jzbtW2P-cCf%l2)TX1) z!q=cMdWqe+Jet~Y8Nk+_iPT(YE7pz5(rdAktzI?D!2M^LT#xt@jk_xe*IXii&N_n{ z53kXei;lvTSfnSp=e=q6X}aUVV*F<%jvljg;b5!>HhrE=4P2vX;gi*DS%MCVE=UID zcazBXAKCChBny+x?pxN$*Msl01=wblhR$-Q>F0V`_@yC7ceI26zrBTePUXz4v%b=h zr=wO|y%a$`wTUeBUys}Gt%Y+B;~;Lo7$&x^!LQRC(O|P5E&Q_@r|+2p!zmqfRVwJKKP{t zn%_CDgWg(!)UP4N(IJ2aH;RJ0S16h-e@7#3t`lsE6~Y(`Cw#8V!#$i=^tT`Z+np2G zM4uP%`cofOXOkG&Wyn8cr%$BIli2=A(Y)I`dN{uX=eT;jlkhSJ$z#nRGF-8jv5B{5 z3|{Hg>D}zGikW|pnapuEE}r~CZGT2HxAuror>{y-uatmGmt+!y9y6d;XJNLj5b&a^ zInUfFR(5+joC?^^J8@FQ@^XbJg!Y(o-t8%{diFcw^G6a-ws4=}@;U6B>zUIXk)^1MsuPegsr&;hyRV>oECdx-L4DE`Z}z9U#XL&VjRo*&_zalrjm7b zPb@ee5%4lri{sFyHG<-UKY5qJx!v;aMyRr(Y}%QX@D$(E$?+d~Pre99*1BR|u*Gj8 zlU51$A(L*j(Z>60UFyz1eZ-F~3dG&_Dv8CtJ><>yNSfSJPIq`rfOOwmL~x@E^C!gC z-l*#&{$DrK==?Kq)#nMH{91@gmzLm`52Dbm7YjG8l)~6+6}Y->jQr+04Bmq?z&%bK zy#Ey9L_atDWGQBSE$v0^vZrrb`lZx_v8#W}mzoWPDsPrPdP zl$`AD=JltgvFBrU(H&Pd!K{U&^b}DC#y6M=R&arz=k#h(DxOpr_Ru6T3z(&Tl;71> zPj6}~lOC50-oQu>u1x{lb!6nZ7)b1YT$sq2JC(- zK@uDa$<1|1tl<(5=7w((hI_`-yy^$+J^A%)UC143@LL~G1}%Ycm$(e@4QaM_^gfjz zJWPKyufXHFdT>iJk4&`dhxF`xX1?t#j@_yYdxjF}#G17bKP!q^ADfNqbr(^WtBK5+ z^oj7$IT`fd=+^CWSp|n(szAbGJZ{No!uB;{P{_Fh9dmjKJvl_*&aS7EEgk51i%0y) zp3~sVqYIE%oJ`L(YJ;j_KJ+YmMy;NIr>IZ~7qa#c<7RuBaCsa%z+Zu8*ACD!-fn31 zbH>Y!-`NwpMQKFm490xUFtx~>23jYTKuAjh&h;zOj_n%ErHG9fW)z9ekIv)wFRk=s z=^&Av&9PZtltBiU^J?TW4a2{F)9ZWk1vfJ;!^wZom=7q4o8H9HC0={Dyr39~efy6} z{98y3jXC~gjvGFIX%Cw32I-p*Kf%+=fpc<(&}B!>L2T1rysdDDxJ2*4?Q%tQ;P^PY z-9d)qD997r-xmmf$c!&6Qb&qDa~*9zBNX|i!h12hn$a}93+n1`Nyf^vtPGc@sWH}N z|LJ7Ih0FrD+F!+`8r3Od^^U|Z6oN46{dAZ+Q@^(>GN~=X7_;7u%3dyoipe>=2XnIU zxW`QRkQE5m;vX=ftMlPdf+F2-@f9cJ?gutxGj{a$(U4kKHt5v|`(xKEs~!Kw*m1mP zM8a7QQ>OOOhVFRc*8iQ5_zTp0ax6%F=5_-dsUarkCuuca%v*iR1_sQoF?CPVU@UMt zTs<0v@2B_@L+f8O;nWhiva6r?v|eBis4t>Yg;DsL^F^0J6O9U;1T_vE!OaAbnAK-Te@K(vZ}40T|Baty?g(;C#NWDFF`-lC9}3~f7P z3B$WnvEQx~OkZ+$DLozQc@wRvqns#y)}4ffd2RFoH*>6fe3ofkHIetHbsM(5dj@*` zd|IxO%sk&b%JwQmQL~_Hu(Vu}a~w~olNJdhQcXKR`3vWqyIn|6Z5G9!Hia~4Ln@Ix zpbyp0cH-G_XGrsTF^rrj%^nS%0_v-)p+Dg#d8(2LF`kidzP5wo{oB(sb1s9OehffI zJT8t|2_Ms4(6gwWT5ZXpk?HfX%Bhzpzy22S{d%5Ob92yh788h-qapUT7=USR1Qrid zdI}CP0cn?TUi49=OZ&Oah<0`;~E?5ZE# zbo8_}zR8*jeQKFBJ;x4{bDUA8sGoW8!3C?|&4GKf2dV8^OPIGoi!9i(mMnLb!D$sv zU>SOsyz>)6vnyxm!!g(>??~*q6>vt=3FmpwXVvPp;J^KR!HXr6;DOLWPQjq=V(RJen(0@)%e|4zp^@=!|*vN!4B`lxK8Ugk=?SvUB-+lk{e)G^mqO-0!@MG)+L zL^CqKa&t&A{BB!=^?uHR-jRF6AvV@>#y(5Z>y=91xrxHZ8To)Kbivms6KkEF z0^^r`^q}K>Nbr^-dd@D~3GWP>dFo&rmcWy62*UgR8W`^Kf_yn%2;U9zSPh#95IRx? z`g(DKo!h-(u9guP+AksYKc7*sQGq9=2KepXZysfG>5or!@Lg1tZm!)*Rd!`yz!puO zR-A(M^!^KI!slikYmLCpDGDDLx52>BL9kQ~gtd1gScRHCs#4K~5fk^J+LQmFqw6$3 zfZvW@Aych)cUiz`8VoOc#zX!%7FN#4BSsl1_;Bb6v%GpdjMXoMGg)g{g_(D0?4}K{ z>cbDZ&|U-Gt7T!WSTOuNupK>ioFK=9JNW6p<~4#6z3u5lb`ZbIxatKDmGc_iT?j3{vkYkt8L`)VetAG2It>f;{@_gz_Js zvGXGIq1Wvn>9cF2lin!6#t+aynd^c(FIC~GMHZX4pn)yfz5`m$X2KEo z6#Sr)j$QxRLF*^(^pN^Uc82Z)y}-Trxndz$EZfFTJSmF;W&s&DWiPzjTSfMB`o57SneH#j${y|L3rr?of&HZ&tAog?4%V(?4+HM+f*rAJycm}OZh#G_gTPCWj@KPFR37stN)0wQA*i^VtDUHW>m0@?7F0JaH4+~DO z1$Xts=BBKd+GxgWs-1&}!@XFe-%50ZU4Yw7lSzf97_~^r zMQ*?hDV}2N?PGm}t`%dV+egTa@F-@y$Z|F&Vi%Ea=Hun1!|aTMx#Z}4J!0|sCfxJd zPl`pmz<;5D_^;i8+QO8#?ikk_n;Ol&j+#rCIwyeFM#L_1h**j3wbHh9f~UL+Y9x`x zn5OK3U@nVF=5#{#7tSHry-r{p7SROgtkqD9Aih&>8yvkya`ONdKAgcaK^023Ra!# zQy7{ySP3s^_*AhVYTLsq5|H|#d zKa;f~{$!eZ79BmM&RZ-{<$b<75ne@@!3SM0oL_sC`1N?vl#dsn{P8M$Ry~Vu`0u9L6Jbj#6#ynD-$ahN)fZ3P0{ryE@(*FJ(lP9+pY`AXz^TX}L z@}mk46Jso}P=bT+MTz(RI5M@Ri>teff_B(FoHh9)IbYrh$y*)p`MgL-sGrJt;%fx+ z8Vz6}n9#8BH1=p(7A$l2tid&83+B9+4 z@V=LtD-<*4@tX*z8^C?@)#$p*65yF$M!uxIr@>MYAR*O)FHJ((C!&>teD4d$dmN0_ zKWxadMQ-J}+iPwe6ud^#W`0eGS%35h8VoIS{UUpL(&zG}hRQr!yl0s~UP(Uy*ktvHvm2 zjVh@?AMAx`gR-l6qOFaLOVvLNP;C2Ohe%ynnift(tkWVj2|s?PzF z#P!IS^>IC9JutialuAF|MCF!FfG+cT5;E>FyYsytgx42hCH|C@(MQUc(&Jo*4|!C%o?Wf)VW+$Z`Bqk`4K~MD-+0_whmA& zSU|jS=hgg>!?lBp=NFAnz4_k*ggRkZM#yWq|~W3-ohhUSl2XvNNbsHZ>7mRX8M z0oFuuaRTLiTu)y=D6V-tvkev7Xunuiz7&r{hveO#j~!f-ctvM59v?IoXr+L<5tB&3;yoVY-8 z7OLWlPkQJkqeEuLFU0E(%Xr@{ZZb+f>fpHYE#Y4-!3QaE%pL_1ct7U>$#t$GdvvmK z)1?At9vTtTA4PPH8^=2PWkr+XmZRF=45rt64QSq+3#TR1Fz%HhbX@#HSa?HBBqj0C zDIV?f4ud&r1(atX!~5iF2|s^*p&tqllKD&L!G^jD2uK!!n|l?Yc8?G?JguP%XR4vf zAjjjncK}!KH3qT4{j@IsKAj_Y!kEo?N7Cs4`5g8^aM`wrsvj7j=f1_m5siA%^(Tn_ z&0dUC8gf~~_))yn@DVd4o-toO0$y-V0oAugxNe^!tZp{O&q8J_<;8&2;1<^W$cy1_<-w##`JZq`=f*Fyml*My-zY|)!Ad{$_T5;cjm&MNA2{^ zl?vX_hf*4KdlJ<@bc%*t3@0xmD(ISB;?O%L!!B`M2+6I-Y0igg#;$q}tehiEeZ}Kw z#U}+?9Wy}QEa18rq7TVcy%Y3Os_eXRHkI@Y*C)^%SVGD!N2B;kPZD(~nwJ*+PLLnz=ULDX;ye{T^scZ6y{F)fZp~-un~^g_!!(HQh?0Ta`mdIM{bR|i zRAs#Waxx7t-%74c<@T00vtih8fVy6K&b?R7!`qh!aZ~(fysq8FglJRZDJ;dlPP)c) zI_AK= z1^Nm`6u;2CoD05|u0 zw-K63Cz7uJ=3`s!u)y0^5k!UllFdt>kky;Um<@+N6QZRG9h3OHP5))lxH}H??*&in zt!XE&Pb4sW#98oc%XW6sZ#%kqc?$Tp9ECqYPe>p?5_Dd3j-_o=8BvXIjLkbg(n*E! zjq_Z7Q^ahNG$aF-`roLkt&Uaut9+*3;0ZGSqv$;RseIo!PBIdbl@Zw)QIg`E=e~?0 zDJ@A0Es>Pax0EOu*(-$*g)*X)tn=KLQYlJHl!k^BT_PlCT-;0V9JFWa_S@2}0TnDptFZZ+-DE@0R2uc>A>pY^tOJW^>+H!`JAA5sd<2X0e)>b%gA()%(SRL1Yf#!)&98r40KTzDtPThgVF=|kYfPKTvP+=h;ZzlsShq~N6~3z5p&+m4Ei?e!Gnntsdr!| znj}w0!3ek0iB*BSqedX3r&b@0o(QJ7kSSu-mo}(-L4LZ*+adw@To%@@#H+wKP9OwDJ*q6M0Yv+;)x8$um zf6DR8W}n7KV?x%ulAQP+2AbF$A4bky&8548o#;9xS?lp)v2=gh59Xt^D*g3vHrgC+ z=Wppuq$(l@n2q1uxW4{ZN_s2M)jo)J8{7vg8GCABdJ#VzzCd#2OQEnK0jhpS;9}J_ z_CrT1sV^0SeeZP1*~((DlzG5LKe473xkhlLI}KlQz4!;qx6{gL=MWzUSk0Th1Rs@_ z;*ZWKEI#!gBhvVg{N0xbo@vgMx6+fyHk-qPu6z>OEhN~_o+O#FADKgMWpH@zPe!p_ z1b97GYR@p+;K*vnP*n8K!YPSXTa!-TJ&9LWZLFukD%z)i*6*PLK?G2(O^m>;e>w`^Bc3mYU6fG^%AkSgPF)cvoI^*dxk?e>O)^@MB^ z8y!pH!z)Qw_-7jVQJqtG=VIE_{nRBy2oLlY6SX~eQNaDq>&e__6_`d=`9H; zqvg>;rGi?oTL9DlIx|0~anDw)2;Nf{hNkDDu;%4QYV^67RrgEAfx1tOjUW`EVv3-? z$py$(A;OCa!8G6yO`Y;hFlkpX9ut?QdHbr#{-zXm zjgKN388(466Q2`FStasC@utAxp(!S>4RUjKQ%BF3U<+c0rjhA~9`W_(@5FPJ1ti5{fUa1tEZDhV z4wWk^!r*OEaM7=gezluQBG2C>^Hyl_mG&n>ieDmKx9bQtNhAGIwG8k3%7Wc4S!Tu( z9cWfwf`VN=jLE$5WSsU^T2dbfK`O85n*&462F?gjJYI!tIH?R_DwGvwvWwPvxoy10?C`&YN&!g?@inbJq`>@< z@ki#eH~DdODo&8lryG6FW9O?=WXtL;yt>SZ=q2@%F_8z2-lB$5(s5W@d5>Mwu!*W|JWnMyg+rz3The}P639;K zpbt)`u_-H@ux*Vgaldwho^qQbu-lu0zctKpqSkY|D_BQhbvA+V|1TQ9gt(A$?)~iV z;X>sGq}jGlk`2e&wcz2-xy<)>DeR@b4E(4u1zsQX03knV3_K>pE}FIif8!X@aOfuu zj~w{xzSx1)bwjGT?g1_H6vtaHZ}WdD8DX-HB_6+Jh~*m6AkhLOO4pqJ`zi^)MV>R^ z(Ypj)r>wCnVyI+o>1U{m!)Y`fkOs%sd2oyCE;8Gqsav)j>Cyc}l4dFrhbJkh(l3TemUW;x(Hs=u z3sFdwKrp+{>v+(>yp5587R7$SGso{}a&Q~7enWv^yWR>Qtw#l;GTi;Y+#e2y6=M5o z1&}*49u6jsLo1UL{LijY=yhJexo?+)a@29!DhQ+_57y#al8R3>?dxxzI15sbev_ga zh8>$b1M5D<5w|8Y>`dH1w6gNy?6WtF+vozQNx06g_;a7xdSMSguR9Sm#;izqwJM3% zSx5Ff6K^Q_A_s+a7V!1c67*-wslD*IcD$tc5+=J*dOJ*k-B&V3 z^cn+T#VbQ5tJRTA9L=R~Wj*0yzzV_KHaCoFyiS*SDWb{Vv-nX~4JLg#%O7nEW#50D z2r;Ka@c8C!(D98>_uUhqY?Bo&V9SY_wjsTL*_JtdPLr{eX%`4Tx4_K}6c22wr&}M! zqmke>X`yp4Md}F0(K^T8_;rJ%p3}oBx`)(^ON3?H7UEfEFPeRQPkk!97}ao&uPGb{ zO1&lQiJ;r)YHnN~5TnG%6-U!Pp*Y;K{w^7NyqEkg)Bv^bLhN;KacJ3ngZ5_w+D`;j zy4T6q|1llYf}c>`(GK`zxr)oT@L1=?0I;r$zCF4P%cG*fO!pa|S{cBbE-O@5n1t!u z&%ijvLuAJ$U}_{Hu_io^3Li>CrMJyYBA0)cvT(;;KkMYniY&ytZM`z%9B|Q4Xa}J;LmaB=UIzWv(*! zXs*L-#=HL^@z^Q?(b{k6l0IoJ-Laj{!!W$^VaiyLuq%1VS%rbe-E4hGPi4kE- zf9!{%>p~^J5V9qQ$+5aZ_Uz{8j7fwLrWkHU zHIZg=NAeRDQ!Ape!uLts7g0!jHH8$P^WtARA%kY03#glXA)RM;md#X{i@Cw$n68*g zu(&;gJ=_tFNlu|qd(VWX))kQz?jkUjoJc%4Tw37fN?h|M1Z8h%GRkK--`c(uYP0Yp z`kZ`+V*~Y8`oDbG=bNHv?U`w`Pud(Gub2v-YTl89ouWxjZZOX09Wy*u zK%FD*vBz{~fqaxGydFzNvxi^V@c};A;%$K1!8V|DBpFX7)nLw?HtKYzl}yX_Wk!5c z@#0!VQf${t3Ke4MziXVo%w{pzOv)odkwK(9vz%I)XT#aGwu0rp3h?4ypVQasUi*TRP1Y$l~CXlTEMSZ4+;qu&2a^-0- z=iHhJZa$J|{cs(5Yxf`X)+Y-BR40 zB>Oe{LE_^SMtPS$b)TO@=PtcKvmNu{;q6EKsc*y^7LLrJG8O8utK|u;t_h<@uiJCy z+d(>Ql8$xt*);sew1PllE5ebj#s#cLn_mY{%aZmZb zdk>KN_a}qah$V{cmjExd$4K(saD0~=Axq}M;S*U973YRSW%<~>T|hnNWWtUe$4Jzx z8YtlAT7e%3cE9swbZ@p+>W zx@XGZRizskC3Y9`P?;F<&cbL%1dO#d6Q6JEVM)sX?fdte%_vC*olI}A|Fsa_oro4V zIcd{F%F!@kpo;bfE@tnBH#6VYQH&E2CugpPQt-VF61F@+$!Za<&-Mn-Tvx?e4K~a? z>vk;r7DA8ZMZzS}R@iBAK`?oC1wZA)0O!tWqcip7EdLpo(^BPE%*(fpq#<&hMWG3jrIziXUOsxLofM+@q$!N|};&Zu6 zF#r2~&cz%@4A(v)SqlJueJ*D5x9SQ+%G=1Uq+u$2rVO-i#Gz_TAsfFwsxR z_cABu(jt}-QMkx8`TAkioDcA&W(j@R77Ocd8A8>3S@@Is2*s?G_ys}7*@g1q=-i}F ze&1b1?UYtAqTD@i*1#gTF4`_=47-DqAB8}fuQBfD`YP6!ddd8Ujil>T2Y=913u`h? zQ1fVWoc`sepiJony(=Hg&K$eQ&L5TI-#jf16BLC|dZrFFTg^k#6G?X@M}q$0#jKIV zN_?Nbm%VN~Ont3F;QISuc(&YH;98?go)|r*!xpQcbSQhg-r(d(B%xOyGLp5KY-(T-4bTbQ=$T_p!Xz37X< zC*=96>1cS;81Jtc;@p9h-QubO@jqu`Zm~74S-crsD@LHO<$q^85>7 zkKpasYO?W%8m>6TdBS^*U^>TlQ`o$P@wiq^q4@$VFZPCW+h%~}%O2J{Ars_22C>KA zKc$L>ImEA_1HMIRH7tC*j4pONgqIRFF%Ka5I3u^2rk2sSnq@y$x7(1{M=(6%=m z6;*6;xp5&K5fOpbM|;TC`UOOTbAY!gn&JK(huF-GNjPuvA2jH(2P4wL_L?ZdYtGFO zdf^Brc?Ceh$Q#ngv5z#$RtYLjPQs1MU};(u_2 zJ~MmFb#(^GdGBVLajqD@zAqIFH5Y>Y;g#_0^f8)Q`CXu>n89?pPX^_U3mHjOM>?#g zL~8e|Fds@3iC|54J%HHJ48yn+IlaojFxFwlNW}@65)L+26>&Q`XE#$Devp4NGQA z@>OPvLk%V+8Gx62F|T`S2|BG%q?>=8gEppz>9gvf_6qA!>yR4kdl4?k+I$!L-iu@1 z{OMr4TmfyaaNdtPDRB9dM~1U08F+h#@e7e8hu@y08r-~9cV#x}>aB-2O2*{2h6|`~ z(}N{%o#^cwcDw`Dlc)`m!tuWLXp#4ZSlFJSyzA1uH(yV{&gY$ifuG_H)kDEBBPs=3 zgD#Pz`WkZQMF2{LF62+hsz(1in`n~QGWzU2N3Fg*#UU!RJ*gpS}2&eL{Zk@7F9u0gNu8_77C*+lAQ8=N|k z2G;|O;NX}TxO`nj#JTR_{x65&k6wu2t3owv5JGU6^V$i;MPWPFNqf8MJau>4iGGFZ zsPJJes?|*exfW5HJYa>dZU54yt*7C~lnUxnyc8Eml(MhHI#`}j2dISe;fB;(`u@{- z823ubdh?Z7@|OI=S< zCI7UP;8vJ7DBd)Noo8KPqAHKe;O%7xZ)rm4+6K7vZXuQ$$wB&@c{t#3n*PpK#PvsZ zGE2`df)Uvns1P^>CNu_OMot+>Z`+D?-9lgx{h57sXFC39T!?1YmT)g_Gd@c##9VDP zw2o7S;PT((RY3#&XmkOTvk&5`t%~T-)K8sDv#IR2N@BpfjqU{uo*uHrBR(dq*g0A1 z!EYvA@_EpClAFmqoll%NC+E@Xt2AJi52P$d5@Q~SKaCS;kKwy z%`k0y(d~iil9FNu_fzq zxRP0#s}94z-o^ zBcE@;f6`%?sH$%LV_pf&jhO{wzc~l{GDTjLoF}~%B!zP4C7~rKlPq7+NsG(W=~dS} zGC6UywxfAmL;sZkc+Bk#o{Ua}&tnB-m6j&tXZ_~ed>tVXewS#2r!ao-UJ5sl?7)2v z6{PocF%enT&*OQifphm(oI8X2{;!{h?1k-gq2VQBd;S2fY3oGE7uRrUk10+m;_mmd zN^~J_q9D-g06I;ci@~FzaOhnD=QiF6*P+#7Zx`%`i?LXsrBI5; zIi7V~A*nToxfGZJpLF}_=gWKP&v_zvbD0V=@61Zbv{A$}s|)FmTgsRx(In6^QiplP zweUKfk3B&1^9C76a(p4-T#^ zf)-X0bUo5Y{LXNCS$hVn6H&@sZhl2aHG~DL_fEi}F^(N0?I+O9&4w@90`mEvH~p2B z$|Tn2vYX=aiT#{7W}cxqsI=^6mwD9J=XiZ$b~?DA-0S@McfI@R@LLH`@2{X&?2W)Z zV1VOA$-=K!Q(*5tWAf&*2Zn6Qr9zv<(N~oZ9gZbT?=mMA9*5u}4+(r%H;r+4P(;r- zw$d8@Imp@H%s9$R!rh-1c)y~K9u_((*rZ1>)m{PjD8A(B?5@Cqq#f{7#U5BlFh@-qGZ>jGos%qMX`&Icm7DE!2R!!ii>f*(r~o@T@Ux7{v&rzO~Fil z3vY?Jl8=kj!ShA}@-9SB+tL{La#O4iv$`A>MjnFSH5chIhT-_>mxyXm4=L>( zA<|U?WV2}{UJ6l#3$;Jt&*W=lY+)YbxvHBL$qB(brC!iol+Z>IO|Y+${_ z1o$-DpEe0Yj;c5^vA?VZ2%UnYN|%n^TZH@iqTtX9Tj z!y|&EfJu<7a2BrKD(1XUlLk;1rEPl0+;g580Xc?puc@FR3%JHX+sn8C$b-&1Tz~qrm>e#b1vlL--vj?d!|NC zf*oymE;|i!@Z?*5>7l`gzcZ`k=i%npb%Yhmz}2lc(DzzB^{p!dS=lMf z^|^nc?8Zg@G_}XnsXiWL-&m7*lf~hP&l2wUub;gy{FRyvsWFyOE$BP#05IVpoX_eH zxX2d~E9phlb%!7S4wq$HqtCfq%+{k`&`eT#XNayjxEeaHH^BVsmi)io60l_4cfq{4 zSlaiJhtk-s($)}ossD%SsO)#g@mY1wlZ7TUP>b5=a3BO zrA6ixE^a8qV-b!ZaxxALR+kg8kILBMe3Wx?ALT0=*rMXkiO`YeKyQ95pqwxVl)Gce ztgWX>b?hIe=I=$`@h{nA#@R>#bsob7+?_M+_Ion?=qWyN+Xg#6%mTZE525<2G*oG7 zVpzK-*=C&1S|7Ft)8%$F3=vPx*i7E7n~EQwJx6BzTA~;FgzvcFvLG)nkLp`A5&P8b zWU~7dI5|&~gm&Y;i4oBwb#5Nn>m$Y}zEzaD~HLPDV7a2V%{PQ?5{eSFBKz!lE@TVa5- zeWEm8r5wK|V?JtWd1B&B9y#c+999Tk3m(if!Hbf9sCmH?awP1PQ*(`JaRZ;xVllPNYFk6=~nT^PTA!k`|d z3Pw8ZT;@&In2AKL+6dw#~k|HTP`cZTdUVe~_w_lp! z!sT_OU;PKG9{7qnU9l(hRXn{Y$MH=b|Ax>!D*->_J;}RkLr;(K=)k^Q!M#RR=ArCY z$a79&l_uHLKF*S6OVp<^xh}Ef>Dw1Xb!;(iv%JiNEjvS%cEphC@!oLhb2_ak&t|V5 z90#p=gLJ(654y_#1@9%*WiP&30ou>FPId8eXe$1S?(6nIt>#f&d2u=vJ(feq&*Grw zwTm8E_*8Ipb}TbRHvlAAs=nI4NFcHZYsaA=qcwflkN zvP1}8eW7Gp*<4&VPXOY3YS|4R_rvA&Vjw%u2Rl~T5M$S3-uM|RBxG+5Holkx-HR!7y zSO2n!dS)DgWeuy@N7up#mK-K(vK#2Ly%Ky2{o}alawb``ESq}nsw8q_w)D{@j+vq! zhgT&O_(pd!Fs~_>=x#VeE%kV)sU%Ki9^WQCZ;#Op%arj;lO77>kF%k9eB6Fp*7xrxt7^1N%Im2rsU zYkr_QXqy^E`VL>A3%0uuRZ9zE7Oac|+4C^xyCr6h4<*z6H-nUJJiQy}3`3(2;Y7L* z9Nd-&jms5DtExiYRf9n~xFOxbDNbUOpV2LT;|E zLZKE0=Y~qN%?ktQ6($!lrUNu_egXu*X(84`gJaq*37JkDxGwBulOs6dIDlur31Nk$ z9W<+MWaeqQGh=zDD)N%*dOX9)8XmRZnMqd*%RNbh^O9W-44X z(L~W)mL1y13PL{&@zo!wp~sjMHEFFO!~5gek-959Z#I~CHssJ>%g>S0eLtC3eMi_F zm))fJa{$DbABW+${%m7M6uJE)0^ORBGB+73>CSpOILn1;?8^tuciYHI+f0rvXMlEQ zd-1bFD{g+@NMcG9Vao~)xawa6PtKR{b5CXnTop1=#6F&Ae~hOOVhNoggw!iOobOWc zhprv{PS0_mVW>|wCzso_NTpT;t#)FKi44e|Ygi&5AAXNi0 zgO2}%D_kZ!I`#_f*FFamRX(v1JJzCVvNHd4;4cyq9YdSrMY-ox9q-a1 z`cwNW>*61d({_09tzNj&>3Y%7xN;`UTs#drsT;=@ILtZ8{rL6Mf=S%0sbp3|7~1#A z!#}kGkU5?NABxsfojYp<-J;lNH{2Oa#X>e7CG0!t{i|X;D3SHL8hDZkyR@3pL1_ z2M@?U^!eq8n zqFOtEL6-uGMa`(a()P44^u zd|3np@qK}D+=5}Z6mVqVAg)zW;o@r=Sh;FHj3ykwtVzw-Ql^4SuB?XO)%~pL#x2m6 z>_;xr$L7qmN2p&Ct zo`#L4VC30g68%BIRBqcr=hwF3QzvIoHkw5b8i;|9&LXh?a*OCa7v=F40{H=63HV7V z8WejjQK3f*@WzziWL<$Fw+Bd}?A#VIQaGN9&*u{d`+UKltCF;A#1qw=b+D_#g)(cG zVh~Rcn(GO&7rD8c?*(vtFGRMEyF)GKZorB|JJ|i+oOHafiTpPu6;fpKXhvNS(KjyQ z6AukWOkI__WSk&-FaP6*cvv!8M0>(G4~NTm+?byY!#+n?YHyBt;g@)0-fQV??~k;M zU-Ep(WYjjl4SDWI$ky)7pzx)H#@+L$Q{>j+q5B)CaW6Lyn`VR7qm{(`+ht<*<|>>t ztH5+yLs;}O2|Y3*p<%Z&E{!fE5#197+cjt6Y~^_{oG-&UOI68=fca!fkPB_CnuVb$ zCd`j(UzmbT1owB}<2B8nZQVG&K@js%27V0a5Ob|CkhO~jp_sJeEL2jmjg7u-2%@}T?FPO-9-DV5jt&)B?TLf zqW$G4rg)byBXw##EWDrul^cra%+5RHg`*aua-s;1mwXniF9`vmeg5=FhY@InbLSdU zC+f2#78SVt_0p+wu!3{tzDxb<$XD1>tCbkYz37ghfs4+1sbR8G8e`hGb(FW!O|;P zcxcx}vVN%?x&=NaV^{a%e;pr~GXiI*uQ|>1#1-R;KiquAMVlA6ei_cyRwPBH0C}8d zJ*T;!T7BF}+*dV_xsG=D=i@ixIGlq1f}^PIB8HD7C_IWf!BU@A#`!-b-W|nNSk0>^ zB_Hmg^_OO9w^or}wTmVJaU3(q>lxKATv4wdm5QSr`?srOm~XVEyIyTiJ-<6VhOy+O z&>4%LaC`8X@Gs4RA0x8>Ulw|iv%>ZmbskPuOKI#BaMnv#kB->x1j?a{on=yl@=s^p+)+Cq!YVe+K-zr4A<)m0|7t zNPe!;A!g&YrrPZmN3e2_BL290jGYvfkIq^GbfOZtReLd>S}O|)kHjHQL<53*lv%Z~ zspM>nAGZ^p#J1nagf8ldKlD`~$80*rS?pxVv=sX6qBL1CcP*|g*bQe^8Ip*aIS{>u zAqN#0SRliIu;}KH)07;&G4cE%-!NKU|AUypn26tCG({N)l2%P+?5 zz9Z}srCZEV14Rh(*QF9wWjKy|koek3C?}x`yM)zf-ZDj|apaWXvt&En6jwv5$MNB~ zOCa@Q#Oc+@bUN;VBAiMKV5WC6?w$#W(7_VH5WTlqPg(S{VJ*#F9jm?}1ooCC@Jx&-VQ08V!zq7X@O80(V&imf z*7}_oalMx>hr)5^&wsRRyFNKcO)xrZDLe>F!0$q<;koBCGV7=xxPL!|A%SzTc;*p) zWP>3Ru>pGK-&W91w5KcO?9t@c6nNj;ggKLCh+juAO}~B&WkRiR)(0Wf{UBle({m5G zwK{-ZEIEfBy+1(KdsYy0LjlI@xWn!8Wk`Er8>KB(XtF;9o7FGZTMs6I?Xv5*d)`sz zbaOZF>p=-7Iq3vW=&7ccpT%L*{5Wh6$>fJRb3ENJZ zr+(DJ+)xGl&%1<4yZMAYDkdrrKedGM`00h4^-trlf-Ujy>!wcM+qqocPyX8nfl%<@ zK3MRzfT*R2;jT$S?0N+mIH|V_*HkM(zNdgTm4?wXTh+j=eH>0v`Ny6Qbb*v)6{4K# zN!Y3wGJN+o4B1FQuM=Q`UJUz?4J3u0i-^_>70Z>5OCfFVb%H^hZ&_p>%wJkruTuVo z|KQFe+}YI46XlN(``jInnBomGt0Sq;{070DD`v2Ke`>7`Q6+2KH)1jzhQnhPwAnKf zgpB{vSsMs5XL>vDOIj`4r6>aLgu1ELN*TO$Z3%kLtHw8Hvmis8^EbXQ!Z8gEGPp1m zI}0xcnHVE?okp1w1nA#ar6)G8`ZLdIm`wKG7FV%|xSd z0iI4zK;ximH2F#}^C~0mJ3+fn()fXy!6iW-++Ulsjwxnu0~2qp)<+ z8D`N0?yeKo3pKAkk=U<4>TZ_*W-NY4;gq;>&~9zbNG#EV!E^ttlJB|I&lpK3Jt~}+ zN5uh@(u!fKM>zQT3rNU|i}+cgn>}GUo2t*Ajrp}b?4G^V%Ux_zP=5$>1@PJ`kN zZ$6o$Xk$uV+POa(3loP+$0A@H@MfN^b{2AR8#lG+7tiBRGW_`o)h zsh!?<#zqk$x@4_yEaljQKWhaU>8W(a`*`Yqp^ToGS<7mK>7nJJ>-7h^&6$E_HstdT zN2KFl;P#*k)Op!aYJI2!Px*$!ulJR->1_w`s9uWcOaqPQ-l;zIN6D*(|3Lds5PW#D zkRIKBO;GmS45XyoVK8G0dU@(HRg<2P`-26TpmBv3?zqMze61skyJzB^JZ@%Ma~Q>o zq^NXVHLSWl6Iy$UaI+QPFD<$usvj?V z?xDPzlX&{xaT$cC zB163XcOBiL6vsyHSb|5_cfgiwJo4aI6?|+>Ccmp3;ns_J_@D9-euPLHU;CCl7Ok>k zCvEE&n1plluEPtt%w!dLv>+Yln=F98w>HtM9|p^S~EcH;-$xR@I>B}G#HbFWTQP;Ea(5zF+ zyCyjeLNC=5pJ|_|*m7O0v6zL%Y18(IVQllRl~8Ca5PqOkrOZ5AG;>$#m# zd(K^!5#LTm7dOMQb@WI2i^nJlOcy;(BjHRVe z{nM3rz2g>KBhhGjUxjwIibMLqM7sLz30Op`sT(~8+jk`i7B-qQyO%|?H=oX?I!D5p zv`Ox$cRv;%e`v?=_@ z^e>!+W~D~t_`oT76uQ%k;g&pv!vY4AK2f~@>9G~%D6g@mEUQnDcz6>gHAd4WGotd{0KcV_>Js6RY@nmT?HEh;{?uw9=MGrlDTP% zAZgeQy@%_$S&%rD{-z9OKOzA2?}A&mC_H#bU`t9|y;t@=;wCZ|-{t%gG|irjysc;H zy+4aV{&@=5O1tOagaGh72>!iRQSzOte&^1ej_Qz-BbLi z&hAVUs^=K1kCm7OVb>VXT~9eLTNS!*j^?^csZ4;}X*@g@jaN#i!{?9Yv~yVjELg_v zNW?FT2b9IUB*Rj$rvJlg?BBwNI(} zyBU!4JeRxgUgD+ZAEkIK5dX~g;C4l+83S(({>$CqfuoatdGnP3Gs`62)=)64eeJX}UKPZzIuoTp8~Seibm+OLPkw%~fZEzf3_AB0MM4a)qDCC7 zWb4>eCuLSHqLE5&j;(*c=?l^Oubx@|ehYndV;l%GNqpY1=lle_BRIEZn!r*&0k%%r z1~$j*2~CNl4t{BDdTk~fmFiHhQaFJwL?!OtvmDfwgs^Ca7{r`d59yP+e(Kg;yqT6s zWMx<<*&y9V^_t()n|tJ_!q_zQQ3xmJ=q3`=E5RI@CIPLRZO|p`5|`(_#y@x=mD*Zo z!Un-^Y`dF=HT`?J@7+Ee^pHb2gGxM}Hi7O?yH0bbhr$yo%#_Nt)1@iD>8fLiM9#OH z)%4whE95H3{7Juv;>9+8o|+Kdog0h!=hspGzbEXkZJ~I3X{(j*FImjz_2Ra@(lGH? z52;8VpikX`arlZBm<;``yB9aaAL&xYEVI%&?{Ti^7A408uwC(xyDaSaxEuC6nvlMq zUcgJANvyVp;E7rHsj9Km9oqOk@7OY$J{>~nVJdPlNDsFEE0 zCk?986fmuQ5vm2~SqJT$PY#+!@&$|4=!xx`@M+IfIM6PjsxI5Pb~CMMjb-a@o}hnfj^b{OIx;Dk%a0jIW8SIz%nDftoG*ErT{8TcjMPWK zma)0)*zQ{}Zrv@i=k#K{+OUBtyQbpPs0zAH=nv2Ijs<&!`^=-4A~0KHjH;M-UD>db+eu$4e+PU~$(cnaM zxS2=4$%K$evqj<1-vb<{x`M26c#Xkrg>=V_@pORWk9CV$!k4o>SU1B`(58F>1_q7k zu3}k|cfZ!ke?kJ^Fg_D+hY!=;5uWHi-~ge%se+Zd7oi||H5vK063ly#lf_rUNtlHZ z^K!aSLng;H{QmSG-+JpWb}*os_+6U|Tm9bDD_-xR&I*sIBGrfRiIef{$v3oakv}OA z4aE<>=45d3JXkM~Mf>zZ^4~ob>f#z*U%5%Zx|WpD`7LjV?ENe?%FN3sc`u&+t<1wf zIT;eG)yGB}wUYr?b*yTrAacUuL_#l{mI%kt>#e)tXS{9gnsrZzxn47fyD5;?RrhH( zat^h98qD$A=j)%`o+jATkOK-vqscj!Yc zV-WX@#q97W|o)F)?(I%as~iO{Xs-3#qOL$AwjFAr24XP=7)<)~&H8 zA)n9SIq@9!gk3BBkj-T_J?yC6v%UB`ekrzi1>=;~D>P2!13P$r611P;KG*pQ&(y~R zdRsn_|50=%{!o5x941St6q0PAD2gH>W}b5^X_F91lJ-=V3T>k7Yj$OevbGSC3Nz2S zX+ab!%F<6#QnaU%*7te;f|<|Evz&A8@AtaQwOL$Zc#-o?xq2bZ2Y0cABqp-XTZ2G&E`rqi8wsYoJ4$_IjKO901-3J&p8S4YguI_| zXv;rObo}SS)`lC{preQ5wOrxD22Du0QAL|966l}8@lZc*G2|^(VoqjBkV7XHqhXaH zy}!C1294b5uT0LXNiUGNwC%7p_!xMeNkA2jUs?RNh(EUe6r6kfk^IU3Nt?V@VDE{1 zVp@=lp*`!d?Z<5lF#1WS_=#1kc#p7GuDmBxMf;d7RaW><*hc(Yp5wA9OED)m7Si?v zR?Ebm0^YxSr18ciMt1gQbbl=lb-Y8+wakG0FrG|1R5rug4LaQHhY_Yhq887&FfTHb%-Zp21jVhsbh5IG+jo`^#B-^Z-yaDGwip?5L{7k#y|JozycE|GID=}KYiW+ zziy%@kx=|an^dA;#S8|7myZhWY?%fH;~rsRp*g(yQ%H97gkq2OG|bBlAmZVP^ujt> zh7s#C`*cbXZiFWh8@HEc2Fxq!dWvJ2CEpQ7pBfhWFN;Q#qXSeUE+1Qx<4E81jpWU< zY$~e0meI98M@gjyJFr}Y{Z`Y*I=zrGzbl@@gylYG-n(7~{V{Kdxw|?Bex6cO{c8gF z%#tG?BW??t&ip22Tuye<=weV@u0*m#S23+7^+fjbB4VsmML+&-AOl?nn~U{v)Y6VKP!})B^Z$XM?`D5{gv6*dK0qI=O{ zog(1^%Niy_z*le5Q_x60DAbdgm$_W+{(bzN{8P}PrGSzH^`PVXf%ZlW(QnEH8B#MwA@aTy9U4=tlsla4UQ|BWG^ zFFn9WjcAS)kb*Vpa_I9_9j+BN!0h4)aBEg5j7(8w65b?no&h(waLWs3ckLzuZq4@F z6Y3(lv9z%Gnq0d^4k#MZ=a%BRPvfxJA zcof^(PBpBjVg2!L{i&x#k*LSEpcRYd%2<59Z*t1Qg8f zC-;nsDF4nrWH!iB|2_c~V@brZa6Mc0#WrMPq$02Fd)I1H6&*>80dGE?Khn?v|&gD?({+A4T)ZyWx zG_d<`4UU{q!&BV5ebIXh?Cb2LId|lVR+%byPbsrfwMpyOPs6!l1xU61uw%Iq3VwXXB(DLoQC$y5&(+ct zzs>k}>ry7YR*p<9(qUhUeWkVI|4{z4V7Na$hItvZ1M{Bs(x1~(sp8s1Zha92U7_Kq zP`;eKDSHm4C2@INpGrYh`~-SlHUez8++)N6t~cfoiVGL!pz_rgGO$&N){*njm@f@w z!_ERhs*uf(*~A#m$zzd$$a0DRp1f;^a#g+J;VF#S7&qaUx6nPuv*WoCl!41H$%uo$eeOvB)kd@6e(gKv=jk73wraP#N?XKB}Q-MSgru|tdOfOhh> zCmUzeU;McyYiNq#6Lr}=8$8!4;j)vHz}(e?7MJv}lAQp&!2l?lJc6#%8<5O7L(M;k z^J@35dGjPTC5R;OKwhuFM}^>%jYhz=kAAoUmudK9?tyiU&i=7zstBb9ikrP zHk_~88LnznlZy#~c*$-yoKz44thB_p`XRWbR~1Xke7PK}80c~t?$_(qR;|EP?v0-twVEv2v1UtO>&6M$S9L*qeA=I_Q9TM@h~UB!RPnoAvp{BXJM};2hlX`~ z$m986S$C|Up(4(-%5J zCWb6Oe2C7k3L_8G420~^8cdHm$KI0<#)hinOX)1dVH&Bvm$BD=Ol z4G(ku&f2*v;nzQNx-bMt~=U7Z=d?=ls(alHbIbA0gLatOk>JlE!G z?zwlD%WcT)23dIrQpE8hZn{*H`Kjip(^tiNnB+ia{0#!x>uJ9myT>{$QRfD(Y`)R<%FwE+IPkIKYU~I`1)OrWRblnzMw`)KB{J;X5^NbW5GW-S1 zrpi3p4Eq@U9je&=brTuc;7GT9koUFCG!^108<%(?}s9G@|wxKB`U@2xNp@62j7uyle??|xBm!l(iX~Ozot6p zU0_!437qZC3f?=#vtK#BZ~8ojeJ+;CI<+*i16D?~`_D^~xOXMYd6|z+15=o}m9yYe zUow@Ol7o95#NqUsTH(*soA^S}3+IY+`Tl8nLbdm4IEOn!%;=Q|#}^Z@uU#CTjLMU7 zdXn_uykaI#dYH`Qa-)JzexxY)7(Jvi9Un!l0;yMD_^z)GLiW;N+;!u!kbRX-P0pWT zcl{g@in`{){>trq{TML;h_ZWd+#wv9Bat2OKok(Y$ zh=fLwWSspb5X?tD)28Ge-i1s(6hBo7>X)T3@3lG|y1EemDXgT;b#*lP!5A>|oQxfU zM0Q<#8JGFB!RU@-{C)>*{5aMJE${AU98VpDS!?1L)6d=@?p1_33)}c_T@z^O;3yl$ zdA>iD@5LiG0K%Vl(Xf#-uwXvF@YGIst(F)LqYGl z9~)g1DC~;v2BXrt22dZ|bLCLkn5PWBd6y1%d-7~Lak)|=3M4u72sjK0I&G)zYj3c6^OEpt_IW^g1*Yea9dmF)9+ioh1wW@GV)&9oDreIL9Ti{6@$mg{aHb^h z($y(Y5_KKMwmFfRR~d{9NhEI66c=z#!NZ>GN#kW(I%%kqTI^njj!WOslEQx8Kk@4{ zP_P(3zIR6bN8G&jj2mck+2o~(w?VD&2n@~g=a}AE5Yb-^uKwO{2F@-Lg(3{eZ>gdQ$L;STqWYT z!+JP5hMOVGE+qlQW8j5NZP~9#ZGPy}*93PT7fidKs=NSG-xH*~(y{5JwE@9ZY!79FK0qAvy za9M^l(z=p!sjYUxinOtG^>RX;zT8Cq)tywdX$n=HG7n^*iBN+(@?2T(Q}vMNeBzVx zgB)}?Pu$mhB}RKsW2Dn78u(%jtQ@dp;2Uw%ak`hIN#sn!lWw>O9csNSL_ z$#c0}_hW(DfB%tj+_|Lv0vq~&f*MZ=w{LD{ zr|-E%b7z)Psk{iTUwf6=LoTpCZY;r1EtyPN#Y@`r>p#;*2S@7H@|)F!B63+R27=3v zv7a7uu9vzI#?2kz*Nj@S;JzsJoBxH7b}PI+rwkW$b~pG&Fdq#~49w3#?A{X-UKYkJk8eWk$aTPtMqTIuJT3*gQy6S8D|D0wRQ!n*EVO-h?DLjAWqrg;a2>B3Mt`ZoxT zIo?6o3OBr_-bwuqra@zE4Cc)`LOp0Cyj|;uE=%<}9_j>4H@e2V95xBYOAXRGj^Xfc z<3q-DZa-Yy>;@|5X>$U0-@>YiuQ4&P#zalY-!`;t-i2R6~Qf57cj#9vS_0j`ipI z=;8;AaRYo1T3Z>hEwvSl^4K(t=gvxHg5Ala+YCyGP#QVSm)nUplgC0%u#m*LjYAbl$=1L~qB;+>7Ias0Zgzejh1oranpfxa_VYb+U zgx)%+OOIz0ccsFk{JV6YUjR;D9>I(G_?z9`{S{j|e#`~^!s^5?>O>{b7RIU8kkJ)G zy!C55F!UpLzUHcg8n&TA)z`z+x2Th(Yp3&X@ivf+=O@9Q_nfQqa~{#3_lam6Di(Bi zP9+DuGlcUE-qO>37GQk6iwL+(&I_Nt*t1;^+TIS(y5j_999a)9^7PSj)fK8#B!c%0 zdr8#IV-$y**}B-*WMjG{4#+E+e|*DbM2;N7i*=)uTe;KM^%FS4AybuoL@v*`6 z0p(j*p@FD3c0A9cYIUnH@lpjwy_Nw>2NlXbNF}fBXG8g$6uP02hb65B(8hJN6n0s% z2ZS5YjAQ)1{pvv+^CX#C%_^#pq{lIK{*bphs?br+tuHjCF#S^{$TTXk^6nd88qbgX zm&T&`jSw2_P!7tCN^p76S0cTF5X+<$j7|Ameh{~Ra(r+Y$Cd<>qxue@u|fm9dN;wz zq*gr2^$Oy*rqC0!^_XifBgo&6V`1ZUU2?@Nn7ZS9kX1}#qORSgZ^Ku?n>!WEaVbw) z@?s*asE&lKp1?jE)hAtCCi_r)Je4?lm~uY~Nj2DkFYH@LXa8L&+vq@c|1g8Bi<2_eULa`+>}mnkxqGS@XtLjUgsZryl{Cllx41e*xFc03OznsVKoid_DQ z90~Z?V?;$bSD$rB6RljEK|-q@&|4?c*u>S}gu0<~QRee|h};{_bbn|QZgM9$sOAJ} zX5B=|+Co@&wV6G)^$^~F$larVX5s_oLe7`1&sZA9pzj!E_%AG;)o++doTDz|vTHs} z)X;R4vRp#n9rER$(>G?h?me#XzJk(U&hUP^G@@;`2wLMAs-XLYG<_(fdZ}*k>174o zTB%G%U%F7Wu59{o_cr=pZz`KKc!Ryg@od@_)sck{37++p1;dlKsFRWp&a~}homQ5S z-!jYC$&q)M(<{|6Xu^3x>pwzb%66i%myp((#|q~>xq|uMCL;%6g4Oq?qTFwNET}lm z=$JV|!~GHF*-g!Q#e25cO}?u+RiQE5pp1O9Dh^8 zBKlh|fi>Y6@csK^c|OjHFe+~kIcr|gZN=T>x>!G5=Dmv1mAGW)a{d!p72pfQe&bQn zZ6mIIuR=zC1%Vm&EZaoS@}?@=(XSe1q|ZpLtOChyzRKvmc&3H!TJl&zpu|URL0^0?MR|mTBmH7mcaLOGL|3u)7 zoN4&j_ddzt@}p`_BH*Z5Mjz6Ch%%NUlDfL^xvZA{vf7IZV}4QT$Jc2@-6T3+Qw>(8 z{-W>C<&xMV$KW)78_8Bm5$?;IhDr95!Dmwqd|R^<{$#5obJmPVTp9;%YFtk@x|=@c zJk-G=U#sU#kr&#=tfO<)Q;6b+a%e6dfJ2;?e)so^a>m1i*v-_)XuAz_$8UdK8zRIc+O&7&KVHn=^}7a zBm;XdR3LkLRCqzz6{an;z!CbEOwg$j+C_A;=PFXEk&_uZo!`sU4&+eX_;Czx>rx_V zrHh}!y5Pw4No3&GZV*Wl<0ov>z{k(lfc>Zx)?G|&<%pdc`k4T_0oa<8prSMN8fIjkZ9HoLpYs@hsqn%>P%|LR3wRqX-CuNU}F z`p1FzRF2pCq=))hUu3tu8WxiOtOc3I9N)stpWi>A4YrDQu&_h}R$sl1?335@$A=kY zmdTE4AMYh_ko&HIP=;2fR^-ICcq)}U6V#S*xfQie-V`k{vI<0McI};q=k84+1q!M3 zyx|=55cTIv3~S>4sm-)8p5udMghEpT*FkJ}D9o|rdVoee{F^2Mn&Zr1F#k1-u3pS* zVN%G#@BO%A$`x`-cYw@&^^HC{l7+Qw50&uN!zmG^a4K*w_2!2_{6s#HHy2`0o(fHr z`5-+0&z{RxcF--KlAtUv0gs+4W|kaOgoNqZ>u5xM`h08_G=R8BCw?! zr|H45yz9&~%W%HJzX(dC-qM`G3TEO|BT%j_B^lpO5!*?7;Qi%t;-ZxR!RB1vxIB{x zKeod2=Ubs>QU>kRkg1tnH;&?UWRQtF9Y~USTJ{M*cMp{-`3pZBOavf-ZQie}@hQ7771~dD2AraM1W$LB~8l zPNj8Y$r6>#s8nyjoR=Mg1=>#;cr6Q^-)F$p1%6O9O#%A0*<(yY8E8x`5W2P=rEwG1 zl7yAIP-2<^%j){EX7f|73sS_&sRe^AmotdTm`dH^jPdpP=cM$}B9yAw3FYOt8SOoz zY&9>59J}vMFSk`NMSX6Fe;}28WXC+o~`af1)Y3)STUU(NQx%gpK*Tq zK3RA!K211x!vbce;woqzC}65&lVI|n7Mig5HItsHO2tfcV6#*QQ5so;EA|@+1FyT0yWBp% zXMZPq`9cD3*}i1V4s;}8l6jcCGMBv6yGN^eDa5C$hYIHf!FEwST)Wr=176HQo16#| zY5tL27`KYQ!P5c;58b5GeWGE@rx|$r*c{Zmlsn-Z^Wi&RyGDx~u#O_$OEc-+Pao*W4SzbKIhMI5I|+3!4GQdQV#rg& zIdC{B7*^CSW%PT;W7)GX;62#St;5O+_%_1&8T=~Lp&xzNR&p1K4nkG!WFSPm?Mvlrz!oF2X zQO{JuEVW#6;p;-iYtwFUY>b9jiGS2Tm-|SkG~#n2n_l>31(~z;F>}rh zdZgI{7KbebvxNoNb+U$=`(CB@Cpki4{!1!4eH?g&ND;%a7O=3}0)AcPvbr!!czE|2 zqBMRTb8ys#9g}B3|NE(pb&+i>|J4sd_Un-Hne9ZgQx7~`PT}$M)2aEYOj^9?AN@D^ zC0Lg&$Fkyu;NHBJs4C6H__YgQ%8Qj)e65vKZqGCygel+8&poj71l~l>YnwBle!6PG= z*|>W_wCuSJR=6x;yU+U3nY(1DT(K-UheCRJ(_;MA;(?nt1q++D%YpsTK=P~N9WD5& z&$RMw`iK{x)9?}`{K5AZ}4`Ap}S=)xi#20 zeEN%z&M%%a<7X~GTeVWM$VUq7ei$$gK0x%M=iA|b=K zV5DIkpJtU(yNRN#_ZS&yskB3BQzz{2|BucvE9KmJZaDkeBv2EV!Km73VzK8gGbTwL zr(4(4=W1SHI4+tQ5(xl-7UyS@nnB99$%3a<4IlPiBtCDrj4?OEaoo0zR(Y0^6^|{L zMb;mf-=0IfIZx)mrDw`uPPp^33rATtc_Q9@Qw&#|w+dDq8;ft=chKbObL3>+S2DOF zmig!v3McU+dHX`s>|(Gs#NNs#FQ@9@an(>V>-JUJ``%UP)I-RhejlPb$h|v9W5~H5 z?Vvl|l?a?)3e`E5LC^Cq%=OCx_TK|hG;R;2gT|pSJ1~Hm(zl4%a~b2eQz(_4G6!3? zSVC2YG|BG=6z>RQ_76>md3uS&kIVQR^Bsdb2Qu+nT^`+Bvzy~g^M%JT0FMZi%LMbm7B2y(nG7cM&| zF(D%=@MXqi!k*8i6}GO7LHlRo>a7m$A;ye@ZaP8ZCU`2-PMS7crN487h}b4;`bt(C zztneATgMTau094OdG{bXFOGB*Xv&H<8sgopuQ`OjPpIDga zEP@v~|F6#sJ^V170WY~OTU-5gB4e0B+`^QwzdRVg+M6g{pdu=< zRNeHJP;#bMscZ=bn&bKlD?v^+SR>$42tw^-_3sp%5~JN~CoA zbM_URi8X=t!nd=w(>G&h;mnpxB$D&)-L_`HO?L@s<3tR7wJ2GR>-^%hdcZg_{ZTexwgg~vrdT9f00}!u4z0T zvzdzTzUQFtq7zJ1i!|P>;k+w8c6cJ-G4UC)p?8>`+5ci&{!MTvvm zPmx8vk~?7O!AbOOf-(ja+#%u{y6J)TMW{E>OT)@+VTqDHe3pI5J`ia_i>7q$%=s+F z%QE=(ToZj|?ForZ(bPuwJ|h)12_$tmwvoM2%_1||KJ0h>JIz`iIh{RNM zok`GKH-~R$r3i|i<8V*?WNLlH13M}+xV_#hYH6Q_Hc~60RR1iA_#lT<7oR4t`@hp; z-pkm6=IjWTp@?J-bAZ<4h^>)VfQJnzzqqPgl_Ne18=<|6ig@Rkm!r=-t2>c zr`}{|jGgdj-2m~|`A(L%sDcdV_SJdO$YuGXh3f)2cS+?0=n@&BllAw5|IiHV-0}+r zS_7os$_Tf*NaC@q67psJKb-zL6i;{!(}iZ!=&pj9GJB^s)Rbe2{7F53h8@&ph_>CA!ql;67Q<_ zuh@A_=o7L|v}?VE6` zQw|K?52Wc%l5Agp6IhEUV!%SpnoGS20^Wv&P;*KWZfYE$fi;Za*rE4)i`_bOWSIy# z5tIYI!H0#Z*S<0u`zArhx(1k~)k-zW%IW#RDx%rKWluK_lBj`e(B-QJFF9_l@3~;e zIbXqUnx@KX<{bm?v7hOKjgjCyU0Vpb=a^lWHAx@u1Y7^;6%*^8L$+}5wr4L(ga=JC zVa-<_7Hzsg?w#PS-^NYRje5jasu&XTt*6_rB9CEB6+icPEANj+`P(rCzXC{Nm{Gzbs91t)-Ts8oXJV zXBhYXuZ(q^KHgeVO|}-Up`EA-OXV)`b=6-nkMa$0BFCyi`*8T4T}{J-$Fg6x$l}>J z1&rkfvoTI@>BmJql+Np?QXVpp+oc9;cY8p+(HUa?u8L<+Wdw#AU2La5!JSf4_vwo!9ag#4M%{H${Qsv29R#pqsaHssNt9wu1LhehbsHI%tNU58KjxjUI7d zgNJ7n(Q*BQv^Sc|n8YTKc<(_nQF;yAnz5Gi**yl?>?p=gzfM@b!G$Weh*AGhSM1CD z#01XFfP=b2U_Vg<#SgruB9R7kOQQpsJ*|c2Csz^wt6UD$)r%Ia<}x0f<9fq+M`mi7 z1sJ&nkg#n=uvhFfEz8QJOL|wr!mQ^cPIn`je=isx8{1=7kAOX8+(sS84$-_lX}&-YoKM0_Tm3x(#Tq+v=6)jKzn zpY=tR=(p>W;?m0~d#M6C*Ia^S_gm;i!FbG%xKHvuglx`1EBx-)%Q5sf(vh_tT+VeR zOxw1ObA@pov9fLO=SMt=q>DjCdouYhC(3>nJSH*c`ti%$6R=oGk{vm*pX%H@g-VCC zAUbJ}psik?GQ+O8xpog4CR4DRvlKMs3xxAe>j>5E901!`N1S&%i#;7FUi0kW0I8|? zLw~f)g_KQ?1gDlOK)%)j{g5v0NX`OAS30v_Qnx98Zf6usQc1 z5PRcK#APypzIVSyj?d;cf0E16*CL4edhwo(b?q3%V>;=Jt;iRPRFNn8mNKGPd;zI zI%Bmj7cv$+Kx4`En7rr;T{!CpJJ=meN}Rjstg}J<2l*;6-r+jbC&*I40WVr;v_&6QqzisZM~C-k(@lpi#*5c8$3u}wmJ+v zlO~Hku7IN*6+-!^(aZ^b?mRlo2hZ;tVgH{S8CBj-y~Ao@*;xYLjxGo76%UDx#&pbI zM{$`6p z*N`vW%3#}WNAD$v(cp|P)ZBXo`4&49HO9ULFK}lOHhnSViGedd9wUp&3-?mD>Pl$SIgWwe z6Y0^MSpFZU*>w3Lh6aA@;oD_ABInDpi3ayB2{%q-%4^rtpovN7+G&fQ=jW4rgA2Iu zSU6MFl!RMnD8WZuz*lp6#D3+8z)+fscj_?XAnFN~&5wx5*+{goDB!ind5)(;M`^Qi}pa-GK7Bi^j~pca@Y6jFiYf5=o?(C-BonDr4h z?)F=?}q>hn4JL^bc`)Z`tJ*4;XCQ^DW10we=BV*<5*t&2B-27gRZtptC zturUHZ+?EFrQc<7AbuXKmFo~3YS5*Jc09r)yWM!avI>Vkj77g06Is8X#ZW2T5WbBSx7sJPaXn71>8+;=G&7*l zC7p8{m{7;ke^l{9GaZ)xLf>aEg%fi&f|q{?HIUd#vt{F%HrGk~sh?LuuvZWH9K0BG z>s#1XvvTz1Sn$JZyUA_MYgi!jga)d82mMLAgyWRf)1PZ*;MYZ|!sxs;#Mwy`qMDO2 zA;c4_^Jm~n@mc8h&zjlMH-&nANoKn$mI)pWAHYUmGvd8d0ds495$mBi`Ymri|EgCm zwE9{?Te<|*8GgYtS2iV?Ips`Q%}jV+vz(bU+m!LG93uPr)L{PGG^(U+f}iKrVch4v zg4A_E7*ncCR_S@ux32wk>xeqknixUSwpV1Hw}Bw@Q6!m@v4)6z;@qxt-w~H>-E`BA zTh#RWbgVjYm3Dj)($@FZv|>>f894ogY!pmlzv}41U%xkW*~jzrq0czT-{Qdp1#*2r zFA8fm*w9Tx8#dSPpi|Z*3zzUZ*>dNfG+@94x4j=E58oznEx`3?)1r-HTO`QgqTN`3 z?=~uI)*pH!bYZ^93VGUu|5Cy8sYJcK-Q#@%Y}wD$A`{9=Cs_H9n5pKoR0(Wxdhj&s~L z=Jt}RzrWZECmgWPb~!weir^oe%d&ngZv08pRN>kcD^z9Yqs^4*xKi;Ll?qH?FC{BM zm;6?GZAKscmSF=hTPuv$EI5%4!9=S>5 z-_wi1jHie%r~Srhnjagf$iT_K0b*-B6AzyoWr8neVTN%L95~C(xt3Lsis#xO zrMMo>-~LVvPmZz|AAF)C| z>-KQmgG0ngX@va{GaFoLO6g@d&IaFoD@=P}MTkiX<&T)c?rXOM#+FVrKVuTC_Bo76 zjvQ-OY%0F}@<^DT8V0$>Hgu%rI32cHPFHe05oyWUa5ml&cW!NE)|B@OcSr}2ss8$8 zjQf7H%S{)GNj1}>A78V<O)tQ>@$$FMk}8jm;;u$+5=>2lqUA9em)l+yK? zB)rsOH(wkTo-A<$8D(u$@=Jsrp6YOL!V4;XY8x6ks^a$DNjUjqJ6)}3OFVU73X0!0 zv46u9F#LT!IBz@)3$LE0N#@BIUucNiGga|n!w;HW+0L7&l}4Mq&l9TrkIu8pz|&z; z=GToRanti$5;J#9%_fOd{_{ZrNq?OLLEla?)zpCgJUWXETa++=rY-=^g~HDoxI4j02n>x*P8R=7ws>31sfaZZh3fZ>hZ zSw??&?!wa-O))v-D+#w*$aJP>(7oG4iFws1J@r6}x=oA3m#!-CJUd66L@#%TW5NAq?po<2Yo#PWes43QEhT}w8s1V9 zrxaXW8G`A1BFL6Yv+#0cK2-a!*RxCji4wt(N5YU-~}NOj>AlIy4evOQ@;-{%5mH)T|IKPe{Li=*hb&+=%n z-jE?`1$f_M9>>9MA-eON!1x$L?)}-1qqPTxX%CI?V~jh7Xx*X%tJTQPMKaVyWd@mT z_mTvEH)HBi8+myr*flezQ_U-rLClWh{@P2RR`~<^VEugImcVQD+m5Z^FK>Y2S4El5 z(sFNF1r7cEoeZf2;bud`I2C=oSU4o?6kmcz9{r%-TeT~nJTy#Cl9FIMJ~;rc#&c-a{Z#@@jh2b1A_Z!+%7 z8c%X^zjE$leJol&j#bPIp*eqdGTk#R`6syy*Il{-Qf{Wh)3$u3&)Xiy-ppVOV$xaX znz1;v)R!#do)IQ5efW=+w_ru@I9$!2iKSi-*aoo@;xdwfYIqvGI4=5+UY4z?y~5AZ zUxr|JJ9g)Q`aLE#*|rsRrh~SWK-u?l3Rb&!sCwQi<{H zr{w<7dRVzZ0l!a)pqFY#zto{;j2nuDd2_pkh_6?H4$92j?*;h!O^UJC5l~UE$&q zGdLDYPi)Cn~`y&HK2N z7)>Hpnl+s2+JW-T)q-oHU%;*Z%dTsDW9v34Cy zO1sUIK7+8@j*^*bgN(f7{}?*YKrY)Tj3XJ5(GY1Vr6ehp@qf;Vb_z*LDw0GrkOmTx zy)sHlQ7U=07yi#V4O-GN+EHlGp4z?7CtrQwzVGLp>-zmZQrN)`{Oi|caLei=%T61O zIO{#V8WVzEqm-~&B?QgZ7jaa53>)S2;(6{-*d^$jY*hytnCS)4w7bjLFKq|Z+~0y- zMvf3|ns>n5^LIG=+Z5U2^a{8i>CE@F7Sh8mXW&wVBlc1IB-=dq8~yr}Cadn1Oa2Rr zAVfPI?yrpH@EK-&z+xW^aqTDn_bo-3>UI?Fth^}gq?@U5j>JY?pUP@ZL=TS`mld80 z=Y303D0%|2g`?yrJiinU+l=Sk|DM3t9y`Q6pU=S)?<{WL*pchc?7%r2UdTJ|yo624 z6Id_Zg<|`S!q)sd+%~BIa`zRZ-S&yx{BAC1HQc1zC40H*mmYTQHh^pN+bMd37w07R zVU?b?*#60vBQg`|NbmwKY`u$*Z)$SrsVdOAxSP{nOB`~|Ot{-R7E3BsA${-yOg8V2 zd5f0d%%lRDcG(E9UL_~(7;oH?`wu=AhT4{UFA_CY?vtFeW9adSAimoCoTWU&PAN8C<$;J5D@t3M^Nc$!;|M zhKIc+FY^>5-qn9SPgOrfm-G|({#{?FTD+Kj%`U*w1(2W{;D12^i0F6~74&_ohQoRX098q>X+SOcst! zcILpHuZ5<(d90gO55{X2W9o~JSeMq3bwbv&&UHOjw;Lqf-XWa>{lk!5rgPPh>ZGxeD!S&vhz&B{(Qd-;rs+WcZdqJW zp>*!ryJzBx^*(sD`a6|74u;i&KiKLYg15uF3BxZ3PzEkXi>GsFcGw?z%30F>y76yN^7gi)LmN}$vf_=-2_^#|LEFD^cZ6VVscZ(t|*|U?3 zZs)+mS0mYBbcNVDK2mnBa0`x^e~UV)Oyqmw5!jIw(>_0v z9adXIe^2bf^U^n6^XfAgRQ*_--20aN%72eR`MM3<9-MFcy@wV4RVt*32g>AIUhm}m zh(xeDq9#0_T`Y?`Z6g?VQNT&#_CQ&*olxK_IU34L8Cr^Q&$6@S!IVMuj@@{8#5KN9 z@|pVFQoySBOdew-2E+!L?VsJ(*tO*=QPu?3zt;&0j|BMukCjG`mU%uaEY|i;F_}PlvVWEfferYPlS9?iJi| ztAZkBchr&iP-Yh|P;rzY*Vbupo^>UqJzl}D*2iI4>O11 zBR{iwFw{`G|KFDhpLa^U?8+o;koY+_*BgWPd24KHBI>^LF?}Dhp2G*4+H~n;EDrKa z=A*rJdDY@S5FV(*dy<2=Tbe0&EAHVVQunV@&k^vfp9a=G&f?8h<9KV34jq0u5TD!& z#wAKc^r>|=E!(^iq@;U7?w7^jw6Em&u9T&N%Eh+FDD(YT8b1(m)c zl=yE%k68n`HnS4#g+H>mrq#IX?qe}{MTL-9`LIM^lmykk{Y!h(ri z#|nvAc-=D-m)vgz!#o4-IM$geMu%Z?1;U0yE8ypvyVRIxgb~t=EmuKJo-$x8KUtg& zvc1JB)bL8T+q#iBAed!?T(c%_qXEY@>`TRwb{1nW(M?rIT7b~jpdemAL(51Oi0aDqAf#h+5U59TDQxU zR8RNBtjUDp*g|oH>UhetlFN&8*1*};qjcn)GJHskVy_voJk8@6#0TCeo$KsH2lkgh zLN80+bbTvpy6Z>`h*)m>tc?GbFBIc99H*_qO|}CySh{KmN>Lmz*7l)s8Inu$cphfl zUCl%1DTo+e^01H2bhf(*zR!wd)oV?3?2G|k?`9@;yy?VdoqSMt z(=nk)`5GxcUxLE=NYt-ehc4^3LtC|@d{p#3G3e0*X(v7!vkm;@THr2!d@B`9V#C;G zaT+cCBgh*&pW&GKcW`RiVlaI-JPEJL;Bn3`sSWsAJh+mr;m`hppwUQxu@;2 z;zWuZv_bf`AQ6Aqd0>*~Zj4-A%-)B3;VpO3`o+>L?js)~yxBjUTfXQ#RVW>HpXzm$b=^)SJ*TR6!}j3mdF0#4gMOL8O`aqQqQ=%AI({nY=- zls*2?hP*^l7#z(X2Kb6ZuWzgDc1f{#W<(vSpR?uicbkRlnQGW;078ns0(S{(;Di4(spspy z!olH-VRmB@uUWShqf1&z)$0%qdX|YF(McHb&6Mr#90gHV+8s5PNdDl1lEX?1bh5Te zT-H$Dq}xwE>$weYtCfpJTWev9)^HxD_EGR1tpgScUHcU&to4j%T^bK3r(Dg0Fs>Oy7@5 zUdps<^dQ)m`#pFkEC1+<`y&5?!mo)!Rg01=A<%^khISS+PI*JdTUdn!l^0K;Hla_;duO8TK&ocZ&>}6N9Xq??O(Mt(`O>)$#%I6NP9C7~+c)|MDmfZSYz(2lP{9v$=(5^NaWK99$^n_!S z`yy7DHR6vL@U#Ss$4Gs>mFZx!K!I+|C=j&PO8($W(i$Yq0zWzrf&y7Sb-%^3cWI`$ zXhjNLlXA^T{iV5S^K~fC-Oo~*i`m_PS2kq8;6LFq1XXxql`CIWx(&SgE3w6q=fa^H zU%Vgq2p%6Y2jgea<+rTM=#pBLY{}_$H26+uj{h(PyY%TG{)tKwebx;Ss?RLJ*IH2! zqNvNIOHV+Lk>;#mJQAz-uC$#LKZ--0o!HZ2xLjL(IW96(7&|XK0^3bY@X^_ywhCJf zc<=lY957%4yAG7{TBkpx`ry*5E=kcxua@?zUQz-w`1+T~==}2H_TKpi57DQcv zvwySTgEXgbORB>tV0s zcK)Lt%wt`bV)8s?u1Ps07#H{C_Cq!Dy!$rnw>}b1&ozgWXZ0cJb*3;w%G;`E#PXcq z3gXN=`@pBskFwoLpkYZX%xR0IL6f9C^(a3MFi((I$FJfMzOU%=E;}w;9LR5;Z-d0p z|G@D=ESMhhV9gtSdHs_ILbUxsQ5fmN6_M(~%@>|n-}DcBG+L9dXM@HQ4kci&f{P!fs1*vfipq7F(zC`sZ$}So@81riVha=VF#Re7L=B zJgcsJEPi~`gP-fmg*DU(y+Ruxuirg*6VMGOcijc*UrYJ>hD7<$0S9SP^)R+NF$V_) ze<|N1Il}rVD8CmaKh4U&KFV9R&!ETln|6LA%-5C)%|jBRy>=w|p4t~a(o zsKG#hp8Ml}9MG#&d{ zF4yl~25%Nbm*E3FG`{61@g=LI@AOo3iXOsK&upfu*tYU0>7CN&fRxJ{JrMuQvz3=5 zhhn0!dTqn{mz$)V#VI}@ z&5Lf27%eyydyD0BJm}|3Z_o)Ei^FDbC#TL`vBMB|LE%OW#M-=rz-e`;^|2F-|2Y(m ze00&Z`iaaxuqzc@OJcKSMW~ZnB5#O@6s-DB!gnvv2>TA4VbjMyXms`@>lGK5b3joK zOqJ?9AFtKHkZrHrMH|+rwPBcIzfMa&Zl=Iq;1BJ93F$4xbBQl7l^4U-Bb;s9+zhXEdYNG1fP{ z3CdZ!=u+?mxc6ESPZhm~WTSXIkzEDn#=D885xzpHYaH*3)8j_?jdL?3r-j=}ammU5 zpnH3n5aqano}KT&M`GJ#D!r3XD`^j%+d7zoA3x@6A12704>w_K?*Lf*E}UX#ZwC3* zS~21y3Tx87%T`{U4?cd7!@y;m_go#aO_zyzu52;dRh#@)RUH+-NBB%H{KN@HNylY?ep9kG6H0m zbLfY9cRcv|6LnAC43ml)#V7NIieS=_e+^RL10CaVp4v3Y_Z`Wl^V;P*-gm}CTW!*Q zIvO&kkL47V@6>oa7>sXD;^#SWLMw~~&ljs{!1iO(Uh;-;Eb;+#=#uh`v|5Sh{sc3 zy2}modh_Yr6Y>zRXo_8R6xv#ESx=H?1iKn0L2f{*yz1{!v3}EcL1R`oK0LYr$Gi~u zw*76`cRam(+QvoVSosMs4{^fTnsJ!%*90S02O*x$XZ>o4YaSg2dTrfMZ=(b6agb&L zw$hH_*;(xN%T(yW_PFOt8*xoE%vzJdzhWC9?YFl$Y<7SCIp95axUd(e^e^IJ=SJXy z?=50)j~Q^d(T-Poje#@&{SccvtpR=YW8n9pBPJSJ${av)P;R$G`PyfoQK-*%zI4ZK zWe)sc;{n-(#s_5Gx}PuI&f<(of$(9-O}O?{6E`ac%IDo%Ma`=7*zx@%2+)(V(%oXQ zM5lqiOWkHQiFJ|w#87Uk6b0?KlCa;4`!K?30Axz6qU7rucz8+!^cyOQ4SgbM+O63@$aMIse>I(>+(t1uTu#r3%A0n zx=-@veUV@*F&4vXVzDMDf)8r!v-Deq>)}TH&G#6p ztlov8yN>c*$Ez@MQa1c3n23W~v)Fe3C_bg?i~ZlGK?f^MT;}u*-hERc*Huk0M#`dg z4Sp`$J=>74-unVxwksfFRt1_#?vnU*!#HKw3;8HZZLa9^pRnn(ANDhRWxMu~D@EH` zP?4JCqWtw$STo8C2e&8+BaOZ2_0};g%NHe4#@v~TWqA9j#?WP}pBnM-Si>$HK z29s9S0ZUwpjE#lzj23%waCm%lLVEX1qz8|alo zH(}>;1-4teTXa6Klj2{yz_1gaM1?iGxZhVNT({*1)fs3AtE0Qf6Ib09s&CcOrNAlt zt!E79_E$M)H>L;_x_1EA*5=6_z$G~JGmOIgS{`i@`Nj%)b-v2qID+l!_I_Lcf5dbX@?#^a5wPs z%`{Fm0WDsh5G*#gz_{~H7_>{9C)O(Q823=AyC?Cw!&+?5&6-7Tr1Nd`*=U-vcPsf1 za^&-e0Oi-4;pU|ba9KWrAIyn|-A7f>WoSK>k6p?WclN@*`9(CfyDwx$xY6-|XOOk} zfq2081#O#KPbGI3iJkoi@WFNmT<|kTSiatf-wjC-0-OuT!9zxgQQpF_z+Zx&N;cR= zEh-PQ(-KmCTmgrn8eD1=&M!R!sO7Jn7ZRfko0C6;DbVI#b9OcWelFL{6(^(2vFZcP%e>-WNHmrYQ+ z-j-k6>*5TV3h$2Tjkh z2V7s!9oDCB=Z~~pST%nhUa@&cdG`<5E_oZy3ZaU)W!`4tTgn_k(AkXvc1NM$;Tw6b zkjyqcX2IBL%h5#huMFE}Qnj8Txz0%E$zr zwq;ZBcHel89~6aVj>AyruQgc=8v}>Bu4ZvWXHZsNRz4(r9N&AQ2TzChgdS4QVhz}n zZdwAp`cLXAuRSmBhG;D9kP3SxE^-I&MYR7wxxAxm3SU}lP7ymb=u>?jA6?UoIzMjH z#)q4sK^P;i7-^1uGh*1FwS@YG#nG~;o7C4z#;Vyr;dsJ(==fAuz9G3Q)SD$x{Xjo3 zuTrDc6Hizhw)PS~8y*w_ecQ{cx9D;GHw!^0tAJ!)Q83fkj_;&Shx(=-GG)bALgjQF zakGyF?7r3;FYYUlShXE6V52h+`sin~eq*xiQE(odohRK3SNh^iaW#ZUnfR26`rL0@ zsSq7}mTRT+`q0=rWZlah#Dme|&vEa`{gEfcsHfnnkR(o?@=LfHR0Zl4X*8qDJu>Jd z;4$M`A#r!8%tgrx*I(|2D*Jv49ZvP*ARBZ3JbN{Ld7MNouS#HDW-nf>q6MlY*JxDY zdi>s)%KZZNVp+r`P&uIj_D|BuX-uYkwFFz$xF>lGzPsa%pF4O$-X9uuUmFTbdSSPF zA8cDYs`7>A^K^sS>E#h;zO>MtJ;YR~@Yh4dIRRi7*AWl89;Jd#&%xe%1TJ0e&w2-9 zF>C7h^4u%4AQOk9_r0Z1qwq)gAUcrp%%<|3w>f|j$-FY-25GDu!iPR6kF^U}f)-PK zSh3G4stH{xl>2W+=Z)4Zp0eVFsztV?`%`hozBJO9GY@f97DwK-p!v^g#UVCScKBpZ zoLp!EM`w7z+vmBc^I5bxiJ^jvuQKXiaN)2HJ$d%KML6nazHry^Fp*~j)hND)n#Nc1 z<>RF+?kXKNFuu(LzbdiUwH&d-a%p~k$rd*ZFQ9>nWf)3b_{OF<`cI)FNCtJzU(pvI zN%^OmB>?a7ySTI4IJl`cfX&QHP-#JcZ1F%D=V>=nhNUx4PdrGgb7u4P|2p!|*d@I0 z&@y;Y-GDY%9$!A_A?jc~Su%c=v^UZU3WJ zKa${3az`O>lmb7f?Sft=Cm>mtK!pW<{CUkpakQ%zZMnHy^35qw)YQ{F1y!*2`#v%F zS|L}OeWvb5R46m2Mc#h;H(Xqrha)xo@WewcnSKW!^8B}f{oAv7!>dXP`89$|ZL(xm z{WalUUIADej)0xI-MJv_44WPJ1Ha>0VrkjK8An6eY30sfsOL*MeZRxouzhlO%ZKnj zt~*`v>@>EoSy3 zt%Fp?ntnF2RW`Z{Z>&0_LB}Jw^T|`eGWjeOyEeln)mYg`1y#W>a0GuF7$=lJ*-9&N zTscr-4>b%)a=ukCfmZe34)I&N zquV?cx%Tc5`T0TLp!9bkv>!IW%C{|IuF`$6r_LUE<{K?ZofOFnn%~=+4k{u4S2Efh zH;f+}?UUR8+9T8K@fB{$Ps6fELkiO!%Jol2LU6JNXX$stR~xNm-`eKkucWKI$#ycI zd}n|MORtI>25u8hsT#8uAIF;??I+8=QR42tr_j66hMSb)al1_| z{0de;{huoMT&0}dlZk?+tj0C-dXd)p*QB9%1Bwf7@%Rc&$lI|KO1Dqtt@YR8TvjX@ zx+`J9^V_lYQKtT{p>hw zsL2t>O)rLjwFa0YOX4p3-w2^4PBg4r2fX*{G@25UMW1dsu(L84< zGP0T6EMJJHH_VhD?$bq7dTx&559w}^e;4k{rFrLN8=8GtQ{w0?5%v14#pgkzc-e3( z40rY7g3eBIyIXf@UGgtjkfkQSeCZom4wL2vlg6Tv@ldvX6a^#gYvGX30*POC0hVSv zVa|fxG~=_xnR0O|-1+8z+@{!ZRU`u+X zVB=JT|0yT3Z+#*+-!{T~HY;hB9pHm^Q^3!EAm51@fopr-24{<-V&bW24vy2OsGoOi zOGepKb7hM7sIQd2+V>9S$zhZ=1=eV`C%Db3e;Qhty+9zvm==6@Uf}WmSjUY~b)xh;UdSHZRjatvFZP z?Gn3iU11Me<`~UZb8_VF|1Qyn;F(;$E{^XXm$FZO{%rcZABQ!b$9L}|d5Q5Wmfe=v ztJDnf3$58?r7DayKV4qX?%}?ud-c zq@M*UE3u(V0XWW^CG5#s0mct3<&&FNfc}zWGJ}>nVc`!w>X&TJ-?~p0^#l_3m1Akz$6mb8=Y;UGeuXS4E&)&7c|+?D+!YG{j3l?PZLAOyjbg+C z?ojc|Ru$|d|4xN4>FNaTytX$^7b69Y2m3_Nf(_#Il|MXgq$nF?y-QE#kru?w~zw6uIJ~92~Zid6?#Yd!{fC&?3^_UU+>AmBI5;6 z?Ri(2I!B9pRpknQSG$WpM|I~Xc~YKZwhMp#S4@2-*IT;7z`2L6se!6R?7c)7JV zYvwNF688?=d~*y4z2eYhb|4Sg`ISO8n+k=4?u+NjRpk#`M$ywmHHpRDAA2QDAybz? z-h4L(PmGpw057jdPKh1pH`kjD@5MntNgAg2J0aUJT7?dqJ78t@Sjvf8ieYvBLVm6V zKYr9mBNDgBy1xK%TJI9BbJmi39G-%Ho!sa{VUp;feTwHCJx$~0pT%j@q@H7kam;%} zS>U3p@^%+}R9QZn>buUxMe&`uyLu^2N^FFJUS}mS(K&GXa+n66+s;SRdSP&E6#Vx? zPHFZB#8>LmN$J!FL7^&AjGa23P2CgTh4ob3}6e^zXytx0S-Qkx_j8_zWReKa@ScM9^Sa zUoQNZOAqcS;$ffNq^l?Gfyej7UYSYw@o5d0Z*bue1A=(o(Cgstl}#N_R#2ynO1!kC zK+4QF3N=H=$vbZ~!oi0xl5R*c%H8a7W~WKqwM!Qse0~?Fw9jRosXg#?R1*o~PEu;{ z3Ap`Qk^5QOqJ_RTe0`FMUz+RbN5|nbV(ey)nV^B6l%9(F&k{SlQ{jf3baBm(YkrN0Z`gSx|)wrB6=dt7~?$qvQ}#&(=n*>Ui1ySCTukR}WlW6HntLU;K?E zXRz~$0i+?k(?jys9PTdq71qMj_E~KDeijzb%aH$Rdm)-nT1*t8Dt^+8WTD}(s2_9|UXR%fvlkCwwd;$}+C@j^du$HAwkZ=b z6Y}ZW@h{YpKaSro(jfn(^RYbjux#{`KeXb-K5%oG%TvSDaL02!I#QtoP9N3yx4IK` zd9Tb0*MC52#Tn6Uupiq$m@L|9^aFEY77k6GiRN)m(Dn6C@%;H&yz8ITRVnnt%S!vG zdD%4h_53%?S@2g_e&`$h87*~lQu|}8?@C$sPK)r+-D1!%OJJ`}wj7l%g6*yyB!g%4 zx5bK=PYj12uhlU4Kp7p@-49MWQ9RN8F#5V~6L)@Ej*fqO;q)`wY@`|wU1zsJ)=g#k z$>0hwe4Po`!p*5`!3rE>;KxOun{oY(D43FY1_qdx%kwo z*l8wPI~Kyms?K=OqERl|>A=`oMf6|B6AEg}#?I^3;{NGY?D2O3ZhITaMysP?t6!Xa z!JT7t-SM-~zT%7!Vx9%zpVkN}%L4Hol)ys6e7f6X5_A1kp>qBZ{Hzg63%*&>#&vf1 z$s&aH?tK&VrMrCN-+?rL{wvz8@l9O+WE}AJ7ZlzY$A0^#;ahb@cr!tT1Js((v0Iq+!*v z(UImDc3vH?pZY`366{&#?u=wpONq-avhp*3cr(3&xcAO-KHn*dhb@uz14>151Ko>I z8=k-~?)q@S+i3iCVJ^10Z|2<0F|@|{BL7^M&o&b#;7Vr;6f6TUu3Q7I_0*BITDxyy1AIZ9~7nUs^L4Ff^ux5>lIQKtq>UwMrhnr7@b>@na3&vvXpwYT`{JPY?xh=AJ z#a{V>p>=fj!(*7!*$L{n7S{YV5c+xU6VD$~Kv`>dF>uEZ+Iwd(DRgs3CF%>p@g5xT z;X4ctxMk~pCKPWpdcz3Q0O*pWPF6W49QD}(Km2^nRW5?;sPSTYMXAD-`HmcaQymBU zcE+qsQ;3?O2!C&O;Mn{r<(gwkcvE&JFX-_cUaoQ!pM02)cH#M?H?=da3=YAXzXsIe z(@wT4kln^#B^lt8lTi1Hnv@W!F?TY{?0-WE=&2XuV>{MyNBm`h-2X@)Gsjias;o5r(YuUmrd_9OizfOoAQ}4|Plw|# zHF)HxPS9{5T^LZbr_6NLR2ccon}0a0#OSZZ;#}>OJo)`g)ZDt554r_HSW_roJQ*tX z3i5%GGnCLhXEF~xEb$!kbwOXdFH3s@TvIv_6tA~%(8(yiIAtKcu1BHrX&4XNKAvuE z%LNPjChAt#1$$0k#RV(g(bh@!cs%tQeZC(@E1Y_gWr`qb4ebZxyIJ9hi?h+ZEg!4w zuL|4L!(hVn&a^?8fn2f%FyW~9&ZUSAXN-q`3trNu1H0(`(zjHda2r%L3P82e8&^yJ zSE`khS>fAx;d<0+`S>vf@G1(yt-ZPIKiZ0WZu@xkq>?oVIxdy`4$C74_EuQM&iYqjX z;CAC>Dhy=q)=3%s2mglf_xEAw@)(+0Qc1n0)sp$$QE+9Wtz2HIjPrkt5Xaw*#Di-V z5Uw=Dk2kc@~&CtB`n8>c?5!76KMlh^KDE+SNL&Uk56ox$c@_~G3{@)SRWZkj~aVIi?%g9E;YbIF89SRKPREv zN*&DY+Znw6u9t0YAIBYj4Z?pe5eDc9fzn((IuvR#`a04gqn~s}R`_ic(3kW+4qDNOfuDxI<|9!%p#~gVE zUUi}9iq(+p`9aDo^yd$zN8~SpQsjF)SFwdz*RnU1z`d-8vwg0+c>6{+eEir++)F-G zC7({()|&Wot~JO=IVxN)6|0$$c#0D4W^c+1RQTvRxaO3&F-`~F(&mV28F~lrzLeTrY#6# z{2FPY5eUgC6%aHvp06p(*i`ao6encScgp1q&&%Qp8o?Ok|9hb0hzAN88Fk39U zxs^I?7{qh8E#;m4yJJ&uJXmbCqMTbkbh&w~*zZpTk90`j>IJ?sXTQ1lw#bCXD4!Ca z%vpjDKYC-)kvP(@)dT%*8azKsD(5>jL5=sfa;Nh_)V5w%avD?%2~9u2P)(n14%Nm% zmX?$}SYplHo5<2@9h?8?5Ue;#$3tQ;EPFhbB`#yfhy?lE=(TWh%{2J1!I8e-TYEz^vm#&@E}CQ{RU12GcIMwnFNtt51N; z^gvs&moghiT?VVu$@o3Ll)iupzFXTah9ykEF-O(eH_$@ZKjc5MS|_8cwKt(?gdz97 zu!D}O4@P+>Yuwz?A8R}2P^j`Jity-&Gbbz){+(He>XJWj*tjP`Rl!KP&Yqi)HYf%< zuU~=pF<+)ry_GWi4i+^u5Au1t2hkV?dl0}>{Rgu@W`wOizqR?b}FJ3-m zh^R$MfF6**ters^R&7(AthTMzxEvNN`@ph}~xT6q)ie&EjoJ}ri#Uu(Jj zu*A(R$QB+6J0W=ESXo=mCrFF~oEbEmV;(*R#n6RlQY~_ea~oA9rHVt|A3$jF=8aQQ zxpoRk%(_A1^ryDCtY;4Qk0=)+J(GmC`#My&IgT1~;#sLfUvhmMEDl`S8G_Rlpl*zj zsI9Aj{bQfP=r{M?L7O^a|^y)qx3v4u1-6+^Y$%BNnvC|vVT27MZH?xekb4mLV9Ca+sdS3RmE!7 ze)<5KhidcrFM;f3ep{S8MHLrW-V|*l?tuEec1Yc3%BS3Rpka0?7zQix59fY3V%7+- z>0(Zf8WJ;Z@jmfs+FM%M)r#pMcPO5BnXm`HRD5GPeE%D>(JiN89>$$JecguFCE8KU{SiEAdh|Y74aD}B3 zD}A@d1e-jub%6tDhqv4K8^sAW%Z`v{)IvO)d6W#LUGI7O^P;HGKo2A*lXc5vQj*va z=cE~E`i94#Gin&0D9#F{vsf*Td{+wehi+qEYx{9E!*iYDC9m0mj{ZaAn z8n|ud&e>jLP;dVjaxXhhWtq2VOwBWS5U2C|^*!*Hlb87SNQT@!{5zQ>Z-xF>3gBeA z57thy#sC$`Gye4jet4RRJ?yJwW1He3qi#P8j93k|E31WRF$0BNDU;dB^@JE|d6uq@ z>`S#TBxl>KD*ExgNO<(-5ET0+@CmicGMnZXwl4GA#O=qgK!VeLiGRKe$~X$r zxwZ>>`Oj_L%seF@ot?N^(FCm9t)%>+26~Md$VWtG^$gWvZ| z<+1V|^sJjMdC8v$_XceiuYa75d(Bhm()J|iG1(M*|H^~HHF2!2oq%dB8_=v%2j2e7 zjMX;Au=qMw-u+-FcK7Ru8s{9v?vBo~<_><$AKnWXza*btk*Kc?(5aynDSyY?O8>`T;1S>MH!A= zDtXZ#mx)v4G5GA(cgnbtM;GQck=`y~ZIv_dZR{oSqNOSKj6Oy|wkcwh@n6yQpSt8T z>?6!wewE%x_r?ZqHBL$%3%46OvvYmV|XfBBXEnqJxq5u zW*dn`In(z&MI5r?XA?bX|Cv~@y|sc*met4%pDp4APD=c{?i?s?X{IfzrMO~h9;&ts zz_+_E^4r)dFtC&uuE*2ZtoJ}(@_9Ze_09sP9d6+1GO0Y=$b&Zr`as(l6TEBv1auS3 zgcrfq^t4lF40@SH3Qv1+tIa+d>%RpyZF(*-9n5%Rx-Z$DI4Le3XM}lrDbVY!oW_T& zglQ5hHcCfg+vbCOL3iRoJ^dRLvs&n(UELvzt^!F zj^6iAXe#+(6MsM#w+^xseOe4@Z<-^^jiG2xuKd4-1K1YhIqD^G6B?XJi^c4iiNhw;qXRssP8Dd zB(yKd6$YpK@u-n|=<%*k;_Lb9IN<3{9%eI;Pdod<``OM^470>}(*E;tk%|zoYdbV% z--P8I%jj|5ZM+S+oUqD+pI?o?_Yw|gTZv6X! zJJs~ahMr2^5ZT2=e)ehwH7!Vj*g<~cLJJcN9%d(TnRm#295rFO>ZS5`mnV^#=N0-avyMa_sP6v9entB&B|( z-?qhAefd1E+k9R&ao!a;uX%>L)E!5Uz&ZTHFAX=%iooB}d7023aE_?Pw(olp2Gw)7 zQRBhOV-+4Y$fb)eF}!W7Cxp&zE`MK#j%yX^vQdvO9Q^rN_$=7NXGZ{gnJF=Y5Df_v>~go{)5@S0!M^!r@A zd|$5^eEU=KGETTgRfQ_3{G$^NSuluj`)*W}IQFYZnX0>)%ZB=a(sL6EsfZ zPK=ist$azZi-Iw#Q*XSoAW0s!$B^BOp2Nzg%jnnWc8K165_4|aNdI3uz-Ly0Fd#e? zYu-<1rCKiVc$gsRX|Io*W($%QkEGQb(mit{QA9@ng0K zRox-{VoBEzkK)xQC1%#TMB$cA8V=LnDL(A+T>cjN zs?v9)m^pVA4sgm3RN@Mt|o-VgEOKg~VOG@#}|E9C`JD-0kaT zy6vbdbNLB^%jSCeb2payDLjy$So?zJYW3v8#V^WJ-6F8}cw2nc$%ac-Rl|Y5o%oxf zHNLs<6K0-#Vmo`0#6FYqR{0gKtn8jHoz+*!YsF5WqT8ao>Qy{9L>~`(s`IC%9dP@a z-jMWko19x6dFtp0bUhqRR;Io9x#nM*ekX(O-?>S{71r`h_cy}f#2BpKTt~yoB(7Jy zHMj1Wjx$CI+@-=!oEZNfz3x#;y{DhXiyyy2@!6XoK`M#Io}}86c(^$>jba|NCgvV;Fq_M$)TW#uM^r*h--DlG2m!8hjQ z!s19bZeCg|-t9AoOaG0dKdaQFKBg(})@_noT{ury*5pD{^LV;V5~ry40Ez4Ral3gP z73-Klvwj+c?f8$3f1HpvOm`HP_!@HX&W8hM@9tRs`QU3WpJX&@J z9lBS;OOLstjr}n?cIh&xxI72{nbUEdohF=rFb@jqZ_%a&9r$bA6279=K-X*C=+6yn zA-`=KCjL*+dB*khzHz)lOGO$I8d9{R(E6V1$}E*46lqvVS!FbSArTS)))4})mg*AdgCskWw8{CbbSJH4;`5QW+^O|yTA&@ui_?}$I*d~ zFD%K_AAHv-VNQj%@Uy6b_Fs5{2`e-~b!l$R_0#d>cB?P?yZPX}Kk1TwzY1Buww?HL zauv|H~;li2P?MOgH772z-@aiL=5ah_d~@s;kz#`6rC_wHwL?tERkE5#NN5> z1EDNca^i0^*S}{7t^C--hkIOuX`g%{JuL+`UI~B|$IOJi2Y0gtm(tVf5_t`jqoH4pQ0jx>@bvrye0xP(=t|nbNyisSzL=Mz;vX#v zJeEuUWW>GiLL-a~63?7z#e62egMHsOiN?C`5H4Pg$FL*&DIxF`@05C+hoI&5d-R9n zoAoX1%k7nPGvAzSXVkDmfs05IF^a!JN!aTbfE@b;h8T#gBW0Php$s?rcM1K?SCWj> zk#bmkWf{rTc+zKW2heUSV1M2Z#?r-6Afs`M8J+3nT;40wH0@6I<&q6{g|Fb7!xrMw zZxJ->pCS#}Wr4BFY%$d)4ZpS52!-OA^J=^_7MobIm6I=^_p$_bDep8sHCcq6%MEB9 ze^JoQ8%a}=#Eep@0}NF1z}6r8P~qtu<`!Fkzv8Cjx_mJMQ0RlckBA-9s$?HJMuSRo zU%an#gdVI3!W9FT(s1J*(cws`k z32s)?W{$IGLvWEd*o4*!`xG=B2E3Vz3Lh`9`;!OL(ch_DzI`xG>90w$qPHZL<$;{y zHp<^p&iaLZg2vl=xTwaBb-5;UUysLAVB}{AG#O5#{kFisouQO_&=5-ZDv}_rA$T|a zCro%#z_oU|;Lo*}X#Vc!;Cwk1VQV8hkd@DkkuT!dQ+4j{Rb3%PoarxG(;~zrjO9P| ze5NZG+?dk$CTJR}Nanr=Vc<(6s@@|n`9nWynw_WGAClfqV?E4JJBFd<)B&Wl*@P~t zEt2Rg+XuBRGufO!9bn;=f!bf^kW97`tqEU9_kGW>7W*E)Bg2Wl?7t418j7J`iWl`r zYtcp=OM&(6DBm{~+``k@^0D*bq5KiDJsyu$t^;w?^=))5PmdV=^55TTui`l+; ze6;-t@Zrp9shA^G$$Q0y|GS2AYm-R&;80pS!xulOma&7ju3T$)GHm*HA5169k>lP% z*c;{u_80USr(OVQvt%gZtvOtI-4EehFgx6DEz0N&W~KdkZnck--ImPdY(Wl^x#)jg zWj)c}#XFykZa4r<$wXzIRw!>qEXJl(a(a3iY}RP1ZZ=M)C^ZS~OfYAu4hzxlmLhlC z{4SUMvzZTbyUVUz8%4bj=deY;)A$n2r%cDd4W)Wcux^$A=vrwVF1_Qx?)AE&aY77z z-9Me~M48bUab9FmsEhG>8w3ZlzLKgMzuV8={a_%hxUtO@#$K{+Y2GZhCLyOP3eZdvRtw za)j6ukK1FXap?m%Ms%~1lz4Vo?3d22InA!EFJ|wI?f6P@9X5Ea&Aj@2ftsEboWfai zq3n1w>oe{U@H-qOdZTwi?qq*b&6!R4VrJL4&lc{SZ32G2*$y}RYN4e1B@3{LWs!I1 zKv2bCdbh!W@BjC@Pd@I zFF{(&uy(erhOI7*p!IzZs>p`v$Ao&i@4_e8HA6fWWku*rsRHcJ1*~b zLEENwcs5B17yDkcpL%O0BrKjHW>V7FO}D9-YRI$e+5^Z+dwET*<`J$@&WwLuJCyak zkb%EVb!hKmEvT(N${b3@;NV{#+^~-@D$r8QRKS0S>y z|BHfCAVK2&`-@)Y4lc2_73fHLJ z2z%`=LM`XYwa@tuJvJ#Ua|@7H$zZO=6PWk# zwEfR1r|7||9xzm&fwenF@*cYBY@DwPH{nM$C2adZ$&I^U*3KM$7Y!o^ooTQ|HH5a8 zjU?f#Bb~o2vXtbH!EHTXGW|84}nq8Xs4Ckha^gokj z7^I&m%T&mU=Hfx&XT)oMt;Yc(ZNHwV(YR|3m*bS0Blof14Q=5nULhIOl_ zplydE$#e2bqf(x)~y$k3p=#(7Z zFdNHVb~4|cUgR)V8d}O*1*h|asXp&N^lzO&p^8UY@(}U+Gi4q;xZy^3KCVOOsyo~z zUmrFhSPL2lr=qD9$6Ziai@kO>tS(3y{O*S{xv0JvqE%1H9u7E2`K{>O8$d}a190j5 zx%4nrmp`{OT8P`GT5A+Aggvz8sLp2sIf;9K(hX|xbCWf$9V<_kT5hN?Fo1=hU5hr$ zM&YAsc~*91h!A;D9*-97#6F$#SwD|$tYpViA-%-~KZs1t*pMc?ExF5Y{^`yBxml4$ z_;1)83wz>j&ZNM7@D(CLp;jj753_!~J0TcT3QA|Ml-Z;cb#RC7qV z;tNw~ze0Cw4+>*?7Nc?AAm-9)LOb*VY63SKa2Rhw>&5-^6-^)Nn%@HUV@=r=bt5Xi z@Pj>gx{mMn(Hz>W{xY>|rEqm{Huh;)3<(D%bYbu@a91Q&th||yEZc;`t(#aF<4E|De7 zZ>br{h+JM-StZo}xt#4B=>;eDWYCaRO;yt*cOaTX(fh4YlahXkK z_@rwW^q;j9V)%zZYJw{|OQs8H_degV;oA!SL^X zh^ya(1wPYg`P8d?_G)$dR&fHah&+zNc31eJr`%cgkZxg8R}Tw=CdtjPhw~HC?~nlXa)CI z$5E7#=)!n@gZImS!a+khj#SVW{=UqDa5n`K8)B?k>^1}xJZEpK&hrzGEWvHPo4GyX zhET8bKDyK(G6i>X8`)@3RwTblM=0N5$cnofxmWo5w9N0vQR2pJ> z8>R8|IhZ&ze%P!v=gS{i- z>5`m9~#n+&+{b-9gR3&#+duFw}4KnvXmK6MX z40g^>nYq_)rm}q$d%9^2@Zrv-9b-U_3VQ~h}*i=5}WHM;h=x= zLc%;?AIE=iAj0+Q3uSl+OG{2E(DY7$1kx7?xJQr{8ep_B&2D@30Aw-xXsCj~y( znA5Neg800LvXP@g$*!?mSY_*EUobD4US|iQq+klVUV95^nP!;0_?np6%Z0J_ZzL}} zdzsgh@$|B$RoPN)kD)$C5%|#E?T0*?k6h@l__Io--i*xKyF_cdg`N zh8)eF^pM>?Da*+BEIzSZC^~=_;`A_m3ht9)dwlO2ZtbZYHhsP?cd_9?wf`nE=?99`66=I}#LR5rbZ;Wu+dDm`(OJqXpHcWIaHUQH>3M}sU#+xIjj z{`VIQ&q&xHaW{I!=&%?F#hiiz+@q~}B)dL^UZ;L#-BGXDl)x4nkzZue^>Kdy?sm|?W6ujm+@nIZfeBHGe(Vz^&BWl5{l5ug1hPph{b z;8jmOXAiEv0MFeqY+_t8Q@mqse{tYTHq)(@l|C|~BO}8lZ;B0h>$#G9L{~2gd>cT`O!IN;YH{jPVe7P z44a}W30YuD&zoCejER^<{5lYCym3I+c+nA9sZYLLY0SX+ov^Fp6>l=$h0bT}2EV$E zc%yd-hQB$Eea;1;Pu5U;xP1alhMk<#csVtxK!dtCl8jw?_png zWwT@0Hj+V?@na$I_5tDR?RQZ4fJdk3JD~hipP723VqKv!tuhY4onv*`nyVgr(G%H zyT&60URIXzi=_)#l4BOco-G1zKUw-9BSkwF_GiPFF&3*{T9Xpf$rh~epkCt~mbvpV zWlTAP8jrWJiVxY4{&+pu++I+fd8C zi#f^_RlO`FcrV2i`a!6&9_yO;TjWxQQPIAI_&8+&E;=U-RyH?wPPpOo;BL-tJNFdNlLH0P2Z zjVjlL$jM3eqYv_e-hmSCRqR9XbAQEJ+6LkL_j}=miue;$3@xF4d@Y|Z&9u(*{VXw4KrsCJ+hhMB7KUvK8&ngMCOmjV2U!kBUs!##MW3OQNW9O zVYl{T$<%w^WFz*HH}=gIJ&bQ)?cZ?p@v5RlW))!OItjNojS`yjyMNy`5RvZ#C(t^1Q9v$qO+5oJ7+!ub2@5W-q zZszxTB}Vxwk&job{Zy56+^>d}tgP{>&}X=qndcT@f;$QCUInu)vPxXcB}W!lHh~Oh z?1at(<+N~z7gGw@ggX?j^OMKuP^Cc;OKM&S)v;S)a*?fY*YN}7enxh8oTj8r)`Vv zi|5*E_Aon2SbvOTe6b3KDjQM`1;XLkuAngQg{1eSibUo9H}*^V5ccjvvDa!Mq$Y?? z`CqYY=qE=sI(UXZ?Dv5FO$-#m!?hr?Jr^r2G^pQ>)hP3CE6nmzrLA$}g~&Zm;Q4bG z+SkzyPWE|h*giEn+)%_81WZSH=RN$UZUxNDmY~Z(49C(BabIn)@({V2D$#>mm$?}Q_P9H8 z1Rb!MMw5)EVN0J4O!76BMR$svic@#by;S}Rl7d0>@9sq=Qf9!u+=^I*5i`oU#5GsN zk+*9h+h8?z--uYu52jnjK0Fj-p<%kZ^i2aJ<5TWghAP)n60rR}X2! zZ$&RaEi|`R%{MW-2pa+b{y3?r==^A;{x6r@E3*$Bq!(Y`8 zc>i~U*?8FqHp2Tn?Q-nEh*MH{-&ClXGW!(n7qb~DQ(c76tTOh^R985rIUm2zzs!9w z4Zseog?#Y3Z=8w9=Y8vNolQ3?d*GfxLD9m(HFWS=FzEzCbX(;176pX$D}#g_~0+}anN;MaK%WJlf=$|nzC z8LtA_(%jJ)zebN;+J6zou3rj^PEW((k5kyy)bG{11|I>>H6HAP&wh#3jUi;eGXi(b zIxB2)7X83>GQ3yOXtK?;6W?1CDBmI${WnP1&ySlaJtBb*nA!vyw^eDQZzoF+6=!mq zcbV2eH+(PVC7VZCOC~HBikrmq+x@V~^zno@TiTezy5{I$TZA3f9{rCF1ZT3==+Q`% zHlyd6c(e$R5?r68af#OFFg($g%6fle{Sv^RS6ld1Vpk$=>P8AbTF<5X*MN_AA3U#N zh^9fqaLA@(81ht)(jI!CM^HDatC@-Z#UZfiq!bLirHt;exoV#%W%GSt`c1tM)+>0is%=>W0>m!c)(U)Wz8sV$u0e;T)EWR%MKQ4L1Av~p5 z&*^MB#o${JJHq6^v;Hvd=F&;O;s6UDaElFnf10%q-cH?d<5BJ4Tj*M*N}&^tq3v}j zZg)S=IwODZI%P#5vt%F+Ik}7V>g}SHFYTzl+YM)qpUIAPmhxMMTxIz`H7R_@dw7%b z3+69Xg6c0~@4Pda+c(0Kyk8d5v$B7?)Com<#ic-f* z1efSIlHA=wd7F-d@{J*^B3PUoOcLS2(lu;tvlpAj9R%|;#`IushNM}L#m~Bf=->)X zGLpF{{F*q9_H6Kksby~5Y`>-S*+w6hoQek7co{NmOQAkv+iF@$hR}$^Qs^39D3SU& zlqf)s3@{!`yz+qMj9?90b}(PKZ>Ic69dZ3Gwq7m_Sl>FhzP1mJ?H)*LEllx> zxXyIf$Fi|O?O;8pQS!vC8t3?PaJNB$+O;$=ZSV=WB)Xf5`pc2cr9Q$@&8_rt?e&@q z>upC>pcaAMuDh<<$Q{eHEz3B1LksjNeg|}AM;dR<=-uciHoHo&t`p?T` zt8Fr?rA``Plu`nW*3!gLRkbzOS1Ykzt$loY6j8QHU-H%s<>YRv@lOM8u{pLUxPl9g z>}o(TYuVNp_AH4&6O}O#t~m;Nekb$sbIh>Da0~tZ`ArnTYAR zT40Lu>3;9+^n>4jveKnxm@AiEN->08~rHNM?-a zVkXJuNIGiFzQ}{M5AX&>!%}21a>D7)wG)x=Ek&mXpiF`aE{Q!M$jp$??&7~6S4Wu}(vw%yq@@6Yf&1G#nqPf7atLe}6AyjC3f=-p6W~YzNfcHJ)#eJd& zjjFfgOO~~=!y+?b#N$sjdoKJIilYC)qqRQp;;IF$Z1Q5!w^(YQ$} zoZb93h?0WdLH~P`SmeRJ_?q)1thB)VY>~(1u_^nf?PN$vpcj76|7Zbq3ZXM1#kVspGg|I%Y zUc#5pKS0Xao>SlY2!6*rM(==BjJ!7%`n@dThOEgU_HiBsZJR>PPG!)pe25y1N?~1H zAN=@2T#G*dwI7Y-EIyvb2b)^iy+bni!PAfXyfhCfRrHXgn$c~aNH*c?MR?RYisW9S zr1@YhrF1vZ%Rl=Bt>kJ59aRrY&ZVN9+Ay#m-cR`N_;X1~{(UZahohKG8O0jpufd`f zTAXQ|IVo80r7;#MSn9v0Cj8Fc>asU#OmBW)EV*9Ed;fSRs8l>*+w{&-yLC1G{Wl+* zTl)#KYnHR&VQ%-*qkmwUz4Bt}V_Nwl?v# z5e_)X=Ot5&8;R~)e{#FbufrGj&zwbeC6~ABDFpg#=2Y4fMhWrL?c|+_c@3Shc$gmQ0u6 zT$AM#$n6Cc(LEKmQ(p8=D50kLd&!Wn~^Rsf`UxYGMrAA-{pX1@}V1$bCZ1tyLI3(X_^T#zN|UehRt2x1;~A zDq*H^w(!QdAAYrbSu<5D2>jwL!10M8r7Q`d(G{B1uyq{1Klp>q`5GsAoA(8jS|Yd* zWzn$_RmeKpQo*Jtnx43iz!%-G#LneBJnLBwJ!WaF_wHq!u*F-Lxvm?&$ZZ7eGe#69 zJ_|2YKCqL~HNvG`O@eCAj&v5)JdpNfzO;{>vQjv^Xa)AqJHn_n056;u=k{}5nQMJI7#c4S=k`r( zam8Qm=HoFa*Puz)rLx$jQ-|52Q3H8Mcm~;jHW1gO%qnXYsKC8bIP6w0M9s6qo^gq^ z$}om>a({!JA7deVMG%0!nKVXI)%|$X{NCd-MWm$Jj__I^zTvCHgfTqK<)) zwI$muc4|*ZlprT|5jV(g7nAlOrnhAVn(inD)%0*0VpPX&^^a$cwJQYKA2O(IID_2# z+v6wiM-;%936mYe0R}Nw;N#CM-F|~C$VFn)(YjC zUF<{QR?N@0WlQ#o{06M<%5ufp&>Be?SOM?`pw z1=<{c4iU23CANl|^l+3hPH$Yy2EDus&d0a${r@atWp@vQ^{wOVZRjt+_{oyc;eU7! z@9CIzdG{zlK{{?FNX6cOX}QICDlwH^R}n;? zvMpM)euV)+Gw8zf-*60SV3T66L?P_5z@N$#TotF`@satq`uqElvDqHz9rGPhPrR@3 zSR5>*sCeV6&KmNbe}-jxW`g0;cl^iVi8wRpE|W^PfxY_`BzSEA8Lv(d&vWl#NpAof z(zv`@a6ScD16t`-yd&P2o6P@P84GtB>}YwG5fwJtf=l^5CTph4-YiL>^zMkXdf5wJ8Kc0+GH8LR}_aun+Ka(b- zpwA#PQA;=)Jw~$9suq41-)6_Y>d~<^hgsrGGnC}l2%RChY_PUA25rnBP*X``jq@?Hmm8 zQo@ejDAHLU43&*VFdlDn=}pqCbB-ehCiJI6$zgQqb~@YUWhm*Q-S|n#4MM(`R;NAI zhZV=wg|4N*e1BdbkD(px+~*`(epqCc4U1)p9@<#zo`E*stkHe^5L`O@9h+FBgqNL$ z!bzoOx_R20qLxf#KhHOTb>Izde@Pfce$$7iabtx++5J#q$8e@}s+TR0w86y5k6}ed zCLo22z4e;Dh^OrcqTziUTr1t=*ae#LS9&kqiqK?ts@L zUGeVOg;@nD;)dQak}@k=`SH@L9l2aX5dOlsapgfT_9lv2860AmE8d72( zrJT905c`h5kD;4;voL(sQ_ybS#QZOuVbAwC(1aJVD1G-3R4(kpTB1+W#}9MK_B4k* ztEbzi&D4Os+tjEg{viZt$P0rqf+d|IH`fj;sV-##N<}EsTo(i3b6yMcl!@kC?9afS z_h-3=;zjuVUJ1Uc&Z6D-+?ilBP_pW{DMs8YCJ`!)Yb)~j{VfS-{mzo+THD~Pi{{`T zxCE{D>Dzy~_n1lFYvC3Yyn)$+_rS^{&Uk0%f$F{+nef&)Mi9Q%LH55ZEVuj`Cv~Zi zgfB@n^z2G}yHt_#XC;dH+c;L=zE|W_i!=HCR+6gqjyS%15ZQiz2Agh8MTyAtkH2IN zsiV~~)T5Hq_GuLsdG&HZW~q2pCx{MBf5rk9EF!(}Mzmt^cTmb3&WimKB~&y^;K`k7 z`o%!s$?Mq7_2KAHrozcTJ&eXLp7Mv%jM>W<4D?eA*_d>Kn;kJz5A^wu5Pf{6+TfqAmWKH$u3! zbrwku_b`uz7OeE>NN&wZW85@q1O*HZ7fx)@fo}>TM=X3V)7gIshHrc>a)H#y!1*L6 zt^Sm4I5~qo7+{OW_v)CN+HfjS=wataJ;LL<`fT+t@owosJf1WjNjXVw6!9m6j<4~= zWg-)dOIRDOo>8E z%XW(SvXRttTiD%irfk~&dF=f6CG;c56E?TL=pG15`tO4S zqBErQT(dnleK;;3G8orz>yxm_%9f_R+l+7DhS4XzBk=v^7dHBsEb)KFV8!OE5Ew9x z%0iDxO#6Ib+u;mM{S}RwUAx$XV?j)9&>Z_eYJcI5QyxFSau@B)<+&2$XzEtE3IF-(0$E@~pJ4J4D_5~f3)%2G<$~R=MAGC5CG;2V{#f}#*yd7iD zH}588Z@xkUlXrvGy{9lGREGK<8HIg*8q$Y^E@9gn6-*g#kMECEVw~Ft_P9kBe=W?X zixz2+=xqmoeA6U_%KwBrrDJgVi+$9LkZU$%NiFn!4oHx%zyC}e0JGU z%@SFnjHfKjR~M}oMbh?;r95fc(Y>Z&%-klB#=rGIwF?(nK)9c9$>Iat*;5RQJtA47 zX*yf^?HMa~*~b<;UgNHP%fg*od7S^`Bq+T34xQP$Lf*&~+=qv+@!O5H^ku5ptMGQj zs3+d!Ei&26r%z)q%LdRh#gCk`?N&0*5_9%#m25?+JNC<(jA0`y*dg0!xMkQ5ud@A7 zQMr`8-cZfT6MNY2gKNo7A)n^8zq0R4UO|#KOJS9mg*|$;i~H-;4=#57sd*TZOGDXl zu50Nd*r>RWK)YIab6ke5x_P2UU;^{k(8j+mh1fDfom5_jfaGNWGkMs?x_=gMjv0U9 zy{b5a=+S}nLni!4?QQJdYtb{Y;SJ}z@+$LsY({#`6<{=DG7SwyZqqos;XXObwi+Q7c$%5eO&^c7qbJ!Jh3U7#&d*(f*hx$ygT2Y{)V zAr)r~3uZ~+$FpQycE_9K-poVAx?bVt6Kf3X&H(GyU^Xk`5gXhRiRtOPMfT8s78dHr zB1HGN&@cjrs1-72_lvaOVymQ5bYOMYe}Vc>WzaRSh>JV>g_M`8f}5)i#mAi173KgV<--=T_=90kFh07kAP%L8(ZKw2!nj~ zN@niYrIdOTRE(35!i7q3Xifu54J)CVdm$K|zXt0UX5gH)n$u@)2`4Dv)BqfFVF){6Kb4oyL9{ELfs2gQX`yX73>X&!pVMbyN-ME(Mls|b z!K4+WAa)E7Yi;4G z0(8i0`gmdAaRc_;vP*FJE+Y&UpGOTRQ?A`?G`hc(5=PxnBAn92MZVreF&VemkHG;n z(1(P^@JxwU;s`3(GY%0(P_AbmuHHI=o2zjbv?oQeH)`hieC=|wefU}!XWamI{f*c^ z+c@ro)F66)tyuVI57cPCi(4!+736&DB-WbmS$_L&cyAoQ8I`SuGTTIS&ClRE2GxUC zzJXBUIFNfCG>3LfPzQrgBk7rYKlmptMdNn)^OhR@ky+M)WXD~oP%vWO4iyQKFR$Q} zWi(U$mM3}V(GTxEI1HRyUsBv8LrX~^8f>n66p=cDj+aWkv3H-*aE+iAT^L(PX(N9fMjykt`YnMQmlHAadm_E-I%_+>Qx{w9X0c-#zCzcr z4my0_2Rm^aXgMtgz3K_f+fkWH!>pmo_Y=GPXF8oYuL?5SePFW0Lu97sL9dLlVB36( z%3@tLT2xRc*^Inws;fEp^Lq<{^g^TGwMH``!UNh}lA2`sX` z0#r0p;eq=JHau(rs830Q3*Z04i#u&;%NbK}kB9)ZlOtI04s$$l+(Gi@=R4+^$4iC{ ziG?=RCtx}^66W73C9kqtoS-+ZrWcgm$nLDAs3;xL(7yn&O| zX0U6V171tN!`8PpvpLm@yvwF=OiVc^e9ziWlOd7s)9y@M*+0!r6+wjn+P_K`cr6z7r3^&!u+`pYeI`Ql0|(a zR$Hj>XTN-b&rJmoZX-*hPHbm2)w?9KETb{%Rv3RI^#!X6>O%(6(_mtBGI9M2z~yr% zjJ>glyHptqH(HfAPro_Ts+ucIi`s`CEye88;5Lbvt`YvIOl4(08IsIHqe0&;80Q_1 zLt_Pb^i`Nk3ql*%rhp%kN2R~tE|$HDMJmB6XO2O{i` zGLNYB)LnWXBCRCY{w4|MUwXf+pCY+iO zZ0FeXNHE|$;Hke4OC0)=*%h3EfhU~M>3|BkX6rDS!&5LLKy+UpGlIZ6Rr;&<3q~fJ zv0IK@n>_JKg`r?`S<#1eX zIV5B!v#GW_NJFZH#m&9~-R+mT<~LfL;xZllxpxH4#5^{`sadeBdkbe5e`71!v$$%F zwG?$-pF9_a3*q~ha~>bQgVy*j!u8Q+Ea-9x9iDfB?|dk-zYFv+*r@^Pj|HNPc%F>m zLTj$R9*uv;mYfPP*H5L8kO{>t-VPK^eB=;|XlZ!c$;pD|V%JdbLxwla;2{W0Ba9h|Ke z8Lu;JDdfOj2!3*&_1~lfsm+y8cdnN=`0o-Y6_&?gX0c>b&31}X8B2%Eb!Bn3A z9*2(GN5lEr46LlrL7{6kQ%JO=E!oz>;lERB`h^jly?!37N9gh6H>9%cbEB|a`vTo4 z%Eo-_-7q`hGbl%!QP%tMI5pReKI}XqBpSNH0bw;c4<66rr1uMpKcu3j-yP^0XT)#( zFd6zRdMD9&J{p^syRqJML84#zi`f3yVGfJJhlXuOo_uZpFwoh zRqT07S20dEo-MLHMAO~=7m}b6||$C3bjlU%0LM4?Je;V0T+S75*ATZ-2)~&b&8c2D(PDe1RbRK6x6NeK*sm zlco4&$P)UKwiI)_H<546SmCpuE_Ru(W?MJy#)n~z^l|J#uK%12EIt&&9{q8p(U0Qk z<(nm-7rYeyvzJhg^$E1uZ4PV9XX2TQiD0R9UHG%Mi|ck=gbQD6gaM=XvmiBF?EEp8 z{VSVp~Xmf8H)#~Cqyc)bQ|7FIHj zWM2=bKtFw&>oc7_+7%1e+LY0Fl{4eMKeCs@$E@h#RR}RjgN-xN;ZUzTx*Z8%8eg8l z--H8LDPuqbXWavfID2^F?F!dLj*nOW1-S6DD=*0O<;)DS(6;y=S77j)TQhkC|G{B2 zIL6GzoUp_6#Bm&~sH%WTJvNdJT^W*<{-@Y3b!oO>@@TA*t>wl~*TB;KOT$13-e+V0x1ucud{%5G-Q8OtJEls|Tt5=ChY!H^z(lOqF~OpA z54O73hh8h+WmR9B_$eRWNj??s!0tOv)Sx>Beb!uqL&v3|x!?{P6Q6`9?RHU$&<1}> za+!fzI-gdY1IGmMf40qxtvxUqt<@aa_`{E&y|M{v%k40&;IPndS)!odB8Bd!WXVla zNAXIIfJ$L}X=_)es_aKKD(D5?Ydyw1}eR ztDPbpsbn@4Ev2nJ_51w(bX_i&bDZNm&wan&uNU`9rCapXrCDq` zX%yRW^ai!~Cc&5gvY}y2t|+Fg2pafqal63T`%&!0_<3pEyP#rL(7hj+`!e)jPhyGe zR+_!$2K``t`SPm0~5FzF9(pw(GK;_JO1{c^aD# zevG?hJ%@QG+f(SROi0)JL2uKCu!p;*^G{W?@b0xh{PjHn`xgD8aq1&5`pN^iY8~Zp zcL6j`KXwu z#R}U)$?W+fTBfQ+YL-MFo%M2Y7qnPx% zf&94tRB+Bab2dG)7WCw#SXN*=%~GZXIYv>LL%Gm7mll_B@L zsh}tDv!$fG9G*G}9MCP<`~`zK`03YrUcaS`-c)I!LcApQu2_ga*F@m+gKxxEpSQvR zGiA6Lvi*BaqGPikT*yNclT$Bef=_F`V)QDS-*)%=8tZ$TU$c;xSd5|)*RQRIpI<$o?c0BG3kUxew_KhN%B!O2&dLhrC9s#D9kmm6 z&hr)V9ml9b|1|k7e+2Ol0FPB9^O-9wxcw7vP{7Maq}IoYC6m2jles(2UXzMzLL|iJ z*5)(&1qZ-L@eDWA?I8c}L_N06T)_XFCA=nQDKm}xDtIeO3@aQxSUpC94SUmY#=(x? zSeAjXpp7P!Wa8D#RGc~_o~$y39N3P9SUI{E^!aIQdG#h5wUyO7)}+Iz`|7wNVgMg$ zOxWVq0iu9D*r0oZ=DSTq@sa;%X+$l5*Sd!c7w@XSdsBzK&0kFRa=UrM@rsBy$I|2b zKOo`Rbx~Ja4vc!9%tan@0FUhzR4Y9hG~YiF-C1f#RJ*W?9B^klZwl{6WobCRfb z$W&GuagAbQ6xdq-zr0?iG+vfif}yJQAiHuKCV$__Sy)=3T-0^`-^mc#mDNi_=1yc@ z0jF{1Hd9>NozqZNvL3d`cW^qz3&C~lacYkE&QGqCz$YasFlOikTq3uQfBbwAJr0}7 zs$SSIt5q{#l20KyD~B-oi(g>I^LVzvC5bb$9l(~ZFM$!K!eIOKVUSVP#jPK4h#vk+ zVk+De%HF5T9Hvian@4HDRZV%CJLn>{WfZ{V3uEcpRCTT@pdH+oAEIY($8sa@Rfy#^ zlHu~k`FQ$m8Ee@dfEzX@(%kxBQHbdcDmXEkZ3;91yP!Z!ckZWe2@ZI7(thshbqkVi z9mEfxWrE%J?sJ}(Y*B8BvN)<~6Ar8i=2VJ@b8D`Qv41-AEnS20c(`8+gHE<#QLz;F zLeGHqMong>`tIoWH(Ye>SsU+I6OH>P+yleeZYUX_33cn#>FhV*|LZ^}^qM-G+i@)m z4Lr7TuWQW2O+p8`ul@(@cs&Yjg1xy;RiVG#n*--k(r8ea3!d>Dhc`-R@MU%_SA*syaCvSDwn26`U9E?W9cPpm$3 z60G>O+u@k}J}Q2^gsm056RVDrVNX(@!k5|tF3BxNyo0wyg?;AatzkiXT&&@Ih&D$3 zeawEkRpF6?mZG)i9Pw28N6OXkrsrMy>>U`hLvNzkQArc=;BE5w+=2KW1r?_Ay-48V zkE77De*8y!RgB+mg}QHA;n2qj7Em{k&i)#MFMi(Q4_9|E7^o@qxP6$y`oHk++I+}8 zEKOs?kFavlU2ZSA!LFc{EG66y@3r}X#FQgw6DY@8%z0?nc*7r`V8+rnMx#skUr_NV zL-YIDxM{pK8+yZ(4vZ*8v%SOFq9S8f3?<}~{+;d}dIMi(%@EI1-3RJHcla^7d3bSZ zKA+I3iCxpDqp{Bl=v}nCVS&G3#9J{MFH9=s{FOiO8Vg&QqDw3Vbo{2#`~0X3f55!D z7~xVIMF$!yaN+Eu6!2Dq5ARlC>NOP*OP*}UGf#A}5YG2KUTnd#W#ZHvCzLAbwofc1@pkow4GD(O~5cS5rZmgzP3V^ zoe?~_ciYzCI-ygvywRQ7p5CUGj${`6XAPTcbPDQbtzk+#^!UfCWH4fFES0rQz-`|8 z81eUzzzG+)$`X2Hr@9(d)_ZXAFLU{K15{a+vkaTEQ?UH56FyU(_~Dvm=^&qT5guDB zvO|BB(D%(`_I8FP7j}0N%AOVYtD{@+s{T!|T(|>X`aFZvdo-E5{8YI0pAkqX>Z9wW zUeXS~3TYEZW17TEaf#J3+P=pGo@mumaJaU>Ctb=qRZh_4ZTnz(zZ!4zmB2nWY7M8BY@}Cy6my`Z-MfDxx$fQO5%IzAEyljTU z#)j9TN>@eZ^Li3X&{^)VpyjZ*Dk2B07v1Cw&hKLb+>TZ3!eX~@wuxA{4KJpZon?OW4(!f8bM1 z0#aZg0}UX@j#G33bs*!@Y--X>2IurO;u}I=_N>5jF4!c)R_g4+n~yul!1Mup|8$7^ zHUe?^@44upfczKTblRJ60*lis$>y9F{m$G>F1y#_q81XF>9FZ$*$I3%`-v(Ffz z%)Y}8D-BY)y8b&Pz2rKHmh|AZr4v}+(Y2yEVS8DV$PVO(Od$UKNS1fwAn9ao5qR(C zL3gqWwQ4;irI$8r?fpdBGG#8b?_JDi+}qCvQ%1u!iEkkBr~-`cw8Hc0x4DxI@A;ie z^5H<6FZ+{5Lymqp4(P@V{`{*UbgD~UKh-kwE?U`8F zuw9(gUqT96*;HxbgFeqxaNCZNm~~qppJ>F;$v>kpM^%OWn<<0#F0Nd!*otlWSH`%B zfzxJF~# zPf=6RV(wOCEIJrAbH4TgVkPS`is*6WE{#g0A?;_uEzlfHb_<-#ZZin>IUqXzQ3QT^ zb0O;9XP8_0m0Nx(S;(Mhkz z;;ER88h~4~tGV@SvQS#p3*Rjh7>Qwlc;TQu&D9==zvGkHfueTmdzy^Bm4ZijX%c&d z7wPVsS$M}I29_&tpwH(0Fz&=ZcsY9(^emmqwX2w;=3hDdmmGuhXO3jUs^2-xOdJJ+ zI~(bI%07pKb+cK?+&z@9cbzkwFc1yrS)Vrf#Q{KB9-cp`MF=e?;AJ#|gx z6V&oir~3$+sQ1F({K1Ijslb~@<7ewMnlvmBzt@@JUrP(rz3ncR-MvC^752f7J&x>A z&P9Ri7|&G1*CBC1D9MJ4aA|BX=4^506LSr4S|gjZ_r|c^PjPfiP7=R31~coZ zGI--uDUwvkr7>~k_+#=?l5?}CvUVr7Cby_z^N4RE#}i_Q+lJ>ww@xi(mC0e4uAW0s z)(1KVkMZTErQ-N&&*13oE}E(7#H)_l4YM@?Mw(3mu4ix?Njn{IwU#xuj=fL!gX3d07WiflTUXcv zh2`;3EUS!fPfM~r{$Ic#YM^7;rUa&-=Rzd49@ajn!iAk;?zDR^+!c*t<(7fici)i7 zX|!@J|H8@atME6RPyxM-YsJ$!eaf5}M?0*_aKMXv&US+(#(y{CEUu4+)h(k~=9o$_ zaE`@r|C{{M=zNG=oXL^|&eDj;?dBzk++mc8B(BIS_@`0)BLe3BIW`zfE-2Ee{msml&)Oh@OD4q(kLZN1+6&Bf_0Li8p zZgw-#w=zwB!r2|9?=qFyWbJ3`xyd+xRW5g~_!676Z9SL@_t344hs3{}ri*6Za$qy> ztKtyNW>o$y&tBi&OV#>=F|JjU9gTe|-qxawD~d}fw7Q657RzDw8#fFYB*iz`#qkDL z6mW-3qxk5`e*Pc{9FWAZxb(t7rm!XyN8VDxNX1WdNpN0=ocPS0l}llb4<1owS^{hc z-oib+t;k-S*I?p#&ls_*2kxi)tXCrCL3REVuOztQ@h-+YJhHxgVjJA8vb_mxkM5|N|<{Ywz?tStwYa8S4 zvA3d!&-X#Nej9K9IR^aCON(!1E=QkaCpKj8Slnl)%{mHXXx(cMk{r+k!x~T1mcve< zy73wVp;g$HKmvFW9e=o&K| zO~=ne+Pq8LAUTi6U(Z1GRVF^(H3*G=?`2|tDQtKo>}2ePvx?SfSpR&v_-ob)l3KbC ze{EslST}}#-n~y!stKsKErYFH8_ON0qoRJ-i8TD>Y3@)#8JwHf!8wLrp#4*OMcsKP zM5bE?i5CBv$#*UaWPWmG4MFiq`0f4>TwPjE!;&n;-Qq-=^JFJG&%3#t*Wg)f__-w5Clw{me7P5gt?bUQhz-b?V{6J10TKFljo{Ecnah zO3Aw`o=v`dT~s;X6IsOVgj)?8*jLRx_;PGHsITRzWWg$K!*@ltUU4-odg~)_?1!;E z;}0>D!8!u_Uj{#%jHPcyR&?Nn1YE5=NITBylKMhYF6tb%*#h3A&`8QY7>!FVX}*DSU`e@nP3)VUCf3#s{jnnLT#s;cEq{YSP?T zy+){&4gxM~GFnS7A;ZI}_$fA(ZpqKY4^yvDf^{@Js_z4tit+5#q{-x7`;p7qt&~W7-M-9=;a99ttYhS~^s_i5>S%F`cJ(y{# zc|oeOG<^9T$ZQ`YetI1bVK>^z!0sV@I-*$JEI=p#qb%F+?s+@9+M6I8LQq^iMS{~OLhLV@{j*aky_SFriN z#$)K`bNpSwF~R4BLLqKov92E|=GJ2vx+{d$PgdYUjgrNur)S{r58>$VTgjO_?!}zb zLml58n~l|NwN(828R-V~g6DrrQO!xB;ZRH-{g>MY`nlH7G9%i->AJuscU%moh2O7i zvon^SjTM|S()?8O6%da~DOk2d%`h?l4ye^ZvVTw|ENwnns%LeNtM`j(G z%VtXLfs%2_{HQmzTuD_LHR&w|!;AK!v%hjcMkYs8e&?ivPKp7$|8pHI6Zp^TGOI!C zlm_X`JU~%O5$}4(!~NMB~hu#Cjw2TpdSGjI~g5z*DlA z|GweFore(jb01lsnG1%suIy{qIJ|b{xVTy0nH|h*fmP2f1XuY=yrK4)TG(p}-4Vzt zWOZ=Q(TikuqJ{h<^SNC;QZV(i4?8jTDCv%J!%Ow!X!mJztQA<6k?qs5tnWDd)StrE zI4>e`x(~cLYRq&@r7$8ej$6NdgTN@s7n!fk<1SzXn;No|t9SBaLA^n+c*FzVB@$T0K^N@bThGgi9>Gsxp1Xc&7yS6o0Sj+O;R3}kH0Ed@ zy;_+l9+aLA``0_8=j%>>^5|*|-8>8PT9rxW@n3%4XGb=B^Dzq8vkAhY3i-d=JgIWv z2};bfr-(8`^jWRV&EFTyI%<+(;A1;(n(aE&kgNkyoF5xnHI0uI#bI=0DBF8)4OeTu z3DgG`vHKa$)Ngr+gMo)(`R*gUW7%vh(D1+m!#wy2!_)ZMn_oGP^IEuNmJ0iwwVbW^ z)I#N=Rcumh5#CgI&%4f)V>d>1aaXpl<}&<>*x&wPkXo1tx97}2-9-n^1BRO}u0ZujYHe}ayFT>qt60KdkmzdgD-cops z>1kaJme++I$dD4U?z;<@cQlK3hiqr0+CV>ML_?f&J~?d~i?2V%aKDX~@wK|$^j}J< zNS;&SFI8^Blq>G+Ti{8MFUewWDmK9MFGJbXj8d|XGQyME@=!Kr2C7Ca7AuZ4p!~Y~FE6ws20dh<7w*)^!HRB_D%`8i92&Y!wr)JS0*-9nK2;i$yBSdgw;HKU*O3 zB`jNrCYwj%28(j>@$OX(Gq#1WF^~07uJ9x&8K|S)eqSz2$%8*_ev1tb>T@{r?jRf0 zb%mQ5=1{+K#R#T%-yK%pzX&O&!y#kLPz?VyfjUMgaXyPmf$LJEA5$0cQKjc;N=W?Ra=MXP3K{cFd9m*CGwPKy{?2e}WAP zPo`ieIpDjJxzRJe(@?`5c-=t-li%gAA~Q#}UbB{Q8u|RZxmPgt#1KrhIKaI%)n?lh zo^S!PELh>?8uE@CO*dw_p)x$6FqK4cvxS`fgLmodQe_roHOyt%Y7?Y$+BX!B7l)wD)l8~eK8+4G@5ZM&Z((1~Pr4_t zQTUpBncc_Tc+- zt~xfDWYogIJYE7Oj<8~Bw<`HSzdI~(^AJ{X#T9GM>(dIsebOs!h2yy)e3)h=x3**( ztvNl1KX&vz#C!U|_52*F_D`VN@&n?Y@$+Ej%@5q`{g+vAZWs;<8;Z6&bjK-tPecWD z52@ih4cDUdAr+`o_hr*>PQrh;yz$lhS+vx84V)FYEotT-NVXtAG(LJC%PpM6-wQXR zuA6DFxylg^h6(-Y2hz-bNf7rVqFp>v;2NeCB(qaz>*0)1xkyE`2qcEQ;Zv?AQD@vG z@xqi#urm4){MuQ_QrB{9M!-~NxLch+-M5-^JC_BE61CX!SRM@v#MHk{5%07N=2OmZ z5#|Z^MU$i>*x``_SSXjytPi<2yvs`hnXFDyUeW`X!!t!c{sc2CD}ajB4k{Zi&3)2K zWOp+1DX3;2S1SHP%AvJf^0;w)QtvvPHgGva+}1l?O?Vza; zz@oB-;Or%e4&Ci@@R9cux;pMScRSgDo&7VK4|NGVg9$`(H?HX zPDi2tg#5~`onT{no;tlaR5Mq_JKx@bbJP{?Z)Xcx78|had;4L`i!yFpX1*xoKONku z`h@>}XbEJWv!D)4RJc!10D%;@+qaF(=TYp!&Gp6OURr{chZn_a+oeK~B|=EiPi-G!KG zeZ1a>r5JZ6lzXppjIK>choqG+;pTjOw%Yj+tlW16n%sxc#T;Sob4ZfKZr=hg8|2x_ zs;gxAwFm+d(x@`$0M#bzb6+J*XbxSb9T@&dL1jG(1+H7_qiCpT`f@33s@-3E@J9+JpDkY=xfbBXM0;C8n50!jKM2cB<+s#Ee+QLYW4*R3_2K2SRQvWep9| z(c?m7qXeI}3>N8{bK%RB;mZJl%jQ3X%@N#V;>WI>oOB!J`21oen*bGZtZ1K{T za>}?fQxtMT0%zII<>vY5u%wV`={O?gyY{ zTn0CV`|_q_VMlr~u$EJ{&qa7cMD7Bw!|;&rJM?#yf*cHb$Q+q1@ta-Rqt@LmP> zX~;I%+P|NBX*2xyW#q4xi5B?C(QF+ldKHuj>T?#s&9rXVlK%!CR?4#zNtgLi zzXL=$%>dblUD+P9A5^V*0e+?KfgX2+qrUT)c+O)I<=a5JE%0-{d()w%exgMG8u-s? z2wI+Khij*FxynH@X!m0=?ETz8&#zTrqqo4_D!EAeT@%^HEACu<{5`TV-hq36CJRhL zFX#-pOXn931RvcgT#wiT9OI7g6W4p8U7HsFq}qo|eNjcv{r2JHsV(A5LT}(oMk2LX zy1|v_si5f}%vDcF$%SjuXM?{z$gA zdyjbKFLNAan9yKs$)Y?wp`}+{L>S^ z{bGhg1kSLy*bm{>X~?zsLu0RXk#0^L-17{=&?*TwwlxLY##|F!HFKaZFRH=SeKa>) zO5nzATZQ|Nt3k%all=F0mUt^cxXW01i-vCT!-H=|vKitTFsAecX-dyywlZ3rhv*cO zs8$010Zk6w*){yx^pV_3g9h%X#Z1h!45Oe%KR%*5fK@%&F0vY2&Tr}sW)b;f@uH!n z%rtN`=6ikz_Y@iNqpbJ*ntyL$%ZOYudu)dV?j~rYY6R0yByd$ydJYYhs<`acHrV1X z1A9KJ2z%KO8a%&&#as=B!_QLa$~Y=ctb&gTJ=84t6_iYl zQNHX^5U)GPk4tf+A}JB}Pf!;;wVLc+%LTH|J5T@EL7b3gLoIW&=!0n>JlYmdi+d&6 z&Z#ZjrBDej>Z~p-Y`;sM{C9`iBd7ScYjc=|=Lu2>E#yzn9f7G2jxe7$!`S|{8PK>% zIJ*gZhos;_;SRV1R|O5E%O&$DRFp_7o>frS2yGZ_8A7^3j;u^eg-UFM8Kf(6miupl z;=wuWY>&_lcr=?8*oI-xg|}SP#`i+5u?&vZ6@hemF|2q#4L4>U=f>O!XJcH&Q0uJ@ zDPJBzPx28c*PMr2mlu)bhUqxyfC{@b!JU;fo}}SjGI($CbZ9YGWlg_z*u9=E${aO{ zbnGPP_)l48sehhIXWXJob5$DjRvW_ggw6D9l87bSdgFqsR_;%~9GVzEh0j-o+14LL zGH;B7iT&qjdF=`?%{bq1>v}5GeHZwj!Ll$^p^UztcrX66Kwj`eRfECt4YVt0pWu4; z0K3Rs3O#z9=9Ft;L3%aSzWoG>^p}h-h0xgS0aWLIhLdcKrM*qd@Xx6Qyzbe}jMx4y zcru$Xiw(j#{g1e+ku%up-hO`I(iLP8kwpSYnZ1}a628tZahP&DiOgEu!MCIxMiJu& zcF7V{s<8yA0CrfVRlId{p7>(UeX4dp3g^1V;*0j)y}lPY(dKjpn=#UY^Ltay4jq{(@Gw?FcZC^->FyFen3fLQq9|dn zTZg%c)@T?K%MUO<&jq;;;^ns2fzr7;es#GV+JyVF*rM@l-IG!@E|P%es$HPizn|aJ zU5ta>zlzkGE5V{a2_{z%Wd)kys<)+Br6FWtk9cE)z#~X`8_hnCoF5>w-;l+hYHVpX zU`yX!BIm0YdCR9sq-R%1XAJ&mngzAm1(*15*6lYpTfe#-v$dqW2!;B&9ps+)c{HcIL zv*tp0%Te;2F`GH=5{~Oh4|sn)V}u9-|O>?iTaQ}Czh5W=+Wuu%FAWgYkkuSzY!ySRx{)Kp?eK#t9> zHf4)yHsZ*LD=_=Pb?)k|LiRUm4=ac<#NB?oP_O(ejhH2a?PHbMLxoLT^M#R2Z+8?V z^66AGI27eHjQG7OJUudkD*=gBp$} zw&KJo$@I2u2$?l&VUOw@_9lIw$jmm4nH_r#{hM@Pds6M03AOpq*Z!7|yEK@2nJ^eH zcM_N7Z3K^M!CSX82il8PG0ja@sJ%@COT(gQ_1|VLFVfV0LdSlp+Oiq#+9!)VhK{0H zYHvuf^D4~zsg57^3!K!wl1#g|0>ACND!MXd2#Sn%LUf-Tt8=iw>ZdmCPfW~`6F)8ii0OO)QC8y`ctO}g{JY0)2g`6i8H0dv`+ z*>kWjS&EvymvGUq0Jbrs1ejTxz9OoG`-6$!15t#btcrP!alNs^G&B>a03I(7U{T2fnX_{nz=+{Ew~S?EOMpzRBPZNWGGv zWsBYMjLR78?ouTUGY)pn(qiYb@^RXY%{13zFSX=a;C89kw61F;eVE-so=vNHGoJv$ z?^h^P+ePH7QAG09&ETV*!u5`rk0Qa79kApMd!@%wWqdODt?dETXQs?$>@il*)6VPc zDB}M%9~U{g&tWg`--T1*y6lpl2V8c$i_H~I%w@(sfxW#07E9WrL+^L8pO}Lo`#9F$ zSdWt{K9QM_m;Lur=(PN9;~D~Qa+_M0Q ztGkt?kC#F6#UF(XEhkysH6AKP1u8eQ}+4lK^pKug~e-(#R0Z+s89DD zMcEvo27^@5n{AH#(LeEU?M$}AVuK#&_+kwzT3cyPSSEWgd@u7VhPa$K1uddefc&` z+I2aKHK8oKezQp&kP?T#H)OJ_#j2vTD#3YtLy_(G&~jM^DCxDqsm&C2tD z+$aTfYx7|lnTJXILEV`tPfsr3z;KLEjIno=mEOO~jlqT)q_M-EPRQNKz1Aa$Npno^+@o!A_ za|<3@Ga+_GF((CYiL3`^hUD^ImP293vf*sE%07|OAyxj3nh{Gd+6SwDj3Ny9#>s~4 zVj8oLh~)}%K`}F*n)YWgv$7|2W-lwOve*GG1*{xAvD{6YnI|ct>@P2Na_UwVmw27r`t8u*bQ$Rj_x+~F zVf@{SbozQyU?O}-$11IA_%qucU!*5cn&%emAGL_Z{Sb?$OP`?|nhOMO#B>>EorLR%in(H)vvn6g4Sz=yL05q;V z#QBfB$Pb8o3U8b8p=D;U=vROf`+Q-v=)uer;=)jW_T<06F#V0-QJ*60asKQOSXUyN zIpryQGo1#djlyh5V-8+?pJ+dG*GF!rZwmL?dMs7lzd$y=_n@p)I9oX9L-aE(eEs78 zJ)pm0_pZ}4_KT3wESLt-=LRyn&qk;gSIk<@)HCzDSKy^Wph)#cFvraq#YO7N)0zi` ztWNSN|L~nFzc^wa7#V$N=$7!KFFhOBsRw;X`~v?q4QHSlyG%+=u^^S<6Q=^+a>w%Hb7F~%irRh8i!D2v(O3G4Fs(d z1>zHx7vaq?J=%CpkFMQ)EkvBVDyJMT+>Snb~-hhf30o{v%B6v*E27Q ze?Oc%C1njGe(5oG@&ees31Jcic6eob0rVfaNQ*xTZu!sJxb?RZoH84PmjbroW}Pt@ zmcAKxX#Lk<>ZZkN-bABs%{;REag)|<$QLs7&Vp~;82qJ9(%;z|!QV*)^x6Py@86lY zM|~cX>i0nV_QwR0p5VOs80?HXD=?6U;rO50O#8(fT6+H{r_;QgZFTme+kYfka?=Ii zkI#dpw?;dpZF57-*ACpL%P*i}Qv{RI)uH11U69fif?e%#;BTRfM*U)FEILL_leF2C z%5+>Aa*F(%T1ao07%J~Ok@M|V(S~aU)F)}d=T1HjTUI%tG~M8DZFxsA-G^BL$i7|)RQxUpeBX4i*xu1V{~kc-uG8Fy+QnFE zvjoQHZgW_iKa$Nq@Pv$w6WKE3=e()XKCbxEYP1>qk-Pjihh0w_OMBjrLcO=i2k1}DV*BJp^YPtWYi76kCuA(Br$=4! zbSP>k4K=LT6o!z{i8OK}$uZmai5jZTueCU9-9W&S#N$YOD2B~Hl z(GlUSdE%oKbgwc*=dXk-?q-2{stlS9ddQ_D8?hLV3h{_R-Rxmk6aP3#9{2dfJLswH zqEW6Um?Cyy0kK}}=u{P!!~GT4S&l@#??G(Ujnfcx>M`%OSs%xQPvd!)_J;Djd{NL~ zZ?<0ed`_8W#Z-4TlkqY)Mq8(|;4#5C>F-W1KsJ*`1*d|+FX@I0X8XutYY7*leU!CY zE@8raopz-BYEuM3V>Mh#g-wWXXo9_=EE7RD{S8;6b z&raxh?{-i97t&6E4<$;%I{ezht0S!)KdrXweT6L(WJr-@kzwI?3UwbGm|u zc^5vYyF;m7ZlYa%S9y60RhE@q2#=4tv6$9+;XZ{FtthZ4@2_g8C_F|-G_O-fbR$SS zekpLMlrh${3*d?#BxS~;Si+oJU4D`twjkd(#DPtVkmHSgjzGbIDt5Nmj`LC0rM#$< zT>OYkQsTG5=De$*kRyvz1dpQMC1dune=2pH@S~9*6WJ7N2Fcm#So7>9nF)8`6H9%u z^ji*9EMJ9LpEjWRd>LjnB?4EPTjP|D@yvhRQe@pOe3P(;5x1TtspCRl#xE2cR(rFu zY_Y(KIs|9hC!j%Su){{H99o-^MH}2+LO{$%TCFAQZq^7ahJZPI?I*Sd?^i`A)D z*O7tuO0H#HjT z^ykIDbt!bI4qFj&kiI>6CW_pbhqh{Jc*Jy}$Y7oDT**X|f>RjZtlj4@*F2sM?#O_M zuZw6zUK+~`P~yc&ApY}v5F4at1WNA*pk=@mn6pd>D&`uo?{}q`x0w?4^`^r1k$-97 zI!pE;R|0>!2o9;!J>a|}kz3O}8SZA~@V~_+8*ddzx4ro*dd3)tQ;9QqQj&|#r5ZlNQ^!h}wo$-*wK_Tt3) zKlYyV^Unpo_k1gyHoi~Uwk{BLrw3+cctaXHgIZ7D($aNHP%UOV8623;=6>iD*IW>M zrt6fj)^iXu6*?dbU8cZ|XJc5^FV~7Vvmv4U)W@$$d1^f9P8@(= z=cr(&jW1g;`=`J$5PVDyhHPVSH#sD};JpuPlV5Ny40v@L`c_OvnRDZ*)w7j`?p#e# zd3OA!Yo9r(E1T)t{9D`re+67oxgA{CK&IPs1x7;#s;t!IZw2Onp2`GHQ9x!i-qaS$ zJdB0-5>0se@+XwXuc8CClgYDx2{W!7#=HXmfbYaOy05|Uo$F;-+2KL-_rQKi$hbtW zjYr~tA#Z4g;DQR!j3FZkqyt*xg*=ly4s07CKIo{-yep+(r`|TcY|~`%>LVtoI^;F4 zt+NnxcQ=8=UMYy077X)P7e6XS6N87UV8VcxFsXAm7yHaWSOdrHwTRU5SiNwWV@blwj&{eK*9XeaGS8zm}b)V=5RF3ApMq!3ETh>(gzdk<-< zBqJ-47Tt4RZ<5lG%n~Vkg(9O5$@hH!fquC6o_FW{dOe?yhy1<}SgGkxWS6@!>;448 zdlOf>(5IAVgFIlb6|JUoWBTy?fo$?EvylDZ-3PDI3h@QccL^AK45fdBz=OW`P^LYM zk3w^?g`{yJKNT8d8VfRJAB2adxA8r-TVM}gAc%L%q%N9>jbg5}aOqXt^{1Pg7`l|1 zFlj1XGO(LFBCChK3!(-2nNrk9`Ub3i3UufB61L#8I`J5J3TFjc({P0w0(AC=rw_f@ zG6Vj9!m97E`?&!z)jmUSbdky4Pg{tCn~Q z#box-2e&pr*i0!byYi1)E?ozC?Gw4aZ4R7qc^cTZQ}n1+C5r#~z>h(58ZEt(?po(S zBcH4S8I@syn?LWUKN1VC{1s9Y4vdTA-p*qNyin|MOtY;Y) zSrbdPRXc#6*=S**Y#G-0O2fU=^7NTa7>x_cp(>9mxXYb2JP$HNWOgQ#JF0dK-kkXb zzIx|ydz&OYyZ!_;Eu*vtLikH{P}(W zf_i&FZ>tyG%4c?h*$Gq}MBpc_ln zNwWJidTqu7D8cbqE>4-gCDeW=&t3EAOQdn(893jq89lS2;O(G*kXflvQ07nT6_4Z2 zhbrXL`4?#5#%C~}6~oUDd#G`81F*l1pgr$5R<;G;-{cdxZBhWy@lRp=3%ux1;aGam zFGc9iBULVB%p!hs`%uy*kR(L$yeicQdgD8b8fup0!1V^!M=;DDzTUx9>2t8(e+*1N z?oDO?v~$kBmJ z*Z8~0w%@p^+np}y9ZMG78Oc6~8%ZOpcwdOzc%nE`i`j860FKmYGj@tOqL)*uV7r?q zO_vMDWG8o+?PEhaU$ue4fy0Vb?U33tA@N>O{&x0%JjSb!hh9nWdF_cYp z{aF5P2McN5%t+WEu7SDwnOvTN22rYc%=NTBWBk>p3b!uPM`MFEJU@RPUASc*Yy8EO zynXc-phb%s%(BC#_}@?Wx4qEwpx&Eh-IP*y{1nu5~@vq!D_v}3Q*ieRg z?HV9&m_j}lujAqj>hNTtE`2`n7H7J6pJ20rEOAS@iZx4aFf;PhiH_HC3}2ST+Wg@A zEsYbnO^%A>+toR=t=IzPyYxwsObpS|y#u|bw*>=Puei>Q2XOGF8otmkKpT^IS~oiy zbE_S=9~+L=R7lt3Elo=@d#waND>{vBS4VOVzhsE*8+$bTl0+gRm3Wt>Dw%ZQ5)`H- z(Tg(3%4FGYi19zm@T6LK>=l5F>QkH6Q=B*L^g#CC29HTNrp zo|8?etyD)WR{Mj&{Qzp3VMw(8@L5pO01G1GIon@@5ca#UMx*K^mmDdEjpPu#znV#B zTCalGcYJ5m-ic)0JWfBWL}T^bvtYpIzGuI)qQ$;t5d3i?M6Z$~?dy(mzaOg65swS- zffwS-waW=_Ug5^d&Lm%3b?AYf5oFrv7W}?YinTebCMzVW@G$y0HV&vy$vSup;)gCdlABxBc)fCN0XEXYHS7 ztH&kK)Nv{FrZBn) z=|7Y>pFxyvIM{#b`3BrpJGy_=Utwix6l!g50|$=>3#6*9*sCJIx|- zcYOI?%>sCPkU!6LHK9DCN7Ogz0?MZDW=gu$aN+L?QEiDihBXa<-$(^q)5mcUCab|< zRuv5XDM1%ISz?}Z2H(|IFc0ihxmShicyE0l>$Z6q zejRd;HE8L;KE9(;!`|B|hjVw`sOdf3#7)?d%|*!ngu?q45bh(x<+Q7|c#3?@E^2OAJI%2^}qR z(ou&-D6B-^hxzoBWfgQNNKo6^K7!ops~}%#yKuk52>7eoj+&uRQE`4DRCq8!qUh!eXYJl<<0lIWP1GyXJvFGCqa$>(N$@?}5 zTQ?+d%NH)^JmgGh;k-mH{{lZNUgrWnjSrZESq8-3xCI-V&*GP7lc^}hfbW;qpk3Ay zLQ3<<8bddRMkUgOWqnXnI*zPa*U9S6Ttb|Au7~~IZhRYHjlT{5V;$bUgGnyU=<@PA zDjfC$`Cffo{y3R#iL@X-nO~V%kIV%z*7;y_|0*hQ$7x@QB8i%ti)$wAqD$k%i9zp> z=*RgHRDIzu+-7r*(eAflyjxQZtRC^HmH+wDxKyKD?seMlBh^Gp%vO5P{*YZ4y&bqbSg7jU^|%_CvKo7<@WR-PnRIq^Abga_KWXz?QtZH5ptgoSV_Ybnj$Gn%yCv&7CI zGxY9w1ka<)>E^M#+r!^M_^MHyZWxn6>?GXjTKh)!>bNoF&xUMxGDnhnj#~!R2Y#T# z)n?XW4iNX1cbLlqdzm#ck)$K;3xwt@1Y4ffDpaz;m~%5((^q+T-2N>FuNr~hhK`B^ z?gRKzcMdW8AtbFIZK-mVKI!?L1VJr|IQpCbw{|d8zEz1RTR!BR-|_yc-!a^l)6%g%~9`<9sVdh z!;ugxINF;mNDe;>(IOX~Ywrc%d79X)oP=7>1G%v_T0~R1lO5q|$h6KB(7fby;Y*V! z@@dT;a-m2HCSPAB9B->f0~Vj)l(MD^E&eodmsLVhBUqa}ms5q}<6?pb8TzEkauJB@ zjwi#*;&8y9cidGd(HUNv^ds|>`&u~_ZEnlaR^~N3Z+Z=WEH!7nYn}1crZ~~WXc4_S zI~B)j$f1sH9jq9gf+wS$=+-;4Xk>XCN}rew(^sZ|bI5UQI`EXL%S&VZ+IP^aB#v*2 z&XZk{r||pM`_MJ~2_<0y1_`v`3ioIdBQz8;X>91KEjbr#Y}8+0f^N|QrqwpPO9b}23;PBQ*w91Q2u6; zS~DLfZhXhyo)pb9TK3b}5GQi=(NS`=Cy#jWPNdevr;MV{II{o1dEtzkD(pR-cTjxc zG}r7@$GAtG$JbZm@xkKRVDzC1uexiZ6gEPP%X+eJJj+znOOmVc>*%F6Y3|(UsZg@+ zI0XN$!M+rpuh(tDgpN_Abt@v^$C7x#F$FoAJ8mbcSXXnpx<7DTggVK%kxRrDuOa@A z&jWkm7N}NqLCY^!e5L9_7S7iP<%TTOICB$KqGp5O*nHx$M4hZZ=tHyHM^TCDJ2=64 z5f)AS%E>*Bqz~rC(YX&I>B0NI85hq!%v-a8KC!6-)$=Rq#-TwbSZ^3jU#AnXw{uv} z08>1)NC3CfTW~u+lM7ZbBgvyKb9V#cp;U7|5p(FlTt1hyo9E*A&(KBn#Yv>WCJ_t9 zG~z`@oL*c#9)A=pq<<||koi1+R9k&Hihogn1fIdI)AJYq4H^m^ETiD*QD0IiznW^e zceA4=jv-H{?!jR_K2P(;o(43eP*3*=_VM=;q$}>@-~KT$so9Gd49q9x5!u)+ZH6cG z>$$8`fow;h9JXecfl5gq{*_T-o%gPX+9i5Kc{mI0dsh+R-5lIrRt6;|cQK65R@rAa zLh@rNBKx2KDs_tBgMKt;U6&y2^ZUdzqG#c^es`RcFGZ(VY$l-z7Pzh89a>*G!i<0R z0g5+_p-T^XiKh5gz`>!n+!&ti@^kAXGJSg$vtW{gQ0B`~>~;FU>^sQ6tHrx>;vpy4 z4gVUr*H4d7RkdK4T*UKS!ZK>wO_O2&&=p37XbL0$JOqQ(O0XDN3DeJQgJ&r-A=vdb zcL64n@3BSffqFICsWgFlkKK(WTVlv@%RMCe=LqU;m?-QnOcs2$_NFI(rl1|q=^RWK zC&}LO^zV*ttl5VGGZPhZcA5va9iB>@R?Ni_G!DuO8nC%=CT&k$4@&cmu|M!TjyKMR z(sliCFY5Au$CGnSP>C}DS;8( z3yC0dRre`vFgu19Hfa$BjdW(^SvgWGae+AzSImez=+iWrD3YO-#5B!%hG#A=Acj*; z(WT20@PXPD_;A|_pMQ_SwXaO*lmstgzrhWB`1@qN!g9uZ-D~bsofbQkQZ3w@B~Fm< z_l16nB3{m48JPBoxl58TI8hn)KQ^W&aZ6}}$OBU2_d}PM0~s#g4HBbb!Ft38oW}P9 zH*St$Ez>3O`O_&pdqp_CfTR3O*WgXo3|J*HgOoNj9Ky8rhL zxi|a{@$Vx1Q6hq|#S(-Bg=56^O>pYjD&`E&P*eZV02h>ZW6ytKT;r;R_2U8~)$s$**Dhi9)F^=EnbYhx&9kUe(TDF& zN5V5tA^B5Q0?lW^JOl$1>b%+sULLC6Du6 zpbZVt3NY~A59}2YzuKq~SKj;Ec{v`0^NgWl^9fijp2Wo$S~9;AS}9R8WwgqK?7KUw zVAHJ_SSCIeByNwR$qR!)&fFh%*N&r?1lL4OFGLV+I0L(kQ{jmVpTC)PAKQ|5a*9IQ6+}xsLwx(A;<({;nHOqulxKXQUc!y(_^xG380!@kiVxVUEDrIU9@H z%`w$Xld%;0!kETT@Z`J61FyG{AhpYEPyKS5zibTs`>GQfbn~%dK@i!i8&4dxjfB~n zH?U!JIlJZ^CZWd^Nx`duvGhQiG-UJ7bhUgOrhI7?*4K_AJxS~E zSWgPPUTMW<#c5F4laj)*v3ucmZVM+$c~7YrV8W~@c>lQs99*5rjGmF?(bO{L*3c#z zxPKp6TNRB{?RU|8C%(a&<0aJYiWXHEIR&MM=i-hDS**X|8$9}bHScf4-b;Nu2OV&-DcD}Tn3ZS zh|yEN%}C}c; z$9;KrnmL|)k-3#|h(t6ik{F&p@y&zpl}wIDeXrA^XQmr4b!rMXQP&H_OsvSK(0E4n z)i z9jZ;~{6rY@{)V8LXT04GGN(Tdjeuugt!U()x1ca7jCft(ePX}%@%fRp+C~0FWX^~nJh?uTS@5Ke3z6q%O21S|rf9ox*Wd$W!{w-)p&vDRf05m2qDHossxVjG zJ1|(Ij62}H1YJzlvpcWsMz1f^@TB}p@Zk9%C%u}mpQ+%Kr)J?`pbjN6dxWyrCPKW( zf;l}Q0@j{ADKhoTM%x_;WkBK$aWWKD2J4G1 zGFmHQxw_pWnHS#Ys5zHN5?AcP5Y_;DOV41`XgTtQ=MvjQEykA@e{((FfwbKq_f>TXH9)t0t(eXV}0^@=Bwl=GIFjVH+TGJYGqO& ztiNwBJYTOw246J6?LbSC;IYu~`)AtW5R>(lo+@D~nxes0WzIkD| z7I+@fqM5mM?2lRPyaynZ$n9yz+PU3OX?Y6&K^Mp{>Ev;97ne|31y!>0bY(B^A+&uY zYH`_wSuV|jFVikDQ^RuMVPGmV^}<(p`|}q!uX%`@ypQ7+NZu2KOWN|8kt9qUlP1i1 zHVWh34~k}eKg&$pYeZzcHwazQW1!A24Y%Fi2^I!Bg*mI=;<`R<+9;t(F2CAEcNK`> z*PI~IV)P2d_)OX=2cC7cSew+fIbiODO{BcZ69dgw(1b2!8rH1JSn8`{hwuh3{o!Yp zdXDgDbSS>enS>RABF5TUoYwQ4!)etPRBfgS9om1FTc9(C8uz6$zt1k>xKGY}_dbq^ z+ZjdHM_*!VgZcN!7JIVq<0k47u87NY){*D0ZsMus@tC8rfQTR8hmMav;eYmPsk+}0 zEV$PM0Y-oE%EE5OrpyKwUdkqCmoDV}2WyB}P&%%Uk>uTKGNe$U1Ym(3Tnft-%2yg- z^;9())VP_3eB?y^fhy#Qm)-948EP~xLElFMZR``2-HBtK)0fR4< z$Qii=sK2X5TqJ5SrbrxS97urlIn(HvbBkH!uo$LiY!%(3<4F9C^U#dHD}26RjV-0} zq%%-~F)7{7ow>1(Kz%UXpOMY(a%)E~_jS1KMk}+$vm7=wm*UqrYsR?sJ2TSPm6L7W zLb%!YSckuNA?W!e*v~s@Bt8G&9`*@}t673{-gaWYyqqyTIJxG=M`;r;n4G z&O-I1VYWAftV0|4s4S>88OJwG( zO63ZsK7*he*MZf##^o7i2=|xXL|tdVMQIDEk##rj8-9Z`W{yIiv2#FV*2NmGvIZ~T zOuQ~Oh75f!g68wabb`wuU7j_U?DJKiulC;)CGOEgl?p;w-FoJ(jW|8k5=my#XdH-G z#=E{+naq7Di<|yXN^zd`qA;^g2F8r&+pA@?G#aZ6QQq%jnX-X9-kW&ZkqKNs~+8T)DwC0g+BP2>--S3NKWLlb*M-&~R54$4vgk`K8T+ zV5?7Dv9thHT8r396+ODwRG*$1$O7w)Zum{TnXT-L0PT4~dM)o1j2(;t^@0@m{=Qw1 zoqrF_LmeTh(-v2GSW|<|H^JlWCDP4zZ{nN}!^;)}6e+ZFr=CfYhUsCXqk0ie+C83* zcr=0rewD?~s>^9;i4ay64&YapTd1MHXQC}fkl3RlkQH3#JAWGV@zEsud~gEqu*ql6 z`wQrmbG6)qZ4M--VKd0}X<(0h7+Gd>N1!%anUv_xB{E-Zsrtq~CZcWwKNB&b-AM-2 zIzI;*TgA}yTL&7?45tRukK$AYF>Qqp*{PsKLN}~tPF}x4Vy4Zbl9`5N*W(P-Z_>u! zlbq=oy#(@do&@iz%@mq`6Jz`oc2M~X+v$0J)-771N;-#vnb}Qcs90b}vP#2*p$3&~ z|d9w%knjVMW<2Hlpa%W8D&wsCZiL@g04jvi2EUJ6Ek(^v|p9_mrq?5-C}? zLu9KSx9-af(!Hx3{1X0SCVa>Rv82&NaY!1EuZba!wzJqX>)$h}igNV(mPD4j7RPvb z7Si8`_;arM6&H0@fmYpV!)gEalQ5}f#-*$eS$6{{4u3*jjg{G3Y$(j}h{x$KlUTuG zV-llaPygk;W7O<#bKJ4B-0IINq}yo*SH%Ioy)_ZAmft@pIj1myng`4?&jt|M1 zHx;!4mR5y49A;)O@4#sb_HkcT7&`WD6<4|>gHzfuf{e(BV7X(ru}r@a*(MEG)xvMy zKdvKe?g~=6qD;VzJ^+O>%H)(!J{b_yLvcR;&iW~wig~cKzAH(vdCpPD=3Rk{A9$0X zq|c)DlV*Zw?pATfZ9 zjLuWlB@EDiDPE)@K=db4QO#ZE;f9ev-Y2F5})h zClb%6cI4!*LR@>-3% zCLr+P@Z)Sxa;f+fd)ZHhVsJL;&D3VpMvfwDEe~VoWo=^rdI6Vq;XSLAFp^A~stB)k zrop0QC2r{*DPlI9#U1CnW_MByP<~4>mX$^@pgD>j)!jy=Z31A+ks&akXE8ps2rh0% zeuvbBE{Qvcq*6Y!ATmxE7E*`F8Iv*n<5+gVB55j!35O$_=8#BjE2hrJoXP+EknbB! zBdH2N>=O2q<&R8}TAxAZJu*~Up_zqq%5((J!&qVZ99v!*lD$QdB208T@}m`lZpH}y@X0A+A*V#`H|^=gpkl3KzyqdMOVFVk?k+`f!nh! z)MM34(Tb5F+^8u!La8Qq?oD+OKGRvkP5Z~9U*0D)8O%fH$Kmik>ml@>F(7J_RzuMN zXX?FmJiVbfifo#55>4)^pbox*QEMeA@qGnllocLM{Kdp67SSm)w^8}@pQ5W>3dBdY zjJd$)JXbFpN6*;v&geZ!?9)xzU?#>pplJE3i#}%oc z>tuFW(?WXwST+Wo)FRGvS8(Pjvh=sSKUlrUgM1?^v^&3wv;}Ch>D8OL47V7r(fc=4 zHBX=~S}bT2Y{s>}ipcdLb#laEW6hTj3~eGL?;k2%TO%Aczb`TpPPH*ApZUi*2N0sXzom|`%41hEZJ){~5F>`{2JDvxX{ zR~PhVF2DneR8W`oC&8*SnINBU+?r@9tP}5s8|x=^wC7)^0 z)a~T{i&`dSQzNIt-!ErKXF;~j6f7)>gAJ+&2_5>)oyk8+zkeD_8!uj^%e^;JmrKX- z$2CLhdMXm0%WL4HgULiU=sxzoT1Mtr7-RO7xA@w38!O?c1RF~B;kjylU$*cGZ8dny ziRm2Vj2c!z_E%>{{OCQVR)oyR!&^xg^A5|#Ou#E&tk7yjDcoXk^VXbXrpd zxwHjrV<>7`QmmaZ$%j;qJuTT={^#(l0YEg#{9Xf!Tob9$ARNE6wrGs+O z$8;N!G(`LtS+V?`*qp@0U1r>Lg_M7VqKUcH-~y&J&^-u3x*FK_EmiPuO&e3R zcNG~i>7?NE&J_?XRt5QerZn{NC~j?)3ydiBMVY@AV0tATZ=Kx3r5@*b@m(i4Cb1p#i}O#J2YmYIbbKx3MGWm)#4X?ZIX;W#mzz(^+>i z`I|2Kek)bxpDpUaSO%Tg@sOy5PWG3{a9s=S{0R? zqNfJHp=J1~_bcZ+&PHVOp8u`PUI4*qj`Yz~4OYM76?BJ`phH&_e7-#iF5F8J&M;rd zyW*9|jONdHdU!crl8fOYmlWf@*bm%>4I&}A9w1oXAfTVGe8SXJG1zJ0&wb3(qJ3A# z!NG~?^uA5oh7K~upB0A8I5l1&tnxoZx&VdFxS^aVru>y zxY2jGrY!U!7`|`7t()HAQkh}S>W?vrVmp`*Z_bmbzZdaHaj|IO`il_RyO}c_C}8F) zSHQq5UHEspPx#C%9$QCDW_`QT!B6}cW*a?cKJWR%rKH-Dvu|QKwW^e>zZ?xnNaRKE zs(t|DsRa0*HN@prh*$q@qweRT1s_gz;)yNkI5}?*S*gS4wvt0}&!u?MuEFobHgDx* z;x7=hXes(D+J@ct*otZ%kYIkS@nW6p?_uBQR>8#i29R?wl#42@fnSv!C`_rxQ1g>$ zzFvi_F~|l7+hdrQYXIf*UemZFRj_moV&#ro(<@=h__I_<-_2T0J*uBU`omx{W1}Yd zX}^i?^_IrW=qlQ68BKf7@*TQ2CbSccz zqzp3UEpTVEKC$sTM&GrVVv_QBoL_28zRY%|^R8LJef?pG7bFmkQ)*P_d_FE2*UMeX z6eG`z3~9qZS(5av8b1VNfTQI;xOPsOcuh7zMr;rw+T`f69hRgKCGp2SGpfP6z+BfI zB8}rW!uPv|P*-C}k_4G>sdGD5$8nI~cm!PPb%<2c8O*<)EAVc4!pSxiGa-J~+@qvE zW~Wyj_&t!P$I3l89mCnwCCZ-IPEaOSHkJ!ET1l~unKfMGmQ#=^M| zv+&2^D{%hLB|*#AixH>rhv>pe-t zWfS_+!wn~@ZbQvpD^zv3h<^E3a2c zN)#>On#*SRwQLJxcj^+ZkFhq&S<4-w|upAp`To%R63T|Mejh{1m*OG-&buoCL^R%GpUMJ(AmW?4gA|xC7xJ!&F z5x2?U&z~iN!muwwwViL6k+0{$>69qOaqR`>>ZW6GW-{MX-X<%AS)-{@oi~oX5kYnt z&I5^qjoh9Ut;~K#l@w-{G0*iCNm}O-nB02}=FS-chy7i`xkuYDI(<9|mVd=OdM`=i zjvat=FMo5H{Xg*HbZcfLJAz=FC+yxNO?PU>(7NXfQM#s^dhSVQymrZvML&st0si7Bs!+6%^DmlIC3 ze->kj(;iY{-7WYKDx~ff;cSPzCYB60;1;u4aD(rA#4dC|IngF2VrM3&lkyCISn_iv zZU?ql7m`n-Y=tfX=g_7+kd6@zvs>T9qn6YOC`dJDMw?W^8M6r>_@YlLR?ebAZxzBM zFUIML2XJMm1O2$r6#g6Q&z;*AM^qcna2HAwQL`eKNiZhV``aSUa(oRah@Hklhr8T| z!>jo7+ybs||AXsN=Rr^7Z1Cy)BKVlQfwri>XO8O^gW1X(T)ukOx0XVcwPs_FQTMj{GS{=FaDP->nPj zD4i3+)~y*RP)#JpZ_=>is*~{GGBv6$F%pd~Msd$KoWs9*+qg?XhGfZY2ezii9iH>w zQI6YrGWytWq0a{$I`RHxdO&UlnUQWr_dPt#IXNw77Z&HEwN?(;S}!M2E=p{x!y?YK zo^Yk3)aXT3TNo>T13MB=;%UP)lHs(S`fB}$^+C~?n%o8hlGo5ZX(^^2eu*~=lbECi zX_%tIyNyI+sdw4~P}aMEx-XVF_Gm;D%yp5X8BvCytFOV>e)+~KbnZp1 zCmQUKY#1@vl?L?kQ*PXuGSQVWMrdepK=|m2DfN0ZgAP1CjXM3}bm7>kIH?oy2R=n# zYJk!n5wIg{G8xp(5f&X*$0~Lo&y(A@CjmC|cP}N2GdRlE38s(r;@p1M$V!TYLoaVFpM8TZ1JT-(Oyp)=wOGMG=xd51=E82u8sgV?df%*q4-x$|0+eH*2KX;}gkuWrW$ zTeWDwZXaCnUYuH;NWy6!Ezu~ik5!&?7IVcekgCbYKzoKG*W$2|VgGB!gyoJny2hFt zAzBIBGzMU0&qyY4=V{pT{1sbaaFMCHp+FmQvhj@3aeQ0l%E|qV!u#^EL}Fzs)P5|4 z;F*cQHoMW!Q=+(EmEst5Vh#8X6hR6^)6DcsLW!Tw_(OXq{I1EzDQ7sa%h*BR_IUso z#WUA#X5+#?5hO42C9die&;tWknE9v2kV#F4n1v-GQYTnRV~u+SdJ{r1@(4e3G%W`u zV|9YA@hB}bj(RN9rMt7Q!u%-@m~n!;aQEOYw(g-H>2ANwJpZzmMl{>d+(DEexPb ze%a8qdwamp)`O%xTP|FE{2!)d{zOT!&p1ww-}QJ_uoq1R(c#x8II6n}Zh0!u9DNpb zg5}83t|#cY`3*WvvZPLqi}1CP23OOQNmY$asFGbIiL|_gOSVgLRn8LlU}Xh{uQ~){ zBF_pxPwRn|->it#tQ}O!SAm6rI}jf0k0W15ti zD|&|`Z>qtC2XU~qunl6DHDmN}5AJM=VJjl7xJcDn3@SK*Z>4zF;cf%2=9CqTtF46N zXCC4Gs{&N`>CPC}oM09IiW8YwZz7pq4@vf_)a=_1Qu#QB{G2-v?0qUl-{%~I8<7*q z{(%jgPu?^>M{x++ZiTSh2dwF;O;y6SPd~Y>PmIvWR}p{ROM#~n`ea+cGrZg0ZYMkI zEheH0u`rOQMM*C}X8k#+YV2elNp5E}C+-x?{g}_i#OskK(a9vhEgBTuD`Ah@F;V)v z^^EO_bFd^Q9N1h*^7-R0JgboeKQ)XQtGTk2%sP)J7O6l+>L@x*+6!l`nuxQ))M>_h z7R}-^xuQh|^dy)v0OREXNam7Qem@&R_FGrNIy+Ce zwbzEt@s^{v>rTQ=wJEeKeHxLIjl)MXhS5iFB(reJS+tpbfgMr)6YC;aa;@PHeCEvH z;Bh~EmwN-*13p-^`ZaUdA{@+zmkS>_jwQ>kM&f@Twn800yU5Ss@DZOY<=ABQQ13mQ zVsVhs(sr6*&k!PYJqH@y@*-<*V{ zU*chA&KumQEJasYxS)P0&ppxlhH;fjG|o4kTew)9_PMWP=Nmebq8k=;x$FxF>pO{g z_dW?sdu3?(lzWi z-}&@XxYh8rDAThCs(I$mi`PChOWmIMZaT#GI&N?%tA*xj?X2eM7~);845g!9pjOjj zc0+>%xOJ_ff#YPj$w@!Z$2f$P{`e20_a-2@U`Z2mj-q165ToHgmgwj|gyTEhgpSKr z3wJ$v0&V+$FwRY{U|xeb@px85#{JoedNY4>*FPx{4ePm#uEJyV{XA?ZNG*rOZyHhU zuOn{x`55=#2!WrhJLr#Y7D`2aK!e;poa?8r!s0e-dd~9*l-mhOWavNEYvx&IPk0ik zTAoEtSE*2g;6{;oi4XFrD!AWskqa%qF8s9ZHYT4cWgkqFrgJ)_nZHl;=+w|MjyK34 zdpTAp-X=$G7EWNhqDP4KC6&OH;jdh}4xu{^%8@tH+u*YA5!y57KOur3DOm*@EDtGxJxzm<_FIyT>UETp# zxE_Yyt>>EK{%@Zs#I*B-3aA zNzriW*`PBzlUp}=JEj#@p#QK3IT`kbDK?j;F8ZUX$ZIiXuW`a}CT;L!{AQS=Re-ER z0Qjk`2J@<&ICyLte1Fk`ilgo5won6luzMYG4gZJo3!`z2_#{kxmcR}LP9Qh-&LS&A zUkGGvC26;}1x+n-q}IL1IGbNOL~_#?)^qhcIMUcBZ1rLQ_Lll`KR`qDkGuH!R(&@a%iBxlzbyu``58Y6U{<0CBluS$4!97F7f`fx?#OYon43RK6}f=X2tKG2#3 zv#QN#ZaeQ-7~@JNb5CpDnMQKnu`lsSP65tqokm@EWy7p|dB%LDI{f-6PBra}Xoy&k za8mhRJU;Uc^ZUP>FwJorD2^0izj6rq>zqJdiAm6efNarsCrxUqmw>aH&v8Esf56X) z_fhA|4o}!xcOyLO9rCKTM8G2SzQeDRF*ZF|O-wx7i z{pnoIT;55nXhGH`s}QHI>4NVSy-b(DfE4H-A+GKsxE!KL-YPYqd9WBW!_yVStG1!X z>)Txa!9xD`^P0;|{~!!`T!h8G7h&G9zYsgF5}q!OLZz7@WLVya`x-lm+cp0bljV5< zQ(ljvR@bdzr2iXMx%4z0ThYu7GmrS5OEdTK9RL2q`<8p=zvXUk&0tFU{m5*Y52$%V zpVaq_Ba`+P(@*b~(8Le-+3;!U@WLUM8_XL`#_U}~ekA7-6DvRRQ};D|o~J_s0^3BF z=Z90hy{9j7}Q8Oi8)d zu@}5HG@%mjfGFPDfX8i$@Ri#{lBX#FZQ~hIkhUFqr#CQ7Gj@{aKeEWX+H3f9(h-qI z>|>4-|Hxg3%lMz@9CWSgz208pJz)uSnq9-!PP^!`JFjp{P7;|A zSdFqu*J0DeVu<@v$-HkiBu7?ECBa4aga#8F>68A)c#8M3`DgJAAqOG)Cw<_$c)rex zL(#&6H&eLWS2Fn8rV;G>73o^N?|9$XjIOYyVBA)ceUA6q}vGDO#fUfn;Fx-H#}yXL7;3^*&aicz4_iAoA7N(1-4&q<`xptOeuskBs-){o!&zrU{g-uIm6`F=ki ze>Z7jYO@wR?v`UigBc8HHl#y-cJi}l{D788BM6Iiri}xP*us69=sc`c=x8~^ij~sX zXMc#T=bw?rx^1X*^CU6BIR2f719e+GmL+vp!&|v9sRra))+%%z9SBKa7@Wi)5uXE?cNFI-dISraIZ0#p4Ac%?Rp4gHWw zyGNeJkRN~H_5d~d_0MSfMCUdMzbp$?_wLXSInO|4RSoX%i6PHNEF$enQ*pV8EZr=) zf>ngQ-C~m%VqO=8WpnaK-(7**yHpol$Cxvc@&Z2f>#)rhmn9^3DSbB1j<+m559-&Q zN%*jH(4l~QSzDsd1AvWyaX&0EgF&JhqpNfM#ZRoI68=SJFg?u#8W7l`6NRG)j@p=o3 zpnvLBFc?`t#z!jCx>UllE&$`b!mu?E4i!a=gP$gL#MimEu zzQzw2*=tUVI_kJd!YEBi#*fJxo3f52`*EY&A+oi;mCyKYzzoI8v{lJi@+NyN$?wU5 zBe(zIA%#S;F2RsF3;oN6vqG=@g(p97q#+x%V<3t4s>HfbX(T)PKzgkLQ5dy^#iYLE ze$KapF3YX_=i^Hmyq+Z*>)lP}f7(nY$3{tBB`HwZiY;i6(#kF2QU%AuEKpfm3-)^F zS^S3>h)A`g31l(co@+~~z$;q3Km{ZHdDI`FPNio>NJL3}$O$>eF5BnaXFDVcQ|rm8 z;sYeJ^aof!@`MZ>L;Ub=7AgOjfKL_bai@bD+4*Z5DF~ZSH@@9Ni~EO>j=6fQMfM<# zRI?&8r%lB7(Sq-8p(fuPGJ_ub;lwFaw4>4M@0b=Eh&SF9p~jp>aQ?3Wj0g7eA)dy} ztfPl~Gna75lMU%K-|bAFji!MwrP$TQ4$O4WC~9q-L>pt&@i`qJ$@JD{jjD-Q8#oAp zz4xGe$UJ71vt9CbK|OY+zI6>Ztc8dh2gpqGY?$0Ik)4t+h0648Os@2g%kBq})Xyvz zGuEGH{Z+b9RQ3qo_(#w^q6E01c19FgHH98fw576Mhv{tJ*KnwRip1vV7`Dl+k%$lE z;v0ce{6hW{&X{Nf;?!h3)}PF+z4=yh&nE&DtXESN1vju>lL#H9i5RHe4L|eK(fME; zO!|8YuSMm8?9^rS$1@W;ZF?$vuegy6yt}t1Z`df9uyhbR>Nt_E*Gp5Q+c=n_H0d>q*vy_1-l z6zBR=!H{2iCL8sWEMatA0?e*Cjm~RJ!RAaXF5i2aY|=KP4$Z|Fpxs0rZH|E9=@~d^ z!&5k+s=*t}j=(YL0`D(-AAVHypq!%tOR{LiOERUTzEz72$b3qUe*8qvAGM%!R<5D_ zGveU%qKWjr$wP6I%`|xXqletY&%A4BEnnEw39V0tVf5R7_LmAc{me22W@?nth6e3FGsL@rLtJG$AILf_<~B)Pp-1;CaAoejSH7L zayOKhu&d5@;MBkEP+xF`tzIbX=C7@y?rt7z_G^K0-uY4T!&44pour9+k{fLc-5?nn zK5@n?_EwWf6^^h9tFa zHyw3u4UMo2g*p9VUhCHtlz%k{r%q5|&z=Rq#Yt!Q-v$fm&Ef#=L-bGd(^6vd{CC1_ zw=MjNiwbm8{1dRBR|L1`k7St(X3{g&){+;&SD<|MA!a+ml{w}+GrfKC(7t~dt>AXi zhtCGk*N^^*#W#N8)vQbu<>f=|;pvz$Wf=3+QQ}YVLieFzh~(Favn1g6ACf&k2(QdC zW`9d#LApzb(~SEfT3$DpHeDC;%^THNnMn%vzWNOdvVU`iXZ}Mon=hgj?Sg~XN{X&+ zK0?>e{R;IT%lR8WY+x~wVj6GESyX}%+rH;Lckj)8^x9o6%Kf3mHC=6oQbSwjS23L4 znjFZRUy%}WdW~o-crnI)kS06A){zlXN05Jg33Gb%Dfd&@p$!ga{pLEjIole;HT204 zvmg9??bo0-+?ILg$V<8v*U_5RNPefBa28RpWg!-OnERaqAm1m#d(aWm4Uq17Ov^sK>BWc5ANIb0k14mr@x}k;{pA7RI_Uz|3mRD zKg7qKw+nd&Z9g7S$0652T3H^QpL#225Y3) z>-ays`9U>Uzk>1^c1m?oIyn2rG)3Mey$%rT0_hSf1dCu<^JBdNXKCaK{{ItDOBSuJ)qpanfT)1lz05pC^OrJ;MINRg1OEE=+qzAQ|2bzAlW zewVCgvA>J)@ZVTWh#3N%l0%?TyqZouwg{Ty)gU}!l=^&POaWVg6QYKYZ?BKcH zTQJb+C{rKri$`V+WAnD8u&j;(vUwh%lkX*yHk(wGTQG(VJMaSjINrnG*>b{r-j&L{ z68bwWk3`0U71_8&g3Bl|hSzFu!=m5Pj2*Fsoi3q5Py39-JFFP4X%lKuQpm4pxP#tr zb?I~W(WK7CfU%)Kr+90@)lu`=jiPOE3ylmoa(vHRM zE_}|tI6nMc0e0wFQvJC))HZAe$(Ry==}GmVYjy)Rog0Qhzl`Yqb&a@F!G$(&)gw<{ z7Gn@>hL!r)alw0U*d}~e9S1Gpq%*Sk3>M35G<`7mS}Q-hekj}BIfIcrE#_Es4FYcM zgKH+S;4}Ls7`~EYLM9aM$r98lsw5{~`!kzyGBjs^z@z!ThD{%E44+sBl7jIb?CI%g zj3%^ztoAL=Z&njI^Cg~7t)I^RV717q^AR7KdzO!S*+-h!NVDI=lTbPOqsVH2H>?K* zoRbsZ*k4iLiGJt!}~m3>k8_V!FiRx*M*~n#5vu zUxNituEM`=H8y(XeL`gpp?S`5)akJn&Wbj?s!t$G`IZUi%eRx{Tf>N-TM{*zzMH?d zT$8s~J;)mxf5+5~DX`NsmaVNDFZdNAFkpQ#O^*%aFouDrsFG4l}P&iTueDl3SYq>+FI_*w~?9by5YC7OaCe z{n_H}-v_cnJ7HeF_Y++H_Y}K$O?pIsBAeG5%D=9CBdXR<5f4dv!as5=rS>WV1m2p! zh~_Sk#LS~OtjB>xto(+DH16^~4)R2C-6|Y>emz(Hy%~pQ4`;QmYOwH78lTYo1~uiL z@g9EHNYd{Otl^e2Zr!pNb3loA4r(LpXFmV7&6qu0y+$0?nTw->`*CFa4jeThhj0!5 zNxDsc<4vznI*6LHmCt8VEf-zM`tnG&@W3Jd{L6{hW7G-v!d4Npp#R{K_!As`Q-$xm z7tv?VR>XL6AxxhhCUWXrLe16(Aa=Y0vw9;aymJDR`g&o3?oZ%+b*RnhL)c_LnvU_k zPVY`bT;!S$V?$MG^E6w2^{rfZ?S37;85v;w!dGNn(l6YwUyiO&^P~fZJ|IPBm-FRv z*`nm%`>;q#hPLMxVvfHl-EAxfhxKoP#iJsuvJ?18^^5tbfo)iICzDjjOraD1JB9l) zN8zh@8_?T1ht^$Hzzl&G&~(q576u)lT;B_@Iy@a(%TDqMrvqWHSrh5>3u0O41h3kd znJn+hSH7C5vmFU#l8P!LaNgQ1X__#a@k-Yv9!{Uh!TjrJ*wxD~4E~F*Wt;hshr984 zr7E`e$WZ;k`z3R{hoI`g9@HE%2+uu{#;;2*f~nF-y5s0?$eW}^KW~#q*Gq3ePRPc% z$35nb3Ow1zItgfXY&R?mk%jrD`(2A4-iA?2X3`tQWng6Y8Xm5EUh`#~zU1sCC3f~i zB=7B;!-kj_puvhdUiY>JdtxGFKx=2y0rC@J+csV5DkaOh)-~bWW*AaOSTX;mQ2CcI2C%~`+jc10C~>+vlm%>--iWM7SwFFb-*pk zmmzPtI^12i2s{kO;e=!9oW|%5*!t0fJRB_C3C6!=rk8=KD_PS>jUD_7{yX%h)kxM3 zP-nRUH}KNNdk7wmlJ|8t`1oZb*t=X;TxTmoceV7x%=?6y%58w;YX|5kPJ;z*?c-M5 z@L->0<6!5o(`2f1AraS6y5;se!jIZcWdA*d^rZDzUy%*xt|zhyVhw8Q9g6KQ3aQ5Y z6Wo$ZFUd|JXF5Bw8uuQs5}iqvp`F5ca^Qu<%+1b)ZMm`rVhirnI7FGy+r>3Fenb?! z^jygw^K*c{u(5EaUPSvxT!XdNDw2d@??7eEALx1UiR-xS%;}aL!D8)${P0y8l2Iwg z#q+Gc;)dEI5S%lFNzX0@2i1qfNclfvYmr*B-)01h7Tm(7C1+5_y9-lxwei)JLIWt(JenOzm)%^XNF;RAs~7TvDMsj~9ysIyJw&Z7XhT zzam;4d7ljO+Dg}IT2T7t4Sssx#=p3?kuiG*-ZPcq;$IE$VS6X)4qr}}dg&3F93w^^ zsItShCy70$$Rc!P`4gYk;!*V(I7#jdye^5wA`QX5*jxvz4*5{^RYUQu>mt^+<1G1a zf;7|h_`oMR?&VumJ*bgzU!KE#Md$wIv}<`2-t9dJEhpEDKK-1?>K;47iXeY{klc@b za|LhCAtma5d_2ksJof~>X?WXyD^xg0Xv)_CY(h{H#p8i=MwTwl-lk41gGKz~B7Gdb zaS=PkC9}4EXKF5Xgb=6Ebi&k(X+wo9RtdSq)z)5kXy_JN9C#aA4u6MrMq}}*`vp`z+0Gxj&l9ihUvaII zJ=Is}0QKoXtmMo%$?p46;5WCQG!F#H<|m{1Lpj<|^SYg+Sj2()C_O6I;z--J1~A$0 z)A?U-8lYulo#bZpK8$PMgm0cbBL3;yxG$N7Y`C){RU6#Soez|z>`4M_yYZe_>xRJg zfF6N`*bbFhJW-1vLce-2o3-;9?B4ze8+xo+ZrdS*Xj^cKDPf**7qQ}^J$*K*fxB;+ zi0jIZ5Z?lCrd&Ri`*kx0C3(|v#{vNOAi<8g`9OX-a?(S`Q-!vvFwcG{HL(B4wv`Tt zduM0RL0^x-+e7zo_7fX6Tb2rMK z?IKyRm)R-<6WqTmgZDgQiBHV8VCC*JuywH(T$7W5cYy{}FWZiZ1qbZ2(Ru>zpqtlf z`v&X0_p->}*YMX(Ma;{)M27^g=G1bZKZvB;z=Ii7KPd{Y=vYfsZ*tWBP&_GdTZ7T9 z9oS{}4%>@ghz^f40ZuPa+?HmA>5osr$i8$Od#n%k+Io`F4>!^Y2fD?g^VOJkc`Bq- zWYXi+ec0r0E%YPInY6nF*)b;EbyC4<`X{stS9Mg;=xBACr{TpmH<^>(zs(q4q{o!b z$0I|5(gx>&~OAGW_Uko5Se%$%z^^9;1neze0-lcWz#QA4VCe zAwRBGY}Pwe_`a^k&SMEILC=dVyYUy6?B0vXR!iaQh=I&Kas(RLTqBcb9_K51O3B%? z0?WHB5!^pt*8+HJqQF8PyvK}dE^ODZ8XyssINt6bl+++uvoN15N!|sOJEpQ)`BPYP zqih0l8q(r*X0(EN?{qOCn?@Tt&Ha=GI&jM;Yvw(Z*oi9u;-{pl&X<}YA3ZPT&s zY9lTUQ-^u+_sD-e5vaEM5#D)Q1?MJS7kt6#@cipt2o-0+f7fd8#Ha!$j@pMl12g%} zgY5a-rsFW}nF@WS`H5q5&hl$wqF~)Q!g$LC+y`+fzFN5*653Os*TRa$Zftx)ZpMWtKC!cZ#Jt>Ee}Qi!4PJ8(L-UUhqUZuers|+UM?bH^-fJ~AlIW%A zZgdxI)s-Y<$QHUS*qW_8^a)p`Td_Y|-LQ3FKOe6&4!yIAVAHt6@ZnD<>ACWP#2vJz znZj>(NmC6T`L>A^TpP|NTpU0}lZ1>*gdd!mJQ-9f^XY0k$_>tb!Ns05U}p=ukvDlm z+yWzT$lm)f<{8I=OTAgLOc?(($C5-w-h;E@LRWvl3GQ&@X>fa4gHP4NB%wZu)N*+g zh97p2^kyxf&f_?UZ95G6j>i(mDW3ezo5T6W;7zEq%S4hZ{|9SN*Fl@XMz-_!DOmE~ z2@b~jk;0yliOzm>muG^&IL?N!@(QTB69>t{ooZmHoTz{0IaHc|6UJ_R3oE?V@T>R! zhfUs&p!Fh%zWE)12bb$G-$DLp_t%f}M>#Sbof06kmXRaE8Af4zvnW)r2{daH_;WJL z(PDorRU8nH7Se)ieAh+MQPq*GZeAR0jxb^Ud;VY+&BpdsccEm56R~T!OjH-v3GaeI z{QgNfbRu^QD z2nVjXpS+t%ol!tb z-6X#1dm0)2UcI%UmkrP2myx;?26Sz|luWnoWlGlCU0&m`prduDC<)`%v+ z(d`fM)zuylXJ!fAq!!dE{s5E0W#D&HI!)USOkwtNY_Umzj9pfe3F(KB%N|Bwn<&$^ z-;H3Ulu4xT_d@Xd5LmLlo|Lx>|EK%?dr!LgH4PPfC}o(W;M+xOpGW;>>~r^7z1_td6}Cb0t5O*FbqxCymx4#jcaXdH6scu$9k01I5T&#{ zVVOo6f4%AoajlS$R^#DRbAb$~b^pM7ue7*D!xyui*MmVrs};A&8L&MafxJ{j2u(H` z#kDuA0ZTnYw%|bpNxZtBk5Hb-PYU@6$D@V6)0)6c$}9!8feRVL>q2!Fljx}UVC{!f zcx$jadKx}Pha^v0_FNab)BGgYp5BJH#=Nuix!JI||Dz<-o$$)4XCy7Jl*lQ$S7N=; zTo`=*5xkZ}pmc#0cJD6XE#wlR_L{6DcJ>1(vFQVG;!cretljsKSs$4W(ha=69uPXqj4#D8+r@Mda`h3gB>T) zcO&^P(n+x3O253tlzzN0je6AYgTKALbY93XxM@;`LD|dk^5Q1`+~xs*X%nU;a;HB92r5XoeW0Ni}-}9LA2@d zVVF@Se7{z?pr)=8YhCgc3pE0;BV-cN{O{*5PO?9MbWGd#?FY(I%+mUG$QZ^_ilsEOPQ)et=4H{pT73}$?`my;Iq3Vods z-0H30Nr#^y({j0qGZWKrcW*1CR5h^S(^c3inIF~F#n<5a_X&KdNhjGAKZ{*+yv!e5 z7zfL5N7Dz_Okh=4DyJ4}3IB(QbYvew=9u03$Kl`2f?j$~#Nb5U>5EEp1(3U4YcX^xvB32}YLy$>(N zJ$k}D;f@6h8~lrFOzuV5cVp1pXRnn7zV& z65%LK^YUb=yqzUf9!%wmzQwVUC&u*Q*%745*i#e}L7-ggC*O8K4vq#ekukRo=B1@! zq*({pq#Og+tT(u##sD&x%YllQDPDN|lzdzu3zOFxQX`i<_;W`A{XOleZ@oJI>EZ{d z^7;$zM!GcpWFog}**H3az7fgTXJN~012)HP8@NqVV~-BHV#1s=pgsPzz{Z-!q_)=J zgaPs7rLQ#gf7Spcg$tQ==q7loKZwowI)`PCH>7QYLP4ir_Bjb2<8iX|@n9QVq$4KnF9Kn-#$NanoQAo>8}Q_%M0&M+IP20L1@p`sU21yY z;JkhJ@oK*h3(6s=eI^UbCT8>gZpth_a3|J=P6S=WBd9lf8a?uO2rpf%Pd_iX3meXa z;g~gp*yFRy!NhhCU+Wl!{UHnBT|^;hm3cvg(4A9`9Lqw?JMg%?imOh;HMFaGO;!&N zgLUt;nfrQKfokrDMQ_Gp$plmWjKl^LUDL6Yqp4GeWqTan}qb7>#HiL|TU&C+?F^v!x#8c+T{L>w}nk za9L5FJD<6djxL$U+NLJ+(uJ4d!l`^R=%6Vrt8ry zWrbOHBaU(2LTo(dVOsP}ydrQ>Ik($n*|+0xBL6gRmwFLOKAp#$k&cqI#?@#o&Lpo> zO{j-UAV!Wn$Sa%t=E~N`lWi{%N|&0j=dd%;;;8$wUf-b8s z813{I*zi(l`R>J(I&S0KAznmv+)U7YOa-RJ3|#YEaB82J$S%zKg}U1YU}NT5SnM-T z@_x-8VfHtOc?HUYGrt1a%RLx*`mgwHObyg5n=2j`ltJ%IlBQBP8>}*xvt5an*l=wC z^*cRLGW%E|wm-gvL$`BuabYKK7MhNUa&zdr&P1qtY**9gvkA*?y@Dqz#WX?QfGT}g zlKk1w4t6E+=yy!u6nkI5@P@f?d7%+?Yd=qJSDq6^>c^mGXgdtpJs2G7ZiD}vXwWTN zOU_?mBz0(kz+RZc%FL3XKmG^KTsML4$sZ(Sx0K0GPcL|WClxG(E?CLOV6cq|B8zvn zLfD3#{Do0Iv^gUPQ+~Y0(wgBcK=}f$sv&f~*SPW%|yw7wLTL{^`0GriH1deTi+5yU zl|H9ksL$#itwXufN$~x+1FY{K2^+_`L7MtB?C{OQ%2$^B%c>euV5yE1j0Fzqhf;hO zkS$0lHR%bdOd8@Mmee2d=Z#D%p?E_kzgOxuk@!Bs)aubFGiDxf9YPxt2)ZL`i*JeImKPSzOjCh38U&>OuhGclR zWilTe7={L=(e$X_D0bdpGOhAgqRA_PI+2HPz4I*|7=4+f)y8t+2Sl_+@fJAcETb+P zR@2k*V{mBdZP?v62<((U;ujZL_J~yxhJt5sdm{F^ZS*TXYQdsdi=~&0cVQ3-(~%R4a5#Q(`|_D!7^vRxIL)KYLNt z2xS!`=+c9oU|+mJ;xz3R>h?rYG~S8#t=}-up4ViI;2QKVU(S1!4aASvnP|tu6|73F z1%~=9qDQJs#1mVR_(56!@pm?)pljzKHsS3-IN*^3uG7@0f2|BnYRQ0E=`vhyb|v38 z=@@E$?II29GROjDCAiXb18SZp!3@&H)h-@H_1H0 z?q`G8q@kyIw;)-XpXw|*8$Fsod~^(!dtT;-80L~MS)+g}yUlGZ_kh>o`$aMT3}~@L znFddP4aOJq3TT5B4A z{<>()2u18L*$wxfXwk9rQ$W^xMRmpCM5uW5i?80rQG78T9Y&3$LH;TfT;`K~&l2#B zjVe6cw+A$wQoyT5%Ja<5kWi@rMLZP->*mLMcs(jvyYHC z{T*g5$pzbgiR_EjBzpbwL>QsE0@n)NAS?U zrd4z?a0*=^bYbf)uHqauxKpKEV7( ze^8_SCKvq8iQS0`<<8B20&`qlsrvaH@Uk??HOoGlVEHz3%DoyN9c^XWS%2`fw-H_3 z*$Z=&hk)v7UvA~*L|nExhMzOJktpvzFJ5)CP4ZoLJuj-E#2Fsqj;M=J@n8l&qiZca zv_6)1R?dLZUQg)0oq*xSmr+mo7wPd&CKKgDi9=ZroRdxvI@llKXx%1mZLtCyJ}QoS z1%E`z)>!;@%8mxi9>MLCn*$1eg*n(KHD=Uh2L-Ql`T2vb=$t)w!RdkEYDtx6`}8e{ z{u&8{-+w?l-499D+*u8#ZX4Lc9A`$}-$skM<>Y$!e96hUE=jjvAs?@N5@xPD22;a# zz^W&SoO1dI;S9Z!bN&+r2kMMyaq=2id#eybY9u(aXRqs*GESnidnKmL8P0wP`Fyi6 zbD$2~P${Yx;O}6%X3!~-U9cfLJzk%k_;8V?mMc^7hJ)DNvlrsJ|6ruV4Q8|{vU3}r z^LMM4(a!EzYRo9Nzefv!g6zcF<>x1E8e+hUTm!k?MX3R6poi^+G@Yh$4Wk1$0 zV8p}$3lwJ3H@gbSMZwqF>|{rSb{)d|8h`MK*=HeFw~?Ol>xaoB)!9^m>7se!z1Zi* zDO^-L8WRs9F6iBapM7Om?ooFZUEK(Yy%OR%PZnCq7;swHjM6vaC94i(kuk^n;mUFa z7I00O#i#E9?O6$Ay-GHWzc7Z>^jwFvFK5xK0vC3d(oniuryj2yD*~lw4Mes>U`&`M z!w>mtIKwI9ztj-cCR~25uC9fQL(}omq`45{a{-2(`OeQ1nbA(^ESmKsz!xUG40 z+|&`m+3{r-yp}enFFzf`jNU8Iz4Ze6jTb|J(5*cDM4cOIvPs~J$k3zmvTTn2QrdEB zI8~i>noLs>1jI+X@#~tYWLZ`!XBo2;aHSDFxi6VsG8@a)sE*xN3O=PXP6R@`TC)gPM=C}DifNz6yxWL4Jyy*^C zet5Sb2IaOe`7I%^x*!UAUU@N>aWT;5@52R^sIX(dfgBa~!2XuQML{El{M>+UexC_q zZFe!b-gq0WG&AUg;&v>*r^#j)cEO`*L(u1n1~%r;*u}XuFY^|8`x*`R5Bs{Gfv`(3xeZCG!Kl-u43j@Uv97W797^9&H8fo!PSKeBqjGa z@GP~&{FRxcN!pi2HIAV27DLFhrMm@hn=N-EIY&$a>wkt?@>4Epb?K@>Ro)Rn|=UpaLJ%#d*L)f>`!ukKb4E;h=P^)t! zeZwtfwaGCco4FJ**@1nTZ%ZeZsLs zZ>AH~o%0Sp-?L(k#>X(|;yvCWbrYQ?@U@(*1~Fy91ry{rAZk z-X6S*mCuGrZZ+G(mM6ctb5*LaYvw0hp}zv}CS>4%yehEJ+5p!Ud!p^bG2k=GK%(iG zPd+-dN>=xO!?0I|;@i6UxZ^(ys`U9U%zCv;e53ad|Mc8KF30m1-l?9zYgVh%Po9ix zU%Un8bsoai8)Go0O@R;l`yXHWyp|j|mQYyR$*bG9gX^!h#ot-F|j}ie|VgPH9h^<&^tqT zZem&9K!FF4QOlK|SE5)g$1icN z%>Im%_Gz$|5&G~w>$sQ=DCc7a8?b%BT5Qn?MY{c*0Ymc-Fm22OEZTVtzE%oOw6%qt zM%N!On`l5c*u=01t7KaALyIlD+{^1mKf}PP7Tntz&adjK<1YTY4--~yfV{Mw^xWaq zY~Q>+a9`jD4<6gVa~B_==g=+0V68u_lyZaHMt5+;!qwQdcnJIvZGe&2#b}$A$SnUE zVPotd3_dau-J{NOt@#%~N8=G5*y@9;UEXp5UAo-ZgW*`&EBKhCg1M2W7P7B72B^@d zNsCq}Q_*fQSO4?8$lUTRS-RAbU0wN3lH_xR4g1&xy7EmR>By2q>Lp-#L6D>%>pP!r ze-XyKFb1d50ivk6LqX^KF}P{U$nC4uI92XF$aLDX(LTCNB)=Pc?Y;Sk*`EBF5niyn z`Xz~Xh#?AnwtSu0au7eP>GI+N)?5D z3WvD+n!=7nhr2vVk*3KTLdDl#5a6Omm!EKC`c4xNHJZ89)lR_k4jc|9=W3wDG`L~;5#A{Q{T`w3N$Fy@-K&5pFby_``MsD6BmL0K{{TSpWs?}Fv z=U3QM2irjKQ(!Q_`#EX|8OiLsLnQf~)wsV?p3#;aSdjAr+abkarY zo9g*#zbx7J;u@-Tuoo(0I!N-|I;>2pMk8x=>eMXDJhyj|TPto+?27miKj*`FfN4bh+r zo(Z###}Qz4R-Hb~*My|=gx)-M1M?gmsPt18^saYdx<{k&QsipMu+uZC>Y`=PQhf%m z`9*;5iiOPopbL}NJ^`DTtfcCW0hDjg!xR3t5(ATSyp@9jHmlu&Ifr7HPqYXRuB%0R zzjfqlxCikc9FFEoroxv3syW@eyJoYOp!X&>W`bPc) z#(IW;&&c8A*Vsgu-&u=nhbnXUCQWZ{T>}^UQi)x?B3vl>D0Hz7;NHVeflj+eu3LH2 zXLE0YS;=p*JVeN?1!qI2)+0Fm>I)1H4q~PjJ88ObHe2LAl3A`3=GU|oFArGBCtvj7 zG=2$=kimz@N544gb}pA0&zMZFoE4g82gu8hs>Hk*tEVLrrc0wrS_jWpFk5Bb?^g@j697W z9-iWk%4}dqpLL?5l^!ko5DWFP+aP7yP?9!dB+fh=$Nx5c0uypC2zy4LMyynFXGI16 zJ8_y!tlK4tS$qQ=+nrc;!U)VQ`{W{~jW9?4EZ^_{9uL_lfvddW8Pe3J_W7f6$Gg3f z{zwPOv(}Sj;$S(tXX68mC`}`FU+zmt)?y5wDPgAOadgM!(e&}INuWB+06D>-VfI9s zrq3#dp9@RisP!00$Qo&S_=p;t7;s+%0inFbwKcRr@RrW}u?>93I&KlY>nbLy6D*>_$}T>Joq{%QvM*k8N790KZ7bkt@O8ABv6IHe zYVyx=%lWU7a?JWby{Oc53;allL=D@E^m_F#h<-mEd$lVh_g$so$>=oR?)Ei{@*(sd={A@Pjd_GQzs}%ore*!;G&Oxo}Kt4qyhUNr?&~@5# z>5MgZaqdt#`iE6QK<#gSPHitlxTiyEE%I}HUIS^p1%{3_4DLqFp7=+I&(l zHAP2*=MvD z|K&V{Y1<2!huU!b8#xrztsfBU$jhi#kh8cy+FJO|tkQXjrMt8NDF{A9I7}7rtCq)0n z=+F85_nKM!#ee#Am#PhC-(w@>YL)0!>bwDWIqMUqRpyd+dDe7(qc`(dx1J7m*^KUsV(^T^Ny&(B zJ(4Rs7Xo)an()Wb5ET4??JtX=4eF{k{!1eLBv)Sd~UL z8&Lf1-N0S{UBD*TK8C*&Z}5$?2czXh9rotMb8(CFLTdAez;e4Tn4*Fr_1)Gudi6Ij zdO4Zw+We90_mQIulqRya;$D3DdMbJB(v6?22Gj4e!l81`FlKrN z&}A>@QRCOCu;qptKJn87sjfr_k~s}{_&NV}ojkP&y)Je;*o@f=)tTmU8#o-~5954Y z;nuqxxE5(oXEfWfo;6qa&gIi3C*e9bf9NRoA*>I69$QRh7Mj4Ym}HDkv16OM5?OW8 z*G&5rc`B1~K=! z{rsSV$3Wjr0%Py)Vl$gF=~FLxVEbcK#C=}74CBC+7LRN&|{reA|*Xwzn`#$HoKA-n{PBQ;^=m{3Z zAvTxQvfu5Ng8KNM^2&S3LV=niZOfhnhCjk-CRs`~i_epfgWRdZu?4v1?+SQwphK9h zdKrV3HKX^#?MP1E#F175vFT(V`l7%Xy+++-z6GsJlpI_tg*Wk0NE{nIRl4In_#|X~ z+v{BOWhLq9u?aOUo5@PNcJt%sTX5M)J^FlP9&Rb=#`%e1B>zAVS<eRh&at4KgA*{h#th7e0{!a{0%r%NaJgY9i7MzVznWskV;1khNoot}i(iv))SYE? zz^pp9zHKVpm@$NFu6hAJUoWD|L=EwGwl1!<*5zmOu0z)uYoNou1bR+Qg`~R!C67m6 z-u^lVxA&YNGkfdLJ^J*QPl+kSzbjV2m`$EA{Kq`ZSTcieT%FHaiUzt&-nf``JKh%t ze748*okh5F=RCORA0=(xLb^!N{Q>anwAyDrXL9Z|C%CBllbFMS`Vp!!n za0x9YKkR>Eiq;`eQK*BC_(0fDAiYlx^$?DBt_GK7?=bvMAyrR*4sl&Kv471&_}6P2tL#~RKC9GMXBG$Q3(j@-(VIl5R*1+bjzr-6qGs%@xG5EDmI&bRUX18?pBtBve zEJ~at%&-3j3XWBH$!!_)+|ifj8WY&Ov<&~zo|IxgI`B{!%=7eQpT38pho%eP88VL? z>h=?M9=SwrhJ=bwR-VPK?2@p8c)}?|a~_~-h68FVA^%VSULE%fO>gP*kd5PrW7KWX z-I>ov%uAC{3&Rm$5QMw`k!^k`a*P2)$DDh^NO%B3;no0Jc~t)Im5cFZ8k zqaDcZUuR*|$aq{wHQ?gq5Uw;kjSZcu!T&@n@{tnzpz7rh@ycy4n(0)7JFYu|LI3np zx7Y|=(qB%8N~}yXHwU5*ljxB6c^GE>i$!X+16z9*-wP$+`6vw{)VA=zhTdf}=yz$SKW9rW z9tkt%VH!nbx_0A;d!rW9h}fQ}*L9=opO_ z3_t9J%X26m`?y~ipJPCE&)x;2A@S_ngithHSOVE*&a~gu?&#q=6gx*-k*w4OJgE0N zeoEh*Z)Y>9p0_pmy0M8BERPm7#!qL#Uqiq<+5;m#WfFsv7nxe#DZE`92~*`#Moe-B zytL6FJ|@9@b)l{B>t!!~==xeb_iz|=Q%r`)^t(jwv%C0shaMl>Hv`5!JB9zm-}o#z zoUF-}T+BoLAyZ;nbt`C->MGInuDt@Ea}b%or42VTZxw#8>I-e-N=ifAjA_eQ6SC~s zMxj_##COupF=mN0)A}1r3zix4-RrvJc9}1pjlGV6F%$W=<^lX!PBq)neE@a!u%->$ zPP5roiu7bc7*;-;4*aY;)F^$F>7+NqmJOlk`K$}Q_}iEs)}07ed0k+8>ri@MH-H4c zU&QYknQ-%>G}thH7C*6n2d?)!1~C#^=UqTWX~*w#z-ptg-=YiT2Mi~Eds`t>m<_{U zuf&vdX8eBR8qDi^U05!E1GT$$@KM`y=*PdQ=snMu+o_ts;mIL*J9Q(@k#&@Y-jQ4t zed_VdnSXG3=t=P38%7`8zXq?aPX`qp$p_GTC1O%2?7eKr+JcVY0PDl&R zwOPchFd0A1%V8y9Cs6+_V^`~~$eQ*q*x25a4;i-~3X|o6o5Vu;wk{ew`c1~)*Qbh2 zp8NS1A9ZZMWljCHm3eT{M=bFFEA(k{!Z%A!vA@@(PQjjOyu$i8EO4F2ofa*?w@WwT zt06PR;E{&3i%tU9%2mfl4a4QP4KKm>vj?fsZasQ_-((VbeiQh%%|msIe)O<@25f%j z3H4L^(15LVY}$7Nu2mrydWD&2RZK4`bYvVTj1x~ zLfB`xjjx$fA{%*X5MR<$gImtrN2Z+5!1OK$as1a75?!H2SGSq-?`4y)IrOD`tu#Ma z)-?ccz4(Af(@wE1&*zfbn0t7qC4oPcr$~Ff9zvX6ndBUJ053lNA}gJYaNN^JP}^e} z(~38v$(P>Z?e2U-ttite!wk_C}+s z^E9c`mdKj#Mu~%qC*%Cz*I-)4C$=g5u!JFT*rFZautZIxh7`-3B{l-4W znfV&LSKr&9I`lO>vp&gQFI6GYy+w#P*O&kP*M#R|ld--ck{C(7kFyph@LO&hZZonI zc917vsO?BMyLP~kD~BNx%rk9(KWWEV zWtasjjdX#pi``kI{tFCKYn2p(19;^ARw3o z|Avz8Pu1{I%QON6O&fB;*Xq6xHs}XwD!M&7yM0l!M0*D z_jVWf-8x5D?%0<;PWvLPDL4#Q|CGR(ej8-FyV~wrhS8~4C5G7Qp5Xi? zk{-U605va%^5yTXdDFP>xXkDh^c@gRb&T5C2w^^HNIivtFTz3JU?z6&Z7=h0Z-Vo0 zV%U_TbvV4ijGk>yhda|ud2iiYFx96OcKY4Hr!&@ZwUh+7{>Y1a+keO3j$LTxFlUe} z^@EEyyOSZ+M=?7pKsNCSfz-E!EMwMKG<hPP%ZF2HJaLey3e-i zxwD;Zu3*wbi+hhejQ(Xd@aAe5eD9bEGv3~W;)}gtD?P^wPY>p;v%Ogl=PFjPO=1E~ z*?{v8roi;NHX-TBBPb~PjO+DWP{-6soE>|JU)ujxVwc;}yPeO-!SuzD+do%;*`Hxl zeiSP+>cRWv+VM*&saSn;1pgi}1EY-X1-EA(#TNNaS~xZy*RKD-&du5=zyEkK*I&oz zu&>);Y9D2;Gd-Bf4x942|1#vyY)+BX7IVIIgB)VI>cH$%_WbXpHDo|arVziziGPf? zrT_0~Z<+2!?@XFb@-J%QHbV!NX*!aGZYY3d(z9`1!+c@x;uhRAP?5hItjUu<^o2!_ z8OHv)Ez> zF~Z3!b?C4zo_U=&flsjy(PY?A;Yeoyvx@A1H>-XMkHaRzHtl6pb%300QzB4*&Qi*& zJ|KOf+GOL|B--`A2bkJ(AbHS#Iv%&XiRojEVC&_lV3rdsq!b^AjDJIDru;nJm{nd)Kd4aJDgYl)#HFk2uO-xa%2BY&4kiEMNmrLxNCGI`>AdgyH zQaPDD9c?sat>Ke?uy zOjVjg_n$q?sOt_`d2p7Pv2Fpzx%2|Xz4tMt?YM9@>zTN^VK`6Oc8;Z&Mq=hDiG5dP zjF%Em@wq$JU`xgfE{?q+9QijK$|ToYz#w}*KbE7_f@-YvNyNke7hJHrJAZkjQ+(QH zgKyL=n8wF5n8YX0mIifdJ;;F@**zeh^9{iw)rl?kc!I0t9rAArW>LIq%eO9>i*Wyj z=+fCvRv#Y)tA*uM7O6*Vwzo5%J9U_8vPYOVNtuU5-NIX%#U#JvEhHTEhTapj zd6kX;Pk;K*adGiv>YRzPEk}!ClJ05L8XrN%q#Dz~YfpoQLpXlX`wTlvnlbv&Cz2kp z0?k(2(}=NNd}CQCt@u_CueWZ7iuT9i6SD`zeR~6WSR!Z9gEYBemIkhk`48Xjj)WG+ z?tGc$Eo|EEgC%dhFmUA^%v`yj9!XZ`cYDXPLGtgQTp5c-&Cl_n#tV5^@=NxyG?|S* z=>;#UCc%8){cP3xV`%*A0|~hMfan$~p!u3$VtVH>7Aa=qu3@@#xXE8g?$=EIg={6m zmrDKM^c;4!ZzBFKvuBG=Er3ddrO?Eqv1%GV`Or1g}gaq z74P3=gv+0;B6d-hdbJb&GFg}h8*4s{w|&t^ucl%2M^9z$*h`?ZkIok!Y^{U(lneOQ zKMU6Q%)&K?KC^(==EQo(alUxgIy!9R5@F8nd~Ep71DRJm?$Yhc&VRfHU(+pF-=C?_ z9(-PS9pen^l~>cq@@3Gq_ZWVn)rP-)(@)&Kq@`qoO*eX6YYwg$XOETB$Fk<|^XPY1 zoA=+kRJ52LB=pPoCsRrj=$GDkG~!$ic;4H|Jf)2F4W%%2>}$e}O^1`sdGWAXNyLdt zB{*Q=NHO}B6*M1OgXbM;&~fw@;?aExls*!;@W>v%=i9JxHrH6^jo$p$c!}5Y+ZVo{ zH05_5Hv*&>V3}hV?h-VfTFUB4mrI@`CQQl#hxVZvAAW)(TJsSu%aAB7hmv32C<&fP z`#L{kPo3xR&GRPmiroghWq4oyGGr?Mdw3>|QBacHyjs+>r=nQeKaWL+-36!rl7+1J zc45R>W8u2fVhkuufx}OR@?XE}gqrLz?CpkgTSqc+qY>2E&asbN3Z2G5D88YXq0Hd*7rlr{)DA@M}UmdZ^Fav|r=MhJLb zAv7%OPTgnfz=^OcnET9?eU_XUGe6#iN$Y)a{r9!vL&tS|hp`qId|U%cAG*WshT)?5 z`atNEGC=;ry5k@5m$+xqKJYmZixm}aWd3WZ-?YjKEq{ArrvG|;oA80rwHcIpR-olS z4c@208%D`Svz$BMaJ5IUxOs3V>e;NsK>9zIEOSzUQe5-CK$V1 z3zn3)@VBZ(nD?>)YPOsp3c>Ddg612+^PT}eba151NFf?$Gc|VC$rH-P^`^O!NBw%& zM)C2oQlTd84rsKy!>)`HwrR{rxxwS({AKzlF!<7o>XjM5{fU*ZFuO)DKXwK(3NAqW z<5JwTC=-p3AHzq3W4Zb7?od1TIfPnXl;;dBgvEyTaILD8n7Qe@MD>zDtr`xr&+WbJ zh;#uf-7eShzgvuMo&NZtd72C_ye?@TmdVTkHepy>UgW-FY!!o z#;>P;g8!^q=<}@sceRhCr*@mtq(C>C^gflmSP=!W$0VNM4PzV)R(wgh5lp(efHJjQ zko%S}>$}-w)ywfIZ^n_~PUPoD$y;%s)hu%CWbm{*F8)rzls|ZEzcl|yIjhQDb zwq5|4J8LoalP6bOHjQOmtp?jAv$^;+oeW}$GGpJ9&=J~`H+n1=Ts-!}q%($K*Hp|7 z>p9ZtV}C=X0+4n;4Ge$Q!CcQOgZIRp*dBk77s`V%*sfg6n5{%ZhmGJV#S2OCI|u4? zSc9MI9Uv}@Oa}fi8~(g%z?_2PFwi~?CVeZy0gpR`f?72m7j+YkZrF=EUp^u6HGw=? z-Ilr~OO8N`#gL=BfNcp+!O<6&V|4N~tPzZ)uH;Ef?cvA|`G&wt+cflFu^Z=j*W%*I z)wp7O49qP!NRO2(@lE~rv6HM9rtx6x<7vQun_iZmw)H^`qAPKUHp{zeN{lmX7I)_< z@sg3QY(bbZb}OBUXTJ9X+w55UvM(O@OUxgqmQ%=`K8rsOobjM;XITNz%$le6q;qQ-Q# z_so7aWQQU-X&8px>h8nCKa+5B{UG$R3m(M{Xok8>IFc=j~d*yq>0H590kj+kJ`)dWpkq+VB&*X40sw1>9L9A$%ja>*VU(@>z#a-^f4XI*Bpaw=_7d}`U|12YA`OZ z5noPL7H3tf^42k1Al5M)&F31>U-UkXh@42Px`qkH#eS5mbKqM%z1fI0B^VGslPe_{ z;I)rYQmz=mZ|6|q?Y7HIIMxKhu22NeA*A-gW~pbP$Tnu3Vy9eNg*RnM*p$7AHXBQs z9_emzbQ9q(gg?mp8@crGIg53toUFr zPHsI+lXfqF6OWGL(nAC2=Y&USFZFg?m*tVw;Unqn&YNhsOPzY&SSrrGn*`SP9%5s2 z67RM&ginaw!)}Gb=9QD^cyo!Nqwx{Fy(27$)Zt_P5w6!(jf%J&Prd-H7uSs6$0-o8VPE5I;-;Hap=6&e%Sl zZe3A_@mE_}WQ?s)@@W8BN(?dXhaIk}OTlH2eR&$Y!b~2%VrTs3;l2TJeAwf?@`XF@ zu(kh%^EReP*F9Cni?`~Ca=>=hBd0eVxOF7jJ~<&g?lwVqQxLd0YNfc4twB;5U3Be7@bmcm2Q3Y_ zipp%Uhsie5!&;xe9MK)Wj>%?m^P(_kMF_lZOT(-vLv#wugP*?65M}=uHRc?_Ir^~> zr`eC49x?~x&#UsU=AYTY6DtK}g&{~CM-Yn@@o;WzT5-RTPJH>C^H{kxRaidsw=m@N zQ*>3EPgHsa%c9-#c%kAQW^V69Jtgksum|4YqGT`Bze~Z1>I^z&X@X*1i)eeJFFrW1 zhTk^Z3%M)v!EuJW{MliD@$!uoFu@0f%qH8?!;P=t&yY+A9sU*JR7tOKIxTX;F}Qiu+G7}XCg$-48zCw?x5cVhMO&{pk(A{ zVXD$dwtQB1DJQ7K+q&q2No5_2wsc}HTGRRadSmiX@wd$R9)&YD+OY9=HO{|woV{HU zjmZg`tSR9iO#O3l#KvC%z5p>gY31z-z!3f~-=O^5av0oSu0;gf)75_=^Kw=8>)&pk_s z(SDJ=?UjL>I;I2p@4Ea`e+4j#4dx}$hCDatkzl5A4%2N%!`d#_z@gtiA!LyXT^~G5 z%Ae|kW4~9pdG}0kKYIxJ30=|LM2l$peFAz_;!H1fmhF9SjVq!j@MraR;CY4;4)r;T zh64uE*rQ{q-kbq+nfhoP5T;GTRygDL+X|4F(t^f)Yry8YBfVGl3@i@1FfGr&^8MQ- z-q?LO22_EvH8%GGS4-A=yOWHW4@J(+!QTgcwr8G>Jwok7?2lrVpKCHz##;tGoE z`H{D!cragwMuz{v%PUOa-=GAc!N-+sznc!;-_DT*qs-{CSFcF<5jR*m_aEH47XXJ9 zV&K9rZ)SJnJao-lLIu?eaNhSAk79keMP)H7UTjG{XBo0F(Z*1F-H_V-&cVm?cZf95 z6h`dqjXAbuWL>=uH8kHT_|^{Re}g2yp7Axo-Xa&q47-C#Q|)29<$B&xpbqz<<7Mry zW8kXkEoRe01rJ?NhMhl8qleKl{PJrAqs{MejBY20e&>nlS95x7^+4XzG>tc086;)5 zqG)lFFRZNzV@a#GLzj6|hq!W!g}&t6?`Ed@AaFymOIQnFj zSW&Eog-J=^d+{^rY0wkG8fQt|vpHN%>eLSD7a+>t4G`?2MJ!lSM?NR2(^JL%GQDYW zg2kvGaO&t-ym!Z%I+2y!$ayt;Tz?oxCqIG(ho{lxb^6?CNhjI=Xa{Vri*Y$^eL+;3 z^#ljF6=S!jec{z{ZQfWW&2$31;aP1iURbu9S!CuypHIhmKfYV&z54~*{`ow1PwY?U z3;`VQdOCdAy9pmkq2ujgi5OIMh3z??C#3oQBbgT$6U|fRFg>z`ZMoD1rf(?$STUTx z*{DX`OdN$9zV|_`qc;wnUgyGwlbyEuNyW%e<97mV04OgNj;l^^^sjB4dd4E+8_ z!6JVw&mA-g*9{DieH}aswI{BFtoxc|W|=>ZdAA#Xy|k9%^CyX2o}+Mo?;!|$`xyDr zESi7pJ4tG+KxOP^(vb?N!cW{@hp-KRpX_w1D1c3)uASv7Hm!Z+x8--#;7O@yP8 zm;JdOSDvn4z zhdd0dNQNi>+0gnJ5^$F_Be`F6M5VOvR~RbI|y#E5v)hfjheUAZ?iKK9%NPSREWgFVSE@kao)+kGAd8%noZ+pL%h-yFOei!=2Vb4R;^1RByrbM7x{fRr z_Uv}1(Ldb4KQ@MawAQ4{Nj~d3JV5L}poCoA5)Cr7WyJqaXOxmp(=JQi-l?!9?g-XO`9G_^ioETEF@CVf7QPv( z;5xfQc<^twaQpfoIw*e@ADH3AXI>aXZH`CMw`;X&>(s*}ve5_I+KVCbfDVss9M9q_ zb69>~8y-DOi7$|R@?T|6WagD#utBdEE2oV^&yq`{*ngUwdyTZT)cYqdZc0{wc|Dx-AagmFd!V#dqA@vYq%^9g=z9-No&%In#jk18KL~k9c9V z8y4kVCbuVr@_U_Ig&9ryxbbW>7IamB_aU(en})-YjdfV)eHG7>nOOH~E{kk?>-=Z_ zK)Bs*O>6rfC2DR}WbGz*G%0&Xp5R-YnoXZi_eTBpG5mBa8-pM9v4 zdJ@Hly`cW+U_@KFY}E&S;Y**#X!`O1-S=QD**IhxOHa_`3l>om z+?~vw8>?X11vjy;@&QOG+9sBphO@gS4%B|t5iuzFDH-B*7rt!jh4KGMp4=^`kiF}G znJI6WP1kTZrN0Y?N;b_0OX`_!HIgkKF2NN(3rrV2!dYrX;@hnKu;QgLrazp6U$T>6 z>70esaDpM7u42URb;~0|?sUS=bZrC9XrD?hTzLh16C32u441&Mp=D_FK^ca=_y>mlHjoy)&ph1kvY8d1 z;n>SWlKSSj&@V`bUQG4FZIcJV5VbbB-=Cwrh{%BY-7+}3P>08PR*K0x+)4G%g@QxQHjMeUo$B|Dhsr&Fi0_5j{M)eplI||TZRw6$7J`zI*_UH1C?_{yl%B6gz0$e?oPNVNw$tRVT5sk~^ z;kLtG)CxHstoF_jn+=T6O{N^T?g@Hh2j zc|tNAd(wrD{rwo98SA3X@-?(%)=Q{+mdgBOyYYf|Hz=&pq(hfP;M@4@5iMDL=&b5J z@E~mrjU8+!_MCPdR=dR$cZbbzM!FAfR~QJDP--TJ-@-P7WsqVjwgHZYAYww(yDnWw9K|uXXdyEgW&!7N=)3 zaCtidK34W1d}$c=n)w~eIxiF7{pvI(QwQ3+E#%kQhSD#?%;|v7UoquYq_}?JD*POr z1ry#U!j9+3;>=2G_ONFzG}j?{@ids)N;4aCn|JtW+h4(ahbTt3#KPn=S!naifCojd z;oC2NfT)AEVu90bXu2yo2{ji(R_i!s(7PMAvQH&B<}+!fRwvm#{3Oc$s^aXcru1v? z8f>rj;*b7Jp=t9sP-C_nE8HJ)4v+9(A4(uMZ)SLo_64N`3fidzj2@e;)j*qkc$ z#HaKG2eUAYJbhZ+LHncK`XF++MDiQw8BmM7G!n0Im2Bws1axlvVH4MWa>A@2e)Lrd zw&e6Btqs>@?I1nd0@AT2Mf%P3s}!=bOz6G3ry;j`9<0Gcx ziSbG-X;BYU*&xlIT5q~kN*V3%*`8qiP@lH;uY&1=thv+4SoU`0S0QP!p5XQ^6(5fb zp`j8J?aQ1F;e3q`FM86SxldLXwnZ60)W};y>xp}7v}xW4ch+2qd|N+32QGyTz3ZED97?{fy#W>^LSvL+2TT%Np${x zKd9S~3jy^$^pL*?m3K8HR|oi$Eg6@D-}82XL)B<}@aQ+B{TC&k%2uO0T*6?brwxw^ zJO|s`*5Z;av9L(xBuS00!L|B%B*H2j25#;{yrwI2aa}aqIdcP5lDc*c6VBs)Qw_4Y zy$G~E=+mC>J*4l&7~l0#qu$Faq}*OHsBX6wGwTy@k9Hznb2$RXPL{&5bxx3WT@{q) z4Q1hvESN|99-Q^8faQkd@WBuYWpoN0$uC8>zb+^@uO>E4x>RSuWU~85cgR(JO{yl0 z=EiQXU|)9?K51PZX6trt&TX-_!&7Ik;u_S>cPx8i`Mg%Ky^kiyT5S_(YxTm zRdg#^$Q+3)vt=hV4@$?SE3!zPQ=`!1MKCN|yc&uvzJo`FjK}*I!W_9jq&G^A&Ylr) zz3dlYYkR4@@HBhtQ-O8T|G#-gp5S->s`z1gE_)c|#rgznhJ77US1h0%R%#@|Fa2qF ztw|ZL_IOnK?3}dYvFXn(OoF)TO2V$JQit28>j0Ec zk3K_}2S;(3_XA>LR*$cxUZRr6JU&)pmF7xJo82Eo*c*J0DApIE%SJ^kEA7dJMt!F9 zJsEzhkHS682l<$}cG%;n7siSYS*(5n{(k}WPFgnUhV*vmQM6ap&>51l8HyBycHL=SYSY<1hVBvdL=$U`j5=JYA&5;x(ofe3I7qL$k$Bh%T12l!SNFZ^4JswI=oef-U^?H zho!!(?l4D~u+$&abk<;_pNr(EZ4_I^TXD@1YvAv;i9DX|;x|vf$L;ge+12Y|xVJJE z{tI4%7;qTZPC6iDukwIced!)_Tb--rDB*?Lo~-SGIe!xN5j#)cVx96@FzC@&h_9VW z``Gtk4?AZ_yD}f_og}fBjb=msnT5D&UK7Nr>dN{pVemwnAr1b}CjOqb5k>dQ*!AnM z(%jR}g}XX^#Zi;AL~Dnw)NrRQm_2SKpMTBbaSKPo;Ft^-ulQ%^9G(NiUK)#bT~zR9 z|5{dd`Zol=4xkeje06a>IF!{Fxxv2bBgH=v`Z4+gy70gmo>;ZLkHfALM1CS4m1FZQ(1`*pf&&|%uaxPC*rW|s$E^7c%}nCF|?*v3C(C zZbxHb#Iak@}pqIH*_?34C*Zu>@;>*e_3;cj{mx40Va+w2i5H`?Ks zs$;OyaRhnURXw%fBoNo1#GJ zxC3;iDM|BAAL_GLhq^DGME?x_0u9*)u%>dIi_e)b{`H;$4lVEy12&e)LL5q9Xk-At zY+!LfJ*O|v?2T)kh#10+Z!%BCbV?}HFGG6V@55H-K zJ z^*kR+_FR-^uAznG)xc?}`a_S`oyiqz_x~dcSJajEQ&xvZ>$cP8d_U5-w~#Cs7I2-d zj-b+KjdN0E7`Ut*$Bp;aLdx6<)@|W$G{0Pr+fIy;=El7Q)#iG!<#ZMvmsl=|mG@X= z4kvjx-jR6o$EdPaWEN^|tp9UAH1YFb|AuRG`J4+P%sL_-yE>R0-ljo6t=P>ht4m?0 z)>zVCI_x_t3)E(eA;d+egURq;OymAq!WB+4zj${@>wF}!Ga4aCdLGA-6xJBj2M6l9 zN%;~vwvKRtOlxyu@p}^g=b1*r{a4c6(%xUe)D^#{7QvrWzvP*=Ip7sjOw_&Npt?kX z8uWH%5T`+6RZYavj)O2s--0Tnb->Yxp>+Jb4Nx`x1@73bMG~_W;o@J$v=`mR1Ff-q ze(DtVz^9b0d*;e3=b7W>us!f4d8ouB*FytuRi1Tw5ByjXi@Cu@^nqFj&X}l;`8RJP z^y@-h2PNRSQ!(Js{);AJ#fy{pK6V&8U%v_5Hr#;G-_0Ms%-#?CBfEg|hZ0yB z6pQIqmznY+S1t?Bpf1HeG&pZHT0Z;?0}G4bk->0WJ7W+(b468Lm3Rbh%u!+QA%k}Y z#o%NoIX*F%3qOJ%L8(nVY>!!j-Y@Ln=08`e7^TMk8IQvP+ksp=UAVC_nh`c0qv z!45oab(g5mK8rh-rh@gZtFlJ28)^;@gvs#$A7X-#CO${ot{Yi&{UH(q^4?)Xp#McXnZ}wD7vm}w9$-{X;>S}oziT70VG3#i3J%zn_7xEecx-`jYYpHNQD zR1)}S+y;y84?*R{rZ_Zc126v-gN?oFh_0e1uT<<0;eS7o`C$WuvlGkMi3l0wO_lu8 z+BU)#secvFkcB>zM0js_gtSLoVaHqu%-9tN8rnzrg)!b_>$16Q*zhOhxv2p^yMCa{ zb%XV&zv(ek$Vwy!J-)#&ZzD|XDubGlSJ7kgSbFJ(HSRDOiqU`G;+w|fFzj{#+wK?x zs;MzVhu^^ymEX|Zv=!eOG-0y+4cOzSMtj8flaD?SjsHz-5>9$*v-KY>z%OJUQCgWm zXYcUBnN@?>>Kj>j>ZRm8J(NR&k~Ddz#t4{wpaQM+|H8>4CDLv$_0dxO!E?4H*Zf&1 zTK!hThe;D4;FJc@I?)Xy&--Af_jdB_%{u;ZNDRG|X-0=88o`&NHE=g_2}~TVff~w$ z|LqtpfBJC;hEBJE@}vwN@$(6(@G#<=CyThs`4=1B)sA}Z8;W{2ev;}b^XQJG1v2fz zzGR)ak0}IILB%ptUb!|NEDs!)Z_(*a`%KY>Y_;C-v_2A<#Z=rl?I!cvb5CY`yA2U7=w5I=}6zjP*z*!#y49O!!+~d{Lbk4?C+QgXnZc^ z*@o+IyLvBr1AnpEUPa`}pxb(~h}Y1f7CckS+oRf>*86egBzG{ z5}~we2PU8h%VH+T-i=KY%}x)32`7PDaOBO}6SJi(b~^Lys2+fWPFcfYQl}~%S+M|p(r!Tb(7`AQM*jpPzGHk}MpvP@!88?wPHhRH49riAGf#)e zpGxok#M=v5zyB^^)5T}ZYL+(yGbQ?m>f?@z{qW^=As_GG0bO%lVQ}dOJZ_-Rue>tj zb4GoHK9dsIT=z!2jFCJKWIam=oRjp{5E92y_fFrtpU~$2L*g1a@ zTk9Drl>ZFF@%;oS?9&Ki6gHEI!;>(*MT6(>J^=qsPr#>M-QoGpU&7s7cdDoVm1I5E zW?iLhSA}~Gi|Y8nf>~U&1_cg zV7|g!5ywe=4RxpMM15)>?$GSTUvF0?a~%^(k7UlFjzwSL`ojl8^v+%o-M>osvjND7 zZL=V?k0LF8lZ*23MiL)tM&1V-U}s&hxNV3IwOZ4U|LFf56|L2I(=s2rX?rgy|L}+% zo}>*UecNQdXYY{oz&QN0I!YXV+Xr1$pWqhf`(oobJshw+SU5OnA6Q;qMKwEKpg+a^ag;axGSd~Z%mPwz&B(i5~Ut3jsFJC%6*ZO8B} z+o{Iqnbigrk5ke@GPelktq-nkHd7Vm8X`r+Rm3CAbS`yhKk)2Uw zBnc7T_r6Yv&{7)GkhWABw3EK~`w#SkxaXYL>-l^<>WARr%zE&(^P!6~JMd3P7wHgt zi`G8cuxG{%{vvI~@c2S-nS2>6G7OmAzukCiYYXuj^%tj!8Cy$DU1GDx0p4`hqI9VV zy?=|xKpUPMj7o!#6?$~-2wi4!WTWJ9!!c63Dgh6P+4uHcYe~NqwOD2#2OuX1ogPc* zbUPoAktrfK?)Q+iuzYkh`3v$J^r^!+UPztW2o0_qIUWB5tc%j+7bM=mLxQi+Q&SF! zL#%M-lo${uj-^u_5`aH&2u^%P($B|={dqQmNuEu_VMe9G-m?;P2#UtFO9xTa&PblC zQAX?7G1R$RkA5g@<;@-5z^ZwT_{;J)+_5+*%$_)k+3O!8mah45{b@1Fu2P_B#d1{o zUI`w$^<`TN6Q!3gd&8?30u6@;P;|n|K zk+^?b6`r}TNEfgWxHw#cy%uxKy9(L_gJHGU-!1_$I3A)>qa{9zi}B%j6+9L1&6a-u zD%kdV!L#Uz^wDl(s4UwL>HX&6QGY$y=M#bVA68)TXIETUn2dP66ZDQA6gIuN0kdic z<2b7UY{7~@T=zjG_OA0B3^&Y!Cu;ZLfzBYhrO#J*o-XEpEErgPYKGP0-h%2tZ+azi zEPs9FD&fr31?<=N7P}?pCHP=e0(8wj%`VKj1WP_RGMuv>Pd>hiqf(#2S6L%AU1Ji2 z&K^aL#w;MonticgQ8H-^ui_F~-Ee3^7XC<8AO-g{1hcwsLIdi_XDh^SRoZO8`!I2K zo>lqJcLt{@?l!AB9szl3$CulBFb_i&7LeyeC!E*{2jVuv$Q_+_Ib&C{;7F0>)^Qpd zcO}sE;vM|lm@GQ#z5;TNgdz(BYqV!toXuY;n2bB z!hKH%zP`UH&h>U@4{{uE)b?mNeepl4a3meWoYz6+`ytru&-l?JEm^ZdF`PDR#nKfv z)Md0Wox8S9`1{I=Se+bA&F}w)__yt_#`qHFvnv!1wThgUAPtP$p(%K;$rgSr_QNN; z^D%2)BWbl37dbHn{7`!n)G^3}CW{pOqKJ|;Z)f4Fnj+yptx%%httm7*tdLx^l7|Kb zE4+3n9G9u7aEGgQVD|hG0#P{xm$a?8#T%Ci@iV-sqVGv;>+g(%FRo)Vrj|qT>SI!y z+-P)n$buH zzHn|Q6Yt{TfEw*DnaV+li zb&>ZiPd}F4kQBOK!s}ncFgSbyxn`u#3fv8`bHqS8bZ8~6+VBwmtUkv6IKPKq!Ef<} z+)_;4uFN)Fbz-HTu0Y~^MSS+eiBaEXk!fPc)}C(Qm3EC{*NoeQj`??mvwzpZ|9hbc z+B4{xHE!(mT6^fRC<3FnB;xF^N-uWlax?tWIN3lqHg0yVbjrjRg4KihG@|w_8~5Zq zdgTlSWl0cjJnx5c{zu3N718G)7YT`TuR`U^JEW&vPB3WAz`TE(BDd#Kq@4*$Ytq5fU;tfrKaaE2SLSD(yCPiq`WLj#+aa-Lqcrc!95&eT6-;uU zO5IoIVb_+UXsHw>-BIuw*5(Xjo~j10Nar{;d9abaFbSo8yH(kmE_FzCzQ?(hn21Kn z3qsKQ5=_ffmT1dez=F_oSe|cA)8eG?Z=(ha_4o^MU&Dn%BYp`+>DO?A$u68`XhDwe zoQ(Sp>tXdpGwx_!9-f@^l;0qFJAUjRPW>7((9}Cm*di~0=^Zgw)6fD|@dN0ukyE&x zRr0uR$8#c5OPK2dD)OVxW6R)mxadt5+!=X^Bq?@E_eUjg*G|mG7I(1+dG#G;8Wp0! z$loxJEC$|wIrRVW91SKswlmB8!A0L!Vn(-A+1qeU@$Gb9Vl?Ko=viBV*TTL^DlWT= z`y>xKYNiFd^jeJ#l-bPEqb01>WD$QR^sBJ&co_Nw-x5L_myr$aJ~T7!92Uto!koJW z601Tz;b`2V$~oLN+VedQH`sQQ$t!ZWu_wR6&h^psLF#z6JV{^l^1XxK6$W6u>Vb5n z#|6B!e*jBAQVKzPHHAHq`S|j1f6$FRi+;%i1P!ZOB zSF^wj@aaKrNn0k4yrNCb{fE(jJJ0cX%Mu(5EnK7j02&>p1Di*t;>L!BG)VOVsa6;R zoYznsuwM^XPizK_pYHUn&K|raNrseR4@tXusc?MGaK5ttV7zsw8j3fU3MxH6$ozy1 zFnSYCdmkSal7u4RG>yjVt>-~ya4O0>IANc=T4-W>2x7h;#qN{G$-aVmc=Ifq53L%6 zU3>pPg}*GZyB+|^DCSfuZ<2&z8f2N~By9f_j4Ou_FyjhY3?0BTL6r!m!P0u%K5 z@VBbf_%Qia&E4L7KLhYKpy+ zn4juQEEv#)IT~Q|(v;TP`(f#NZ}RcII{h~43b`n_b5|zZf}-9+TrhViq`GSHmN&0M z|Lu1%)8YrblAX`jWt*^FAN(+@c{*LXc{5y}6bcQy&R6NqS&f~IMvNTx#nj`+>6E*% z#9&MYoka~Wqud7vE$NWlb~a}UUmuZEgT?*d{i!xBMKL&Xinzyp-=A%s6(}_5hR_Ep zg*fDC8(t=aJWB5n{9dmTOjo`TW|*H8&d~qRaGU~nP%9FX%8_X=-1Tyi2yne|vTC!sq)! z23G_t&IM!bTT{-JDiWJ!Iqv-6TIhW-pPqPHkLJHt1B~)!?7&hedL1b}=OB7V&otw& z_0d4a{SsXH1gP4r0!MbOW+T^JmCSVOL&jF#CgJ`Y=?l*^>|bsNu3Zl#ZyuZ?XT+Y# zh;3D{I$O+GpY4UIOR@#|>%G!ksUn2Pf5kihS<){>+i3ly=Wt3!h3Zslu+z;2aAUtK zkz?mMzwT(T@7qhlB?7!WszqW&zN3}NLHHRMjay3@f6D*6prQFo(B(E@v_=K@dA%Kd z8DoYPx0VU!;Z9UBB%f5geu9rL>d^nLH-n!+J5c}C^wUp!+Lm+-1{^yKg%?i2y<-u) z!}djj>f=Tn;Sz-=>bJ3V{z*_z4F$V(m(iu?2O8YbA@N`3=$-?^Fk$Qevxfh1IqQqz z!lZtf+?Fi*L`Sk)_bqw7?k22^=Xo`2XZA4V2kHHNfy^^V6M4ketWV}%`hNdQ5^v|u zTSNNWXlP1^-?v|FXIjxmCBQWxB8Jt>SFMCiL(BRj*LGh1-oX^r%c1ksJCIpkUxQN2aLF?K6O`}n9M>BMMT@CxR z(n*i&5o{Gb3Na!_NPkBsoc`hf2L^6tA1xX=XW2GEd$KxRlQDu-T0bX$x8*~?h3Dj) z+bWb9mj?aDzm$4SY{S@z8+m)~5H9daL*FkwP@h@CFWIibPSmO}YyZjASWlk4Ow7f{ z!wyRS1el}QpA^wEG>l*Dnn9HpxZ;R!mUQvc0!VH47u{|VeAV$5tP18>tZV?jyLVBl zG`NXKl5NRQomjjinMU)q?9jT{P!n(N#MS zRQN)iu;_7xtZh7|R4L=1+s8qtBN*O@J=kS9pW7qx7S_(K;%rM(?TX6h(6eKU(S}7q z?E4k0<=$U1F4R}jH7Eq;jtnBl>jjeOa#dtXzXVUyvv6BjhC${!++Mv9>`oqyE4Qto z?tb~Gzu*E~HQa$c547pv8CAlKx*$wAQH8Zx9@KAsqTrx(U260E2I;=7PiAf^fSKxr zLPq8-KE$RFiicbk)DKIsLi+?PiOVE!3a<+F)lzPH@*oHdIYeA)PJ!jMKjhk!Z-U~* zet2zZ1USm83w>_42^T!t_%SBcKr9P;eI5jgX42l+j%4njw3vd@x$FoE@adri`)z{$a!4Rg6mvgdi zpTN9N2)19`F4#UD4PP=cm`#y9qc4p>^X)5KG+-0e_UJ6+8Invx#5wGBR5@C2V(C3Rh28@KJA-#Jj79&^BAly4Dv$R#rH8DXszQ zvp=}VP){7TW;}i#YAvMH4`&B`U4+XIMW1Q?4Y+?XTuAwMkCY6ngTD_1L# zlWmqXL1ibVF7+pn;Q^yY{3D)ge+p?B5BH6xLzB`x<}^H?b&jE zzj7N!uYHdy@`Tbpn((yUjD?_&S&e=8K^+@i4u# zm&df%!lMJ`Z1IS3*icg=>8%ctevi0`zXt|Wlf`$)GtUx;F>5A=vU|zD{8?mbdmyvD zxs%%NYms!e4@Z#z4W|FigC!!@dXKddez}_hfv-*pUk$wALEJ^KFH#lH1~1(ISL7Tf zjiO%O1K^dxa(1(G2p;rJg{3cVkT)~R?A$+#Jfdu|1JIFA0!J&*oByTY$5<`mx&IV@ zbyo!UWN#WMmg*$WD%}BMHjbyg_F~N9Buuy+)C}nX4@W?6-E}72) zVVao5%Y7u-e)BE&j7|}=DT`@=N*PWS`vrGBEZC-UXTkAAl(_po28Cxn;6vF^aR+u; z{7xiFQwGV>tp0o8yv;H~`;DVt6Ktu;qf){AtUneHe+bje0T;b0f>6=J7~bJ7Oq`%W z6=TXIUQeFGcGufD!E?P3?o%l|TATyZJr;?Kgb3Vbuukat>J54?-w78VmZJI|d)hs^ z51-RiBDk7Uv`StNPYrq@ICKxS`#v2yYo@~C2YPIcY#Gju@!?C2Kf~p$rLgMTaOR(J zL%6pq1bRof@_tH3an(pC)~e2vXR6iQ3-w?SCG+sT!HT}hzeGMqegl`kwYYefBZ1E` zoaTpo{-MqZZ2Z#0XZK4&n}yM^(=`IW&-#He$M3@YijS!N=qx<@Sj?oR6}+zV0@RqM z1L1OBSh{*S%C{!784a~;f#=N-?@j_l0 zNS(aS3;+@g)&thyWtHfuKDezf*N0gjAZ>KRt%uTd+65jU?R8~u9 zmTw?jz3~x?b`)Ws98Y|wh2lFyWKmx}OD4tIfZ`ejNHh!)=A8(IR(=p&{UaXj50vl& zR99lZ7zzK)Qibl0w&iypN)!7uFYwlJJNmhN1RD4pM&FGsc1|7&pgiNI;PZSw9yQFQ z%k*}`OIc0&azp_XFNh?v)3T&LHRGYgF&#gd{f9@6C`zq72ho|`ThO&go!K^p)5nW% z;DC#t$PAHl1`jlde@`kxv&ZrpIiv{PE3Ky(m4=w{}A*x zu7;y!7Gm#bH2>}BO{lS&%Stb}a66j9z+Q+IDgwvB+M$(@ZSWrWUk!Hg9XB!NfD#0Z ztL6u84TUXQa!9Z8+!h&2a4<9G)=bU^`4^LD;J-X!!KeaZd)GYnK_#DT3&^qcUpEUh zh8z|a_f@CP1oe-8atE5ntr(ZWv26T74z3HU1aI1G3-o4$w>@Z7qoFmj_53+sSf zpFWBOc=v;)Vkv(63juO$$Ftzk?01NYaNALw{m6^r_al&Qwy}Y$ZyVYvpG|ZO7TVml!ha`aLDwV+rH2Nw^#1$F^U*eB^p*}V?dT!< z(?`=u$6vu^F^6g4tH?&q5;I{ZB7`{md4fisJDfaG2?NXGVUb^7y28VSD@nKnGk1y| zNY#6Ia>rJDw6ciJ$Q60b_ymGBJSPdyuHkz7o~pI#5BcyURd%k~jAVM~;quCTg2o*M z2u_u<-;eq*1=D9x>F|G7>>UVlvZSr2kKmbOAL#1**|0OS3}d3=urYKXyHGHQI=Z;9 z6N%l};_bm1X2`PC)-_-uYf7exj;vL`L~mPl1gh4Sz^a9!!&Xg=p1h?*mBWLPn>Pgt z2JPeTZUv#sK8}2yvRc^hoCg+b!a-7qxJo6S>)9@c`FuVotW3kd>Seg?f*+gt=9;9% z>@n&^{>EE>2u^-uFL)F#!7Vo@;nd+{MZV!`ws*^Te$!U=5pmrxS%m z`=xEqlIXvE^#>oaywBGTEycoO?l+v4sj{YA(44Gm-pEE+Ezdg3) z-{#U|jzfsH4Hk7r(Vfy9EDqX)A0y+5q2*oBcdo+X8&8p6eGw)f_$@s#|DVV#%?IJ( z1gg678f4*WVe6)N(F0Qs)s_jcJaUHMVYh;RS)h%(`aTl!Pf$9z#vc2JE2DZs5Cm>o zPJ$h0fhgIP$UabGF9-e*G&?i#mq8^79qEoL{8oNkwmMZRKgB8h&?k>yO6cL}sbJw^ z$`OAjR%LD=*dD8uzOU9KI+tywUtDqpb8m6ieCro{{b=6E;WR`^EV zh#uX81)uRj&Sy@c!;=-y0E~U539bkjf2Vwm5dARk+GiY)*14Fk;AO`EmbJ!0-GaYc< zX$?vPhQcBnG5dR|FZ>mmXpIMraje)Q^21lSx%~+^SGp13&1+!uh3i$79`n)Wv=8_4 zss_ta?czIf1`(OY1(-6wfq5S+7Dha_reSK%pf|-ARnF+sSAyuczg$DzQVa2^?Hg2I z-GG7AGx=#6{I(}kW6z?!^hcuVcdm5;9U@nK7%h}t>}H&rmsOK zIE@fSy4Aspeb>0{6Z(owj7DDTzX9x4Y!+I4n27HNCkZ*1B1l*3N}B$CGmAJnL+Ye4 z2xcp+5F$FXDeh{=uP=8Ak8f+yjsJ11(kTX9{Ji<}<)RD6H;0IzW0+WO%4KX%6z1e+ z!qnRdeBHrl_*JX}gU=7CR3zFEBfR8-$E_jPXZGb<{f&e(PuqCUGjTXAeG|rh{*NX; zzao`)G!)Vve-)1C+tb1M>!5V|G3;<#$8mGYiGu$;jBnqMJ~GE>q!5nbg&XLrD)D<1 z5)N4t$Mdef!)a@GEMD9DA1XMlvF;UnY8oM#_-M&jVg2{(+@rS&!htoFqH}nX2QY0DR4$L8Gf&tAolXM@WYpl z!6PGoVs_O$@t)(0axRwy7vV095zjTdf^ryadDnJNLtnNlGG9q}6_%85gh}3+a4v5LZp!KuZim*0o=7FkFiXL%1(C3DL_Ner zPoQ%jMZne(i7?#39kjBF1=2c}>)#bgH607c<2A0*C2O+qu0|5EnQX+XtW`r{Qxo)k zkO~1>okIL;SNQSXfb}y8LGN|)ELr?_tX2nsZzZKFF0Rmb#TI(WX&{d264{@1C}{mD z5`6l!a7L#U*!=fnNz(M`P_H@%ZQ753xvLV{IHw+_JPX6<;Yp+=Y&g+-nahIiYM|fk zlMuN`mZg}EX4lT{<}Xd-h(_a0ye-a_i}QkE52OksQ!;UE&24tVQ1qCptR|5)rqF+> z=vmt9PLGtS3D=Zr=+vUiLYWLs>PhcTT`n;SRdD@p<+jt#q9eeP2 z-(yhzNC87L-DvXo^{o4of2B#@eHf^@2F^wgqWfu|%30Y{(B)t{c==W0GD{OSVdHcn znI!INt2Nn3mlSAOaRAoY%%WY@BQd6IJsT(J2%o}~z<;(q^vVo}<8Q{ntgb|1`T#kc zAkNn>RMqiwV~4Wao}b8wZ@P5j{&`%iIMX?mv|LDN98bN(H;s;!47+(Eg*)4mj~~~k z;-=ocq&n{?ZCZBZ6zcGKn*iaS^)O{oGdZtLCH>=?2q2|Is z(#_R%nd0dO%5?dLi$b=)8}gn}_`O;Ql^+_w#sl+&?tE8a+P{0kD%(M1@_{*YLRuU= z$v#hIo~%V1$c3!K{v*rT^l|&#W5QytWV`UV9UMlPqf) zGZ$1hR}hQtG%(At!bQsRT(7k(vz+aTHq+(ki*p&M=e>?%H3e5!UAj794#-QCrSq!X zQOSHV%UQs%RbfBw56nklr573UU>Qv@k>{pcBuWkrGNp<`lZoe+0#xgn#9jm{afP<6 z(v{0&!Lt4UYKVWcXObwGi&^o@ncB?S+Zwf#Rd|hrS7^Pt29Cb5q&5n-Fnat^61Y|X zXLCEY-2WMRwi~nVss7L~eV`-X039E zyF^dO@G8WrsT$uJ&o`b|fp3Fc?U;D^$_IBdW%9MBO$o2M*f+npl>&Z9^e zBA%(z>*3NyuQ4=#ffjvew-XP?WC+h%#(;3~gK&C2htIuuJhxjJG$QJ8ca10AUhho| zr5g0f)F|?*&jc10Y!AVXM|{j^xKe&-;_`x1>kt6Sf({ep}$3`Z^qtuR;fJ*93Fo z0lu#7M0v3jhRS~yrs=v1@@FWhAH2$2{M2XrU%bNmdxz1F7A+P2zhmGP^AXyL%&3>s zTllf|G8Z-2Gn!~<(d z(fC_}{2dQo=g~^K#6BCZD4nE!W8+Xqzd!4~ZwTKyz7v_eKyHRbDLFglvY=?BB$=h` zj{U^5cCmI1|M~d>^1il|=!hI2-=!(=W}pB?*ADfjSbcY;tg{v2u&otbJK_IrI+xHM>JOZoJXtFHn{TY2%LH#&syg9!;YE} zQu#SYF?aiHy20i$M8}xo^|gPXQ&9sRmrg)k2WMttlLZ#i2#7T>ruL(1#d+xyykpYA zXI%E-zr=c?@+CjIf97c1wZZ^vb2h<>?2+u5SEcm8*BVmeP2ug6UWgLL6T@}-kTdrp zE=sB*w|ax$|}91mWS60B4U$2r->=++V{ zE&QVhOWM~%T#&!$HhB$3#%j2*kE386^N=l2BzSmB2H3CU=#}N7`Oj@8L|BbsC)AJA zwO@3oSF#y1>YyiT@eG>bMrcFKHy)m-awTQk}>PeMnZF7)(7(%kli%lThc_ z54vam1HGhf;csA-;PIG7pI^42(*>Np8HbB^()kCQRwMm2sc`-fzG0L=~%-4hek z#BZtq>-Nqd5fkh9hf88fo|yuiR51{?L?%kw^PO#jj-^BM**x(26oI*!8Zduy6@=w! zvFGnxAl<7!II9eRcRJ^B+(LrmngWC`bB;)iH?E@FAH5(47e>%kx2KUqcHhA(-j~2` z7ts&6g&b%<580QNVeF%0RjqT}@SgN3e3p-fiJ6JQN`;rCzq2e2e^!Rc2N`5PmuHuY zGP$K2=8!S*;(4JThvCs@(N#x@2FFK1-G$Ng`R{-5ts10;yDXrsOr9Tp^D5{}3&219 za^aMj8}W=A4*b?Y$=AF2!jBX+I$%bOaD9>{)HPVK%EdzwL*G{=rK`ZHW96XKY|iHA zKEo02HF#tCN3f|M3f)R$`CIR&5mgfo`iZ>lHlx>g+Tb#N`gsE9_e~_6i#k#5U57^V zzLDbcOMGUi5q$jnlY5)6OGx|iR-&Oh9?v8d!m8vrIN*L6MtRPIn?ZANOF9p4!()YM zB7-R*Y6myeYyfD?0nEsFfs*GD+>X$(^x}G+*p`~HdtgTwI;+wWM+ZE8wVNF12n3ax zdvR%#5eJz zW}tTOmSDrg&DiwfA1UkmALQwwps+1g@GWkaO1D=F_sWmbOT#VM`E^RPJi9+h4*7{^ z4mHBA+1KrcWCBK%)MNhRIV?-;UY#;Oj-x_P!1yao0!<79SGO+`bE775_q#l}&5dNa zuFr6zA;amzrjZ?|m1*g*B0flO7~QBkT*%1XjW7BfrNi#Ypm9I({Z{*&84u%m)xU+9 zUnobfOi*TR13uu@ni%Y|h^Ghl&F3_1^NDMpDEjzq4-D`bLRU_(7CyRM#;Ao+{1est z`08aJ;boUCD8C683dH@Dn|G1W)wdLz#-zcgo<%sJ-+3^gV{zl~yRdG;0(O^nf#aN7 zq0i7=w#Z4sA2eJ6yN~`QioSKYW$trqZ$AmS2kc;(rU4xxUo76WhO^3L zTI|zZRY;z_m2Mty$;J;6IqM=w*xS+-C< ztqNocmcbpFe$;ncA$n=&!7k-R@Yr${m*x~pOagUb#^is3_&*BsKfNH!`RAlvV>E9$ufilT?wvor5dqmhip3 zOR>FJQS@7e;vy{{I<8ucZkRO@89nd(!IdlBAW6X%#-|;F4BrtLCeBMIP7e{5ydKMabqJ;( z29BeJULq5;NzD3+o;G7r1pZ!&AZoNFby_cW+{N#X*~1cPufi$Z+wm6+?2@=^XYI-U zA=5$sRx5N)%)&SSl|pF#bgYc7;7q5y5JI{(!ec?9fhLYHg^SR9(=H%9RyT88zTdR=^st)}{kBbJI`%L7suTclbwj6YM@5}#I(H9ywmtdZ*=q2)R z#m6OCc(FtoOM(~TsewD7q3s>7vT`XG{n8g7MT?9-oxViG#leUjL9EvK3#LuHPtNbJ zCg{`;Y5gIxV30F^@cICvW&MF1uBe5NvrePj@~x2N>WuR?_`z#OUDR14`thbT;;i$D zIQYvwuyN18!8_*Rf8XS3!@U5(>F`|$x~oa!4(Zb54I=aO;(X!wJ_VscWO2wQtf3@n zDNG$d5sQ3Wg-oqO5PWc=M7*t%um}~_aj{n@g2()f6SA<$H%VTz3-+uzeGT}3WqrG_o(uel4^#!oo7Hl6Ih8J%#GLSBg%atsX>4V8D z=fDl{L1*6}ysg;ACbO;Fd~yHbX4R&oF0n22?%$&n7RI|2WFSroM=hxD9vA;I{<~49mU&rh|*j5F5e1h1eg+idm z33|^c0;VsG=Nf-xqF&)-syH(kJV`jb{qG$FAL+xK23!a0pM~I1r$`@~@?=-(A++!e zzz1Qauw}QHDf7NZyr&i843FpV6aCP6{}`ATUcyf^mWPFP**G}Xn9hz_S#|l)So(EX z6H(&-qYsk3q3!c;a(&oeKJ{3j5a4!`^fOA8w1q68I&$PiA=M~BZk z(JtxEHpa=7NUoSJ#;!aYC`Ap5Ldqo4PBG6L$XmK>|+32*cdPzoEuTta?~WTqMKMu_qBv9;+ah z*JeupyuKscG5i4cbkA|C%n7VEi@*voYx4I#5C4p6VQSyCoK;=BQJXHWK@H7R**@9MfYyATPTB zKQ0U4>prG|>N*4AcdaZ<6>}Z26>?#?xTKMT`_hM>ydL3sLG4m!qkV@XB^Z!z`_ zR~r0(52Fq?C>h~PqY1F2_7FI!gcJWfPdw~4lz5jazz6L@Fetf&TisURzjvlIDRCmI z^XVw__Bg5RGUD^Sci@ABYgm>43l`ifgX>y{aGh#3v|QMY9)Serh4!Tu+NqXL-Im@8zI-w;$|K^WS#1J$<4 za(Nf5sHcYy=%nYu=J{%T;*26plQ9FynMZ7T$6f5-Y%I*3q6AL+x`pdoROVX5Kr#;yt3_x>$JrB>{`?4y0q`2cZ56 zL&&!|g~#u%CKDSvVAT{o;pvZGND!l}w`dWRz(7fHsIJ z@?|z|GW?R!w_)`8nYboBh05PB1&_~VMA>B+)9X11!Jn3Z*Q#d8j~8WN3QCMd-G_gv zAK-~|J}yfWcM^TX8TjP$yzaPcAQVN8>pv29r&CBQr}V3bBjDHs?r>uRAO7VdY)ZBO z`Lh-@wP~fa>ZuETd149n+!{(3#M}`t_qCX{=O};R>_Yq?dkNyVJwc6wj&$dVZm8$- zINI6(=~?s9xpWYhcD0sN%sq;uVqHM4cQ-e9W{dRTNqziYdlm1rH;CEP61qo48DA_M4O$w+!h+ofaU}!c!gpn zMTvgf70ex4oq)qFhI8^>D(vd-I_NhXp<(V^2w$kmUR@&SV)#I^cgt@aC-#5FdN<;W zOO?1La}FzX=7Ii-z;w$MJU??XYqifZ>h%E?}|I?zpXDO&Ps;EK{*(lp9Mdr zcZ|AIIq?dzkIe zap*iFk*zlOCVe_P@YUUuRZq|A(dz56v|xBPu!~Nx)q_X*`K!daLo?gy^A^iiGt?0? zL_^M<#^q`=*_XBB@f#Eo!-F!g;ZX`1I`}@e$D1&}viWd$b194*bQdo*7xH`V<>SBY zs`S9N9K1T{Cv;T>;+_FZsL7f8!h89t^gwVpoLg@woXk?8ea%}S>dAEK6EvBg@IQbe z{e)b%twHI;2nf#I3&uC+l4Wzk(C^1>yl6cfWUCw@yISlVdZyqp^~rE)*$(lpwww)< z(W0@>wdk*pN60|kP^j7C&T1@^`G4!Ja4WvYlCpgISDTQ@3tz+LzkTV;lUb6wPK#e08;SeX@XJthr!OqcJ|((S`orc& z+H7;YJYG`2!jHVY31%mK#FofWw5(yMRJBlKdyLiu-z|ga_ragxxWPJDytkHr`l6ZW zZ0H2Vn|0zh+>-Vka30n)1(4YbYT(lPIpkAuE!S;q#-@6d;I^fNzN%cxJvCSam4D;N ziMs(Lyhn+uNv#BFT^iIKl*2jdRp6#+2Qz+klG%6n;=D+GGCV9^2&(hsDo(}FvfZ(q z-J@)bGEk!~`c|+jdQiSD<@)b9qV9OU&w+#FBRacs>*z@a?o7Msjh|VvOAoA%TCdaKw@_&<-62SG)nnod1k-HSXc` zuz`}0H9Jt-auc~?*vglb?Z>w=@9@=>XGHvDiF=cc{Oy6AkoM;vuGW#l!oAfv?Cn5e zFgONZy4Dk?kp4LGdN25WypHd;9Ky044~6F@#ayiK7zm!UK{_bytT4VV6(jv~peVQq zojeWj`@U+(`|<<6l@EsuQiLg!H{pYUL(s(25Ob#KV7}uSoLPKU_>fhE{fz@4enTeO zXC4<6HUwjz6-(g#+}T)L^^p4(+6jfD|G+e0H&5ONDl4CW$%>2{aF$+JwVLuF-p4Rs?cKl z2SHoDnJqVZ0lUq``$l#T*W5f=GSJhNSqYY~r&9x0-*`ga-)jKf#f|WBc|9)l4McSz z09sW?GMQ&N{PuaVc)|NTJlq=CF;G^hM#8y5E}2wA^xe7f+1pDkv@N2xD^ zBd6-fi-!}1C=Y#dr|)awU;kmC_wNEfo}GsDGE=l&WJw2xT1yuMMbf-nS0tud)M4`x z(!E>-!yfAKMQ8pB%C&}Y;MXq9dODQ;ihL~S7@LBP+Y*`^J{jG{ZjfATiGt+a#pLsq ziGt~9EiUY4GU0#D#mZTFY?GZ0U1oY0Pqu4er1YoYIOPw~)msi@5~ssY)d6hs`s1}CWSz$Jk-&ru(aVdxVe?V6`NjZQJ0?dR9i5Kzr2WSg_*=@-X*ec;a04l zdKJqc8}95)z{sC3B~#?rU_{DAOxMwZr*45*5EumU{r>P)vB6m7sLvZGPGL%=M`7Lk z)0}e}3a3tX;){ZN*yVOsQZ9^vL8cBk?8Q*FeCh@C`E(Dv-+dtknTycIMU!t4do3qq zN^oRbJT%;>LciQM*p~L4d#1CAXnA>&mrzQkZXJSWz!Wb#4i{#WgmPLZT5+)UIkenA zguM#2Wo;L~VNu6uqEO<*Kh-cMwfZAj`OZbO>qZyq{8)tr*FA;8=y~|9Is>9iwOQGh zC&KAxy3F+CWNdSmXNmtU5Dxc>ET*P&I0r;$V|cURJF6CoOR8aE(HW9kDdqQzoqO#o z>sX>qCOlp>k*J5h!`}C2K+fe2;VMURUXJ&L&{;)7%ywnkurmUZ`X@`5j@D%-o{BxP z*KSlZ;uk(m=}$dRpQpLR5%)}t!9E+06L-@-bgI~`OO;uQFWi*LHZfzIa_J|zUHyx+ z<}F0CPhauV=O`R$HxV9|bEG%Lg=$8PU|}CuGS$5Cu=MQ`L^0=gKyEZMUH?gXD(NEh zo%;jsj;erRUgI%i^>5VB^+S~}SDF2|F=TL3U+g#R1e_OnJqyO)!bNgDWMQQg=k1e1 zTF4jZ4A=)xwV$HV*M~yJ{wAbJ(`09UD%T3E?5{s_pQ znyYce?yq1tH3ilU+y?{I%h5m41N6i^X>hPFQ=FtjPx*cUht2>PWdD(TOIgEQH$?J% zR$oNZlv1{I-BGFyRp`EDFwM$Pri;bj*Z)y;9u76Ve;jX1OR2OJl?p}5NcTLS(?a$p zA}K2)TeegZk(Nplsi=sgeb4iGN>WKik;opQg)bRV`kmjOaL>K>Ip=&n@AvEVAvuV= z*ZP5OdotncA3b&>^(`2$d3+4@aMcVCc#W@>;8c9I6Us zS8iQ|g8GekO8u~qTE31twsHKfMV5TYh$_kSqSJg+up1uC(%^d&>oFtAgSbo%l=ktD zq-#8N;k=g*JYAr|yPErf*@$D%nSN6+=(~ZedC|r#kNXTMiVx^Xm07U;5XY^)`2ydZ ztHwoX-(Y;I4QPd{vp@C^VQ1ZNSlT(58oZN|1+VSVa!nlm@d|+lSBKM>=Nrhe5J!H? z8V{KL+KGLLr~wl+;Ai`HkytZ1)-~fNHXDAV_e!qfs`6CnuqETkwinG213wMhG==hK0iS=^AUrqXiyUNq(cUN6@uy`oHIOiMQ z4bYLiaej>>hbs_=q0U&)Ph>@Ug~9UBcy8RM9O!M*VN}pC!~uKy!5kkgq)wNiwOI}N&dx=x^R{^1YCgB_p*q>QaWp?Qq73_g^1?YM z8Qnkj32slnLiP?XFWLXR3I-ehCU8#&p57AYOP|!4!`?t#W+lsRZ@G<0|Cy2XWdqni z*Hj!=7mhmnjNyV;G>(Z1;G)$0u~5U9)xEv~>mNJAZ+SI#ZciG#5f{8mui}iR<=t2KqY(f7vdBAr};g%Qt)a-~CJ+$qI0>qB@@yc?0Wq ziySWfYkYObRFe72orPZN#pnC$V1Mp;{Fo*S=6V_!^}-JpPkMs7$A-|nq-~(Hxs@I} z7*2v}f5YbOL-`rIaTkd?NFf_Wv=Sk2rT&*w;M~JMUlS@~aZ@ zg8p6j(*GsCQ5wR6FWh8L((U-SU5nVLF#*Ce&xKH=AG#bgJ@kcCv(0%dm@%BDN zcJO{KzIiy3osZ0hI#&(+y{?X|eXh=oGvrXyV*-1!Y7oTE`ve}3BS3mVmfvl#3l=$# z!}w)>SQc~~bJl3$#T)10^WGESlN!YA`XA%u#H_0BoV#?tUl8{G^WYv-uk zJ4N1W|6cyX#yW637fT$!Pb1soHwd}oB<$U}Y)~X6==U{V*zKXytWM*WQA?##!*!Q=MBeYdq~*au~V-9>B7$?d0m4 zDhP^`N3Xte?E0#QP+_%{4r&jR>^xlsMfGmjtAC7@Wr=*J>|r!uiZf_uoM6p5;kZil z)a@xzf|T1!rN`pOL$ljJUh(Q@(s!C19ICSCSB2{Fw~MBcitI8l_06E4@QE<%z7li1 zT}U;Ol3|5LBssit4L($y%DUvAK|sGf?A+#8lC);j&~<&~nCmnu3AH9B8$?&joKT!{-Z*a50-clw?jf=HuO}z|ZIz#w)suj@T`9 z-{%ni8jj?f$IA!{;vBFp$iR#nK_qQf2Ta?40+ScchDV-daOI5xTQ*gV z%}%^alC^F)8tzEJCX?^n-sYL?-q984H&csk*YyKm7OVlE;%Q|wu6wBzenxS^6m z1XfP`3_Di(;LbQ1jL}HN)9#OO?yC+gzpTh?F92S8d;yXV#$(f!!Q|H0W%%p!W&FG7 zC|IubB};bc@WDF@#ir#DJP>WhnyC+ZT($+9MhzAt?!Z;@Phd~#Q0~}G9k$l!AsU}d z1+ya&cs>0JmA_bz*XsQ^E6w5j>#W5^J4@Kyuso<TiTbRaE3e$#8V&NkV*wVYx;G&-rufh3|bs3k?#UcsRYE@aW<%w9ld%vMaR2q1|`3T$lmJ+;@Nk$65p%oRnfW*;Bimex+O;6LA;%ancXc*6-7@%JDd{(PlAZw@N_^XFRZLHurP z_jm^XzJ;Jw@Ku;OYyxKP5s0thWL!UQF%DcXf$!s34r)Is_x;WY@_b$tq&yF1=ReEw z>i2?SfkBM$bn-L|pQk{3J z5{8IbGt0KVVB1{)T`%{;7#&^Sdf_sR?spegzj%NPPCSMxg>}L&hoi;WFBJIF!RsAA z6%&@tEriC4(bW8X6p46~%?#R`u@cKT;}>eQK*vfN>(r0WxNX3z3C8elPy%Yp+wjE) zHF>nwgV&?;K_xGdyJL4-DtVrS{Ni}&COb9$vFKxqxc>yqVJaJ2@BxZ6TQNyrO)}0~ zli93NW-`0QnTyg^blx=t?GFJlSvL%(r}W`o3o^{9`4f3*nhWdxO=dekrs3}AwJ`Wu zAL08xN51y59{+ulIL}{s3yu^iv;2geB3sv+7q9($2uinIE+kdS;|?oE&;aUJ5ruaJF-7;XWs4VBc}pbo0D z9ttbZ>S5&l5olU>iCB!-jJ(TXq{}-{|5OU5{G7u=QpVzhvs3Z8_5@t@MVD>6winlk zXRn!KBEG6U4h9cDV9@>$mhdGMSlBh9Rr`W!Cmex#DZv9TEZCt>TTxf{65TV$fZ?s7 zYBXt^CXSIt4If^7bR zld;tJVmwUN= zY~b#91!37xC7Nez$&wZigz`39{)g!%Fdb^mr)yM8n~q+`_V3fgUVkR{(lQdZHQvFs zNy;>2;dM4KXcT82mJ0UMmQuE^6z&DhW1j+7!N&fHY}=T0$j_UKO=m?<=-XPj9|^d% z#|w>4YOw42!Gh%}M(bqSa7oV{sJVDb^nz?9W3o;8z^qBIYo0cHA$Bg8tA!KUCqEGz z)4=PG9Xs5vkGF-;#*6n;m? zJJuCRnkA&1O2Zq!~-iHP>uH` zqU)*0vCsCKLaERUiJSFV{W%9_AUgG`z1 z;qW@en18?O8{Ti6jzd#MN>1V+zE7nyb>3G7-+E5dm42Z@ZqQg7Zc&fadoVoLp98OS ze~^bdY53;yM@aM;!YccfO6FMiVM0kVxl!baqZVt!$Mx!vKGvPg+L=N(?Bcl-1qRIM z$y%~`N+@b6&Os{d#V5Uw(ecq(ijt z!4r{r{NLUt;yEh`3l3UBlgkIVbN?_k&rJgD%72iZbX;Ugy~EcN7tpC^ql>C5`hx%M zMdY(%2=>7$+N@DZzFr=L|K1*}tXC6JtQG@tD+j0u$ZjvW#d9 zWs@_-%+$LMrZ)RT39sAkG|KwZN+e9=bHl*qcU6)cpDxu8tpq z(MLq@*C0LNhk6liS5M_vpNYg$&#kOslE`H%ZN%Y9iFkITHyZBSz>#uUW?}o0s7nGk zx6*NtD0Jd3yz)5aUT-=*L&D=+Xx^n0?z#bh&y7t$v0yyH$!0$TqPjTLU*WM#BV;e-JIx zM2yII+*&7IhcA6FcxN;`n0y=F3H^zw_dT3%KafepUVhLzUvetS2c~8k@($xuAZ^Q8 zyuJ7u@Kp3s%?iObu2t~KeuSf!CygWPXtmFc32_%J(xQ zY-#4?l+Fa zJMNpc;vnEh40(L@u4K4UF=$R3z`HF{V~u7RnAiN8?pDo z)c#7OM?=8deF4`$GMw6WA0cr$@|Y1CL9Xf%{5z?FTQhOEFvWKx`0dc+pQWdOa#B6y zEh)i^V;17`YXNw<=cIJ!nX|m%NHA`k4;E8r!s*LjvH55x(fculO<%kS8+R3=Zr4llt3`@o=gY)>o(yvySWNyi z_uy?7f5JYB3jE)gP~ms+AKX292`(%dh3@Ku*u`Vz_zP1Zs_6_<&rZO$ae>f(WggOC zeUYIV%>1l7$h5Q)c82dy>uS=l)V2XkKLpXc$~G{1T?o$QCbKaakEA+BM+?!WnymTI zYW%CEz;Bqnlb((C=e^St9S>wK!JT9k8KtxbjyeaS$*Qxk>q{lBQ*mWoQ_NV&xIJX` z2~{$=+#kx7P53*CkD%^=9jxu^NsvqitwC?7ntMFAak~Sfzdz6l*D>(SeHSa!3IkWQ z8oDPeg}QIq%N{IQ!D3~ebIN&Y!q~)XP}DXSj?Rw-4HZxP)T)fT)*Xjrk$v2$p~y~! zJ^>YllW3b04uk)jimML=!k_bRg{G=SWa`8!QtvB(S;`F@pj;(Xd_GV2k|>y~e4cJH z-ON8MLaAW;9lWO5iXI4KKG4omy3*nWZ@#mWzL@?2w9+-8cj*LftEIoNHldX$`>bH& zgZi-}Sy##9?<$l8tFV55reod%Lpmfero>Do7V`DhKyS%gs{NX9UT#xy`;Tkl`LS6% zr}UXs^dvE(_#f(AF~ipUFkHRv3$^LSv1&POVMwJ4lbev2h~Xa``yEPP>9Nr!@GB zQJy$)@+Wv(_>>e47*5ysi>1MnGU()qePL$g6dd$(j&w#z2wpuk36$T-@fnleIgV*R zLkpKWfmZDa#~{n&5PTqwR_xu&#SL_TFu6yP*4_%(q-#Z@8NoB9$MDmj-!wUR8NPct zSadw@!Gc?jo~K<%b#^4qHqYtpRj!@WRPGFJW}kICyF84Y?Cc(AQU%FFQLK_jf1J zh(8Sb-FSkowJ)VI9$#=lY62PacS}j>+-KP1Z@@;pwjr{E*Q5N>fh+4#ip?k)zBmp?9I=BK6jzQ##>cI<&2i$h`7fhlCXeF==-5X8Uz z(uY}!I}^Qm?zqy}1v(aOqAQ=blgzC{$fo4ibfm7BJ7}3plLsoXEiO)M3kkqcudLwd z<9r;w@E{rbC>dI3?21e3brGkVCSJIYFr);nhu4;b<}^D zujz`vPe!qm_vEPiep_i(!T?Cqe}S!=wOC<50H@Gc&jvWBNb)Yp;v@$(cKYjPQu%Qi z)UVdV<$9XzVRf9KZJ0*xB@JYQ9*+mEOpX8Y(VPCU9MAjKwZTsP@xs^OdN?+phqP@& z>7yHQ5V2wtJgIq%J&(1R_vmeOlgM_y(`bqZY=G(lwjJNU5ax=*=PmftZ>TZU{pwF9^x zAepts55k|jz){5zX!+fWi$BXStH1$##j!LPD`t&@1~`M$g-DF>IZJKloj?to#a<9O zzGzh}9+UXeJ&v9Vk!cg$(9SQ8`0gLUM$3oLqQZP!+gSw`%T@7;xgs&@ z8$q%aE)dI#T7i91gFEllh@6%m?hM_@r+ziX%qOkV@tp=ZYQ$F(aG1wO`UB8y&@ahE z{T*=Xr#64XKbjq!H68z431M$5Ch>NW4Y>BeYbX(l@!FqA>9K2Kris|&Z^vu!wAuhW z%GLR~nF{#L{uE@FY4Vq9^`)O*KP07JXF#X3=<>_BfwcqUaF>!7H|ns+m|o+@UAg-a zdX4u8Su#g3`pk43KgI$RR;a_vry+vF+jW@f(M`AY^nvcWtI(UU47-NgvGgEUG_dd~ zc@^1=i-T@q=)cGGfO-ISZHc7MyWQAqopHqS_XlErq6L-e(n+OWIx6oer@lqHT<%e0 zv~^nyynQu^Ir9v{cBVk5eFZV{I|r+4y0PS!JGvdS=8m+!huoL*q2Wgjx%y)wzCTxj ziZ5cwpXEPG@O71HZ3I=}QiaQNei zbTvO4;`U_FV|V8<(r*ZBXj;TB#D~F$Vfs?1yQE0PiApG&*afJX~(rYl+#21YorC^_|7+C0b8D6c;M$+dcsJI_= zoD(7XKp)J*|6b*x+{Q~(|9T*r-}nz)vbVCHW)pPp>`#VC&ZBI_Y25s(36BqbP73c_ zVj9q1{SLF`S2GjCcsOn1z)p;dhR5&EF#G?O zF^AQioZU)9tF0^G@d5+*@6Tk|BD!k!e_zTD=?#Ia`>p7jQqcwB`W=E_Cju4rh~6h{ z@SR+b&36e5zcL>Wud1Ramo&j{%Sy3}A{EY#^dZ%vBgw2K31l<~L@@)wc#$P6K3M_x zJGJ-+t0N>O&0l!CA^z0b^9h71gwd$4$|;di+(dlNjW;fOIUSqMz~~yt}#=KmNTBCJk3%Yxpf; z)^!HEnvHg>y;IwKMI4B(#-0HRQ=GH{a zFBQE8O()^N7%whJV=;fm^cYpn7!QYo<8i`~F#22TGS{_nx*&5iTKs=+B6kAg@ocF& z-?ynrl4sk1k9^0oiT=N(&D#XDKBoo&JBH!W=E?Xs>N*Op&!uaPhVy?$`k+C05m~ci zFzNGw@`#maZ_%|{LT()I-n+cbwQ&%S{*Q@z-*uLp_O7E?&3A)LL#H{txu zdUEiM^!%K2G{tTQ$*T!}63ba2emNPIC-Ottn}i9CqIxN`Rr z;g4X3(^Nl`%^rhT=KH}y_8K+%?8Ig|df{H0ooa}`=6=KlcGK{t%5s(wmWnGEj3aNR z>)`YGE<)tEFT@JA{TVA*t4KG1@F8N*n+M7N*^tEA{%n1tJ{vvV zoE{Q$YWJN&NnD5#KWSDWe(}>`A`MpTL>|EEDHCvJd>C3gTeGAeMd-dqhkrv?uy>Dp zA?u$i>$~0ohc#YcfpII?iSJi9kMIBjSSItAaD ze(6Cte{2(R8_*xk*6HAZtr~c%qZw_+?Ss$CzQSPdO1RLHfk`>TVa#;{ZuS*DUZc$x zC$3B-9+QJn`QSm=b}5UTsrf?eiyF|?dl*0Z+&9Qu5seXgNAPG)9eIClm7t@)1}99~ zhMi7}DBUCOh>U-Mq*MkK$DD`qM|vzGY#=`R#R-p7b0osq` zOv^--{}xh<>ekQTx!*sk;PXR+7 z34GY2C&YOfGfm}Jg#P|ZEGF`#ZD27RSE|Qnma6DF>6g^k=niTfzKgq4TtzRqJ^rl; zre-%d*8kaDRI9J2Q&caJvNxycwAs%1Xig9;tTE);_2t05DHU5{W|74KQ{mv=0vI#! zfKWGhIi6iy2h$e}hW(GCs6~+jkqmO9Q-{m)PgAThF4K}|S#JQ(CF4=mHyX6xrGb3x zR6I6Tm3|Da#zgaDcheij{Duq-OCw%nb2f*E1mDn%S zWpuc$F)`2#06(i)82oJlUdeamlU$=gA?hSLbN@iEF-lkvbQSrIZqggjmkks?k%h-z zVwmj_?&pbVOqA9_kpBhvUFij+R~@JOcv1(85h!Ix*>X5YZSjaoKfe3oAsk$6goDdQL7S@u^0G@X^gJcp@_ZPV}3B*7lcRM(bJb```#%3N4^Lf0yl zQw6sh4K%xNv&3YM8oxL>2Df;cqhH@+Fo!n$5PN+Uo=8n?L%|moK$1>`WQUcm z^rE#EzxQr4Y%r^X_FdvERQ)z2cb1|w`VK5>ehd9K&*pl(Z__=KIb=ys_uT8yowFYlYNtDUdFPkSMb%6o(vnN#?`pUYs4alG`)rO{l)deI|T zya=*fM?h!VCVX0;#T3|jJg+Rk#(-L^uMx8^ug}qP`xbbpo&$-CR4`%MYg#yQHL*`o zhBKjKaXUUDL!y-Ub;q1B(p!lSv~LjE*#7KqyEtoI{()?I>xz4({UG1QXklp;k27B; zk(uH=&W-bDgJZ0-HK07-=rJ+ z?|@oGBlgd$8rDV5g%Af>-Xtys|gH&j8bdmL+hJrt0sMXq0|pq{)(6SC;{-#pV|seC;^?^_0Os%XDx^T>;o_JqIi`l64qv z!y8LJlaU^ym`6bk3<`3J=pNUOe*_8gB_Vu4Rsq1vX&n!nBA@x zZqefl(v$D|fLF!#D7?|>Lk~sj@`0;3W)S8}3#yx_YugjD#$yxhv-mWfRd`bBG_i*sdQ}JO z|H|=+f$D|Q>&U@HB|KZe$sCcHxS0KWb2B2IB&x8&ac9Sw?z$!DR^DkB^mc#gWvi7EnO0O z0Nx>6*xDi07|Ly+1I~SsekrP;+pnte*Nq%`)8D_rKV<@$U~B`wTo>ZQz4>tISpu0~ zzg>7FE5~M7+3+LgoCD#@DQb5h7d3o`!q6Lbq`O1Roa&dCe2p<8Ph?aW;l{yX&--MV zp(^8{#4R?nP>z)mu@vof}1L#bmq3hXuIGPo&Iq&mJRtvza{yxMgEUab5SIu zy2O#4&;XGya%h*r9!YD4s_681N+Vwt!4tc`qPwLFmQEW64t`#UgWtfpws4diy9s7K zT7gQnhLB(^$LpkY3Nees`mcuC@$~YC{9f?1pTZY z!|j+tsM@$1hfF$yy5Y--OxO$3I#ZP?S}9SJold;R!!qbye~TV1_GO#?sFTU_?Z9*7 zHd>OM`zQG-fgy6g))|cf zJyZ5``&(ggTVe6x7YAU=%h4>b-#K_%7Kz7QC&1%;eLkajARTXg0Q8stpqCy*;K55> zxcb?5$Nbd?`Fk%;6H+P5V~-(wX(c6TZF7lfdwMQ}r|*MWRY2Fw^|jql#4;)c3=_(gUMb;^mPhO?2J zYjeQr!8LGV7RSFB>I|9&?bI~uH7&jEPq+Wl6T+jCK+9j3S9>#$dHf5bapk%Mw3d-y zHrL^-gDlI9=#jQqCerHr0q_cB`LKa+rCI-EoyPXfz#(~6Fh}_WR8I+kE{DUMTDTk= zkfzNWreDPygD&9e^S9xRQx$AoA-W)*b->0c=Y)>dIpq7>`RIJp7$(ULl04O>RI^|# z+9oK1hQBM{H~BW4YHR{UyREo>xEeMWor5P$+rV{kKUDL2fGytn#60kkz!y5PW9s&3 zXMX|;gN;~So*ZwHnTN|FUHI$k-f|^x^NHzYZ){pPS&$38M{Z?k^F?#V@l#}P!Sa-= zaAwONVUS@CIW5FO>nde_qxN(zrE0&hT1E<ghP6*@TXd zFTkH(N5Eo^9onY3V|27T`Ml)-v}tz3Pl#_Is$s;R^W^;T|`T! zo9x_hh}5j~Bm2_lfQ`qQ;=mQV(O+4WuavGLYNtzxPhKrL&K?72vqzyzN-BhgKY|sY z#V!xYg|68RSUsQ;VpNi#ToMCA)HCqtt^_cfQ4C@AmRLiQP{&*ebVm8Jqwk*6Z;QQ{ zhRqV}r$@O#ngSN&X zkX8pn`OrqJ965$hla!E$z8_(lUZl_`b~pKccReQc^hFn2dH#v!JsRDa1`C&E!Z*uB z=vFp|AF#Uy^OXw(n_qos_99D>Jsu!zh?s$)$oTNz>vb3>FQuC!e{t(#gR$wO zc>g!>#BA$m^e;Y3{`JiuZgp>kJ=bQ!$N2ty;F?u1$>u7vxcCcmN~hwDvvO?ra*-9? zl}6?V6q3?$yI{)%2QK{ER(L)<8Ph(W!n1wTseQ2_HtNk}eVW!GG@piV--qG7tmUxs z*EQj;y&pK8*n_tnTTsFFJ?1{XM6C79V9eY|dhgqR;I>YJ!l`4iSp(&TF+55jS?3vl?sZNYt* z4oOW@X6tsT!P=>Aj=f@@%ppAk!&^T>)(0!T?t&TrpK$^7n|%~M_^9K!3n}=!;4Z9* zQDu)Ko}uNg+hjt4HO+K6!B%|k$3MBT8s3EsWdXm<@kqBF>k~U4a!$5j?Ti5IOg)6V z!Vd{O|E!@cPmTASK84pbDu9obS#ayjDD1DzVbJY^;M;Nu8a|Jfddt70;i(PWs&Xsd z-F`Ids1cb#!@WRvdIX#m`*4z#MI|YG2vG=ZqK5k%L{_X7ZkxJ;$~c-)l{?qSyJ-_8 zcE08?d(8>Fw(f$^DwKh5o*kIKuElYRu@d(&^Pp=+FBisaz<-4dtQ)hDAD5>KQM(v? z4__=*YOxgixf!_r!%ZAKpuer zEI0GCD<yb^Y#wZKsJy^k7Q^fl^X`xg}jvLb5MJHHq=CdAgBzK)P{~$LG z=iHso;eb}^sjb3Rj9E&LuJaJ{Ftx-cTc38d>M;L(b;9O=PSOxFnBO}pj7-bDELca1 z*%iNLY>(Xna|{7_6#t|aPp@*sIoDOon;sMK-k9_$*n7gt%E zi?gp=-0lT3kQ8l#HzkdrzBL`PHh#o_TyYMUXolMZKZ4=8jvDe{M>PV$iW<~by9J?1o`|P1!3Q7n~6$;P2-{ zxqdlj%uxO-G`^k>@6JTvubfag;TVj0rw735WlL#h*J1pVTZ9@vg2>iaGx&o11(4?O z6qLq&!td8MU`}K|kuxjhRzGdA=#MEvA5tq1 z4S0KlQvIr7yvp~h;@+wX-S0I@wq}fGOO#KOrE6x;?w)Y=d*odFmeLGYulzu}$W*di zJzXf?BqQ8g^@LQ&9i|JXuE5Suj>5Y3O44Pd$}iotmjBkj8mQA+{^+`7R2^V~_2vO^ zJHsk5Py5$;G7EY?UaBrlfrxo)ap}gJ zutTMo4lnyIwQ?UPcIcK8+oi=4xAk+OXnQW2v^Jo5yB6;}Y$cTS3nj_+ikzu!E|=;Y zj^~FbLF1U0^kt$hz>q<>tWb1&o~@TU?GfF5>RK@JVHNKGq)vv8yoNTFW$^K9AM7Y= zpy}oVY23?k^uG3T-ZplT=ya%*L_99SvdY2y#Wi7^=5uGE#l4*@I++PhE6j0at0G#T zNFb4lE!3<3O?ceW1lKyt@m`7}q(5B3G*0h>bqmi!kYXd4$}Gg#q!9Gjn+cT@-k@RQ z9xxcXjar+eLiX1;7-po!Tb)Y+>2e1=d)JcMdmM+C?JHQ>qg`kiyNYQ(9F0eRiEMfc zb?*6NeVlsC0OgnW;sKBMp!*^Y$7;&KfJRd>YEy->>X#%}6P4Ml+Ct&N^N(QpwTWgt zEucbrVZgJWqz`%{$Fp9u>hpGYIO zULrYikAn1$=SuSCa|byUlR`(2ieV9b7+#&C4^ao&>3N4K@I1X7f@c+whoH)C z*gc_r?7XX(R5I-plWE%<|K5HA(>kIl=+k3=EtpA zV3H5BZh`3b>ci@4{h8B99tOS~1ov-GWHVj2OR`#XF{-lzeC$i{k$eU29a0NtoQ*&} z`V?nWQOnI}Fz3s=6!_sC+WhX*3qTcpSyJ!`@+=8(@1(wD@!KQZonr&|m3E_9!t+!* z*e8`_8Et|%2Oqe$e+KS7o>&sSfMV##B#@V@hkUk$ley5t8wQ1u4gW;X(5*MVD`!a>SNzh`nnKyhqfv?#&0Gd`O3qA#me&75Em#G)xHLDoO zyIVcDbjuTzohv8iL&w2Dn?KOM`!jY1&xA{M)2Z;?knLI;0wd~uE{oYA270T25dM#k9q}maw~FTF%55^>S75wzRd&f+31l?Wv9p@$xoWi+F_0os_gTQBGIyr5a0FU*`@o9b`yXgzFLJG{HTYOW1DAR9pYiNVjmjbL;}T z$ArPLK{Y6^;Eo;RGvT>us$e|LhQ2P$p&_#lk!xzQtk3M_@cY1ZvVO-B{8ly$ydM4) zX3h_#Jz9%VeSHMFMSa83BpPy5&DcBZ;r!YyFYuAuR9M4wADQ2Qa6mBJPw=JPfTnfoWdG zK#C92M;g}96fl?$`qKc`12zaF{8ZuJ!AE5Cm`pN%LnHp)*+7hwa=8})>ZE9kA*en# z=gSgyI4YDr5&0e|Sh%~G%uX(XUgu8AZim2zpQ*Uzo(G>AbOj?%c4JHHW7_&woo#xa zfCY#3Sn2p72~x_#4xyDu88*c zRQVNVfpFyD4$NJC2`npzW711we(Av>EW1dH9jFA>Ei%6HYu7@PtrMH{Esv|L9tP48 z3jC_tp=_O=CBoR>f_&yPQo&^S2BT{bzi~EPUpRgC*iV~ z8nSlc7h+mv$ah64F?Fl6bo5OLwb+@AyA2evu;;$`j#wm#8LJ1C=8d@aE`z04nz;7q zzj*JH2jNI!FjSAI!7o-wM(rpSvbyXb^K~Yf73l~1?t_`#vsoD5x`bB}=TPP@+Fac} zby^T|5K}yrnZbcKSksXsnI^Bx#y*VVA~zS2@{O)^yQ?uW*Y~)}aR}!Z=mRmyU*V=C zr)1GRS7_u8u{8ZUqVTX=aGQFXrCY`kpSa_YeeO6YILW{--KFq-Zln-0e;LT`T`jn{ zeI;Jb3BqiVC1-b9iH|qy6#nJhq}yao_}EQ)?DDv^U4r$l-MZdV;7?x5;v_HPX|F(_b2mcvJ(uWM;BL+p|&Y(_gRWRZAo<9TO zuD7}C6%TQ>w>>-4rplmowdk_w&$)}8&|@CnqHk6olJ~z75>nUTWP^VE#$l?QhMObj z6t@pHoxXyzZ;IJtk@Kg0onhHr(bcz5%z|7pCY_VT?z1Z3fXDOjY?T9Fmii8DPORcb z-Yv!67K9JN4^sdBJ}g=1%IwF_LW{*~**&Xy*!nRK7p)x4S)L6BT5*sLjQ@ici^S)o z$`d00&fMg;K>6^=9w^i*R{+d-N_>OReIgL%6d!Lgurpn8)Y(f^exITdQ znc_KMFbW$=PtgAFtypN%U|doYK}OkF;QX{c@aKjdzw^32XPF!V_wSp*y^;}p(za3} z?2aJiJ%`x!bY5M2X5V&4h6sQpia=vv&xtn>`1 z)X9dJ(%-_#B8ETj&BXxgVf53eu_U7B0XMrf6N;-(;%@U(blV6GrhoS;zOqfhjUG1q zuqu82c0a-{2m?rNHly>e$nw#NgPH&KFC`WMkD&2IwXkD&47Ds4otL#mR6SmwA9`Ckv`?V2kYTsg*(L6T8W>w-h|g}7{JC4pF;^Z%H;MZ( zF{|7; zCG+-^g3?lwb$k(Anm3r%v!Kxm`OCTdjmsjpWVbwj|G+hTleiEUUQlF(-b&!!5{HRP-C^m4 zSk8X(brP_74=$-w#;3c)F8aPG?6=_)_dklx!yl{njpN8D*(s43q9I9<=iJw^BC{#q zb}3pUEwt^CWMo8?ijWjhc%J(@loBN+WlJihq0*lEo!?*J^?ID=Irn{CpU?X(dVgDV zPtKSN@%D^PrEjDavdFC(wN6=akuaK~SL;3ml7d4m-CJ-9(2 z+%pYu9sw>`&k ziI;c>&DK6pZ@&rk6J8VBk$>sd1iJT$8JTN)i0-|&5I(P43NbvV z=9c?8$LcGveXcqNC3}O;%LdwGyasRcnI794<8fSP3ohf&ey(zE?D7?a zs|^+>Uaxe}WI-KlZeGUi%xf3CUpJDSnNb0&U+<+-={3wBB`N4TJ%zo!><07xl^rB@ z^kDau6wthCfb%O-g^Oek;8EXcyzgQ(cWCt~nwMaQzZWau%sE};{^Qx$G24WhrLh6Q z-GlR8D2B9Kj9Czl(0-UhU5Y}b8aZBX_pzYEa*lp@X zpUIn&AJL1b{|j64ePRRICYA-LKZUN?p#zZ-^{{VpRn=opUD5vNVlv~7P&C!m9i*c3 zV0c$N>a-+?n3ixx>R+_L`Faaf8$H1_CxtZMGn>9ix&{ZtUGQ7fEh}zjHYk;a!PMg; z;mq9~sAuR$wq2>=`KJrHXNnG@zZ$8c98Qy4@m`8sAY}+wuY};6FBOow${udy#!)?= zQ{;Du7PpS?j$Sx=lhn){MIO4l<3ilVrJT8iOOn)Cd!DCM@G&1s7u?{Pqnp9ULjjsn zX2KoqB6`trkS<)Q$JT3Kg;P!~_|*RgnizbAJC?6escJyf+T#Jsw$3HDItFmj5-D1> zdOS|tuENGI8_n(69nRjFs6g6Vwo$G4D_}J`6BeJ-;7tBJCILZ#+;zMP4Rhr{xHOM( z)H(&bd2iE%N9%DEpMhvu9Ew+aKMQx?yo~>H7U8o~D_~6h2{c<|%S~{eM^+u!C+zT^ z4Villh-J=mkeV#ce3#R}QtfmYZ<35h4;P|J-bf`wZWhCZ+7oas{v=)bE}F+&^1jzg zCPM%4!?1b15&Yep0Cm30vG$HDT&OF?kxv)GasTz`z4;u9_3-W+ZYion?;@eLW1%8B4kmV~R;gAB~o9G;3#w533SVIMzs+fe&pK_#do)Zk`8`J$A zs@#&sO4PC1jKejjM6JJ$!i4-dHaF0O+i`~_D))|Kf43an_)waAZexeb^5?Qaipxl{ zx*{6JAAydF0fFDIscg=06iit0fm}Od2D+X;IO6nQk;Lm=FrcV{k%rc+YLXL_jSCo>!ZQwbqNFAj9MFFOLC4SV z{@JxKW8!h@Ik28<6wiap*C#^diqV*v)J57pYN4%6A2?oXK#v8vd=DlAN16`6!_*UC z*R~4})ienY_7)JYXhpX9^Gb9cD8`t{or2dn7Ub1t3#!xSB-l7gi!JDViw7W=-sv94 zjv0^Ou5Ciyt7Sxi{Zn9lkR96YctVb)Y0%9(#}mWtmh_v;B(`|_G?AQ>J{sS&!?o`f zxTg*V5Z-i?>~K=Y_hXO4-$g?tw7d`-mG0uWaRan`X%SvB6(?s7OQC*BBrp~+I4U(B z5_}$D(53a@Cp;?{8OAgHtw%!ek`xTyXNKc?rQx5uCg#1%0+%_rvCYp0yMGzL%QT+F zBy&XMI5`)J$F0XJ_5C=uFP(%}Uc{ygI;{G8SuVCWn@!a6g*PRw(4-{=e_fI1tk&Zf zIXgD!P!MOTHl98Iw+7_$(wI2cBRHww4V}&|!qYxAkeIa%Ln40B&lQh}sYVmsws<*- z`fwT|IyQ4Xt-e_9CdF=l>4dr!$AM%d3JT9eqxkP+q2&<`I1qal+~EPuU7^fczf8yT z(O&H2BYq@#`gUk!_S19EqcC(;D`>Rv4k}9lWWDVW9nKfg1v5sIc^m(OlK5lbU~nA2 zDU^zoR>Y9b?Uc{LjAXmZcG2ZKSk9(>A@*mNLGq+VP+nC{GX1YYb80Qm;V6Ku=l8>v zL*f`-HwggS=3v61g`z{gVX0yP(^1XG)++lp^hd#CMWPBzYE;0egWQg z-KOWhugA6R%EUW%8|g{YhMjN5!0qE}$Z+==K?`jpa_yA@nSR3lb{N6VWdhu7JZ4s`z|$x`;L7~hF?k9Q{TWO=TSdpc+spQJ{b{QV#! z9&Jo)ScMh8pe^ztHBOZPb$dW{ml`;J@+kTF`fR1tgAZ0t9pbbpa0?tSRV5Fx&Nzs{Olu67nS6l$?%{X0-?;30}4#xR_@ zA{Qa^5N->kaEt0u!S1GXh-4pP_l8=0b5$JM>J>4(Ohi|l8pDN_GVE*7LFk^g5N#j! zlkxSDc%jpt(1cjw?hzapGb5jtKm84X&C(*Qn}%8?*NKZ%ARerD0Qs6LXs9@Yo=Qm< zhVVUN+3#=p_tOVhW-7+YuTY}FGnaE~)+T}JR^Gj_E(Aupo+kGl-l3pwB6!f5bg9)K z*NKUqq9%SSL|NyA6Bk@(Y1^QA_i!TWNRp)vT~n= zasdkD`GmD_XEP+L^a-i1?A4b;-C7GAYq2d78; zyZdJsJqt2SlwC0H`!JF__WdF3n#yOkMtwt>gD1iG{Z?$6q{&X1sK&+aJIm_n$kI9T zfV?vo_S4;vxHKK5&H7-@xG>JkOPo~wJjOq3k<7)rX_N_j0(y2MF+N)rRu3;hyS7yH z?LPo=Bo~+2o5S8U$MEf$Fs@_ne;BcFGAlH z4y903-gga3`Q62`i~pc@RU_86|6qPT-GM%}i72fq#^&#z&CRe>K*OT3;NX;t)#+O7vRjY9$+?dBsg6PCpCe$#J3B1vnmj-JC!d02oWyN=-e4^2YpO^M_`?*u$?PpnKKOz4Xj4}#BeMM^kcqj#!{a^cf#I{7L8e)!84=GpxQiuy*VX<*?V&$6XbFQ zUQsEYXE+v4m`7n)mKS`9(GXqwFJ7>dKg0ZzSqNdbCPCQhEBI`f#oc$Z}t6&^G^ROB6zJH|?MS7xDg%h9x7s38j z^Dx`#YSsOu(d_l$X)xP|uoE1|ipJzP@!Zez@O7&SCw9(_*A0h)|G8@zJWrGDupGhd zoW2P>C%mF}HEg-q6eoN%Q-vh|;K+!gFjoG~e%$^>AAKUUxKk&jImPwgsDZN_*DIHU zX|NWqJGYVcqEAFn_>pQGRl^#6QwXV^hi;E=;#SojQ1LA!N6RjNmro8At@w_uzcQgG zbP~3lFN06Vbh)sAldPfnA9zAD{fv{0 zO-nv>I_RL|g6ERB8hnI6^P8yV`l#@rFLueSUIU=Zc+IITre(#9hfkK zXBSUpcW(HB{(SG!&PRYj&osbPMILO)b%=TP3Qngdlc9dhFpt5-BMeFiR#>jkOlzWDh;AvmT^;TGpI5+yegRK3dKwZA)O z@gRgM`9}eb(1A|Ik@KsKU`OQgE=U_?(j710R2J!TrM{}%8e>QBm|O||qqXTL9S8Uw z8ABf|;lJmDc5LWdcQ`T2ll=3W#hSWafvj2P|Ih#9hVc>@9$?QJT|Nc{N1xF>x#dJh zLxN?KxH~@W%f#`+Zd~a1exi9{3wS?J<;IsbU`f0jQ(*KOHHI#Oj^SihocakiIj4}s zBWrQ!MJTRTQUcqprTkv9ga~JzMNva89`+Sy>-Y{+uK5!B-OLJ%Oj3!PEum$HcXIx* zY1Hj^G1sU@v22QvbTzkh6tJ7&o z=VL70zn#IK=5 zJ6jD!^IUk|>-$)|{;mi;1O;f%_hbqmq>%lV0&Fx`D%u>Oh>gi|+>@h)BtedCDjo#BBRJH+VNjB&AMmn6zj1{0i zzm>`2M=xiCAK-(5B%V=!ShVWMI`n?ohcBi6F`p!UF=MAUK(AUAtt+367jG9**T`e^ zZi_Sx3)l+!N8^N3&BoyIxi7)s`BgB;vd8&DnGk)s0c7~y*2R=CjJB6V!%P3jlcEgK zGufXsbd^6^Tzrmw5fmc-`%SGICE&`S5m(X_!XEmspNd&7qq-yvstpe?$@{O?AU|mW>umsgBg_7}m=kdXL7jd3Q{)?FBzOdpQR_H6O?I@=n6(Nov$YrUKJa&eO^9rv+YF zhe)N`8?q~8CVMBYhAve+f$hJo(Nijf`V=X%!IO{ja~3%^IsXlu@3mmpZM;Yy%s&Z` z8IM7Ce~QAK?$I8XGSR=xL}-gpp`B#y;F^7NzJ^ffOqJyjN` zAHBlo6r+j#MHdiRuVC9p^VyP*KqbGez?NG+@G
    Sld_e_fia)iw<*9_5brD_>G? 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�SG5<0!IqH-$cC|}mV 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
    K_#Cr zg7>y((J8e9rltzMHLr{!nR6U`JTrpYwhcm3+DGc?@*h2NC58sQYNR8M9U>{MH?Z|g zDcP7Q_{&`%%@-eigO+EF=cvLjJ?f_|8 zGzb3-PDMUR$h;pKhw_QvxuEGW^yYV49G4Y={qWUGUFe6Mm{as8M+ybm7;|Fv_y)4}L$ z4|V7GdhOYILNSxP9k9XbOb+|iN)(~UEF_RC2%B1g$68{Q*>Z+ zBkbs&#BAs4m}Mu^A);N9EBO&ZZib9Be|}^F#EWO+(JU3*W^acD+Ib-I(;bZpf^SsO42^!0C@Bg3k{V~?}td~?j7jwgi^%;E~Y>FwP^wk=Sck?)9S_)≀{jUp;OY@9rW|Rkb`q(!R2;K zJh#V#bZk|osrLT#*ZN3YCi((ej!a{v=sY}rRtAS%5Vuiv*o zt6CX84xd6^-mRerkG~}c4|vnR7uDg`<8hF-wul%}wynt%XT>+Q7tce$dVHa&DdsNFn;i#}jJ#7!m z&P|84)o(~h(n#1ce29Agx&|NJkD<%+>x3z2#6>sr;H2SqIGms&=y~yIxyuXFe`|yB zx?r#!eTTX7Di<~goQk~SGB6x6pnD>ExH7qZ;y#M)>sm$jq`1bE(116s}L{CplTA0qbgXaA|!LdQDu0(vh{yr=cLc zaea{aaL@s6=I7B%3ra|}`grm+iwE1%%VeFN9?A1^<&B?C!WUPfX|B&np>OpYxgZ)u zo>-g}7-$#h-gQ5yLtzM&e!iUC)8R2v?l^VXrw!xFLeOYjFrDZBiY%Qsk^NyV1wXG$ z!QnHTX;yR~gmr{Kbn$UI=weN5#)*;X(+Y|B`1f#_J|Ta<#E|jp-eYHxDX8ULA#ues zNKaZg$C0g7G?`SYkkOlk91Y)Vbfs8eg<9c_c<8W1&z#XjR zwr#8}PROgG``kBBgZ)y;Ee#trYH1=S@7~r-(cHSoX|LMX0@g5+>V-Hshc=yfTiby&QO!=jl9SDnYb^% zoMs)_NZ5kUq*b({c%SPhM$Ej9u{pkmd0yed2HEFxvUZ^)S;>p+ev&~?X>Xtp{Bpp+ zm1mfmUO4Pv2dUxNXlz(dK95f&j>D=jZsjS+8f5~H-F69#sTgW8s+o+TGf=4`2zMDh zg@bA7R8oBclxnS{qE&Up=G!j8wbF^0;dPV#{ilG#xsOpaeF|uOe^ET^eGC)Uu$!&1 zdWe$0jexU02*XLAXxuqZ4B5~^7VVggQFrHH-Rrfu3HWpI%>5$?2 zK^kLpmQLR*Xv#mkseNWNdB3Ba^gITQD7qzuY0OyXkd ztEl^UY4gao0;arqywFd7mD{tsjx&3yjbeqD@w(n8@?m5Y_2kTf)iERg^g2mf(KYl~ z%V9*JDHznbKypkO)0Ys0`T0oX8d8XO-!$y_Ee}D}UFMhAQnD#e3FJk9e112Y#&(#| z=+V_ALZBW6rD>vkTqcb&+>5JfF5;QJP2~N;(HOPwB$Z8`f{T7x&=fygR&9#}_Iho` z37h8^J3mrn8*fYol~6_a+ZIm#thT_vzemEf&!_10x}_K{-a;RJdBD_^tfmr5w@H!x z5Lk#LV)+L%7{2m}jJX$uxO0F@d&Xel#1c^-Z`oyK5BTkv8j;+N-Y{M-5c`179+ z9Mj3cZ!v%^ z;p`HH_8Z@zD!>?)ZgGZ@$vSABX$6CFcVY5mMc%_s6y4O~!CCY(arifqKmN7>o-3QP zF<}jGXGAxQJne+#8A=dxGYNV&8DpP%FiglX#vPIIr2Ww$-p2F>h#dWdi)Nljt0IA` z_(l>x6iN&HtI_O%K2@kS;xS>JKS-Fy;Ko~-SkH;z(~>*PxF|{1Gq)aXThc-MUL<(0 zFn~CJZREagLd)HvU@vI9Il6OMs?mrtYhIIt&VSV8cs=)dq8f8ObRCq7K85?@8hF|D z5~TcFWBwyDhd8#ULAIkVx)rE0%Kzffw@{5e-#d#KJ2s>HO7 z+o)7SEViv_qc1lWV`J$U446L?=iONkg`di(%znYgU+sl^woFE66Hk=h)Ixd_o6+@tbucp6=UBCwqiZ5BVG9^qo2UmrL2)kbW2jUUT9$JN|%73vskrNYHhODEnhz z+I;gv0a&S0PUIzyy5K9HQQ1n7_E$MqUfX=gng5Vp81_#eRLC&6GyPEAxZfA+;p~b z%S?XzokHP%T@CK<1)cGKS_-2lb`v@bI|x z5E!QnJ1Z5aiA*u$VIGV-wkz^q83mTE+=Pz$nnDlNXwV<;L+2@)p}S%m6MbHpgooZH zOrjSAV|EA&GB77XgzK|g@-NXs+=>t(yS=Whb(Bj?eG z^-HPa8-MPJ&Tg8u;tCh7VnqK_)uN-fBns+=6s#ERXL>KxV8huH;M7`mx>e*l z7r2L|NmQccGqa~Ip6e!=&{L&^Ka{4ER~H}So+dA5jEpWe8b&j1 zPfBrhiXZ;{xq#jG&;*C4xZys}vm~TJDB04~Wqq5oAXLyR7rb>YQp;?^dli}>;gAM% zdk?|23>NDqAIG=Vs_2zk3{_=Y@ZKM1T(furc_$C@6UcG=1m!R{#OlG$48QrRvU;7 zZGn-Sv~f@1dpJrS2pTF!^n=gAFWq4HD$EpzN;~Ot?Tcid?PwakK#hHGe1+Z&T#CyY z{EDVKt^)fbA?TzXhEZn5FsCLEz90EpoUT8HKe=@|PC00e^*0m2z{wPXGB03}-6|5{ z|B!wym1Z4B9f0JG7lj_ijnL#4g#4FxxaD-JdHB#4%1%B69Vdi0$Cux9>CWF|v#OWC zcuFS?)mm7-F9B^`WrdzcZ|0}6D@H!mqZ1{4Nx<=8#_6R#+4cMZxiHq7u**7NLf<5a z{FDQ}@(I{K@loN~Kwq>haKXy%PPkyhqOL-3aptT4sO&%~*Y&jw(vOTppOdq(`;;_% zSI&d=N^{|RTp3yA+z&$u7lEIW%Klp<{Ga&_%8wIf2OC)|zuZRTrqyDt&<_<|pF&rE z{{q?VxA5$QTwFUsmrUIOxrIC)3a5L%_0eTtXG4jv2Yh(rh|SlBac|W~1X}|>G2aCG@6LjZC`If* zaaz463r9V+#){bztpAO2xGei0{`Jbn^ObL4lSV4ZY99yc>s^H|^!;!@a1wv|yTI>x z7lFGi(turKMnr;>$*=GgAZ>1DZs}{wdkSa>lSUbcXL-6^cLkhSVhSs_4bp)lwybxA zA$8kvk=#<-12+!rg(;)*Nk+3g`)_9+jM^p&Sqs;JruRr3SNM%us!zlBCxYH?;mfMFU4sHZ;G71io7aH4m20w^|!cw5=rgf1w#fCfG>mUbKVp{lAFk$E!dG z_fV7T6lxY)!Zd3w^8DXF>MlJ<@?53(xR?Kt`gg-H>+Mnuo1nwWwn(yBk}v2wzK)a& zyUC2sDXf123*G-JFe*ubefzbo_>8-7_FTFSve8mleaH!2j}t0?+koBZ_LvbX`9>mj z18_loCsC9hgFfpB7xMI;pz)lA=qK{5N>mWZ$;_c0DT^WG#Cmee`huVftYD*5RLSU+ zujJNg7x?4qfqx#`LBx_|CD@y1U}O`o#U9+iZyYWZFX2FaD+*(;`W2GJ%x? zW1xCRHSKsi9-}&c(Oeq?-1n-R9DZJiT6OE-(0}*I`vp_!jGHG-2W;+Q zy5mOSxB&m6?$`#hIYtLB97`jg?g+gm?HkB#ov~;kua2?zc9I+SuhEq*X23OiLa>o9 zb1-~IF|&Ui)mtBpBGY4wE^x`Trs)RbdPtTKKu_!=PWz!KbhAtFx9<2u>Bjr?zxr%axZi;0seEOQ|EPjX8J746+n`g}L+=#$ zcaJ2TnUt_hE-5gcs#F}rDH|5!o5?zG$#4UalU~oSNKHbEumCjuoCM>Z&A_u$-%-0m zE6~zZ=z6kU&U)w)W{g%eiR`;VZU4R}FXUFhoZLYuUZ(*cTc_eClhLeRfCii9)d!8E zJ-OoXlStaPUU+yon;hCZik2H!!1Nz+^xkp>a4_8k-rr}lQTm?n{_p`fXI0A8KRl0* zq%P2+{3y8PG!|EebYp;yH81MpQ?1sCk%1h!!wHF@!h_`Dnm*;{TAcAha=XLJcaWGq947J1AP|0M9(GnjEL zlE9^@L3VFDnYq-b*s16=cC20uVq3CdOsNOxD@dR^%tyUbo56IcAzQmO5Kgc_4)_nk z#>jo7%x^AccQjywB?~u4`BKY$0pRue8no})2*%_qIlFI*@IfZRoTyFM(Zyo&mRd5y zL-0{gzDfo(>ano$Al1IrN^EwF#jtt)!~H*GujVm`c- z1X(FJylM8DY)y}cRUHifYwcg67OsFVq*^&C#~eJt6~L-tQ@DF>9~P%C#e%Zk>^vDu z7;D>%a}9jJ&7~4%URVxlYImU7iibsBgT?=pi=cd!9_`aq5qukGs8^9DB-%976J2j` z*~Ncw_`(&as3}2_6CE_=d_T_qeFM5h?5M`LYb47e5wsq8<8D!T)XYv4ItEyhI?)qO z?z1I2)08ku(0RYMYC$%CgivRrU=^wDn-> zIxXJLdNQ3f^(Iud<-&}xe*`P^Fsd>h`Px2UPH*H>%U7^(24(m@)z5I_l?r=I_X8*- zx58~fzl**m$~YHSQ!x`8s+@3*-FV)Z2nb*@X5|^;H`zqk!)v3x(R{F$RY9fAg91A_ zx48VBF_`|E&BDVHx>HdHdvkoi@bDQt^JY0~b-)d&)h)0*Y=)cU>$qK~XH(a|lkl0v zJQAV0iR8C=LCMpr#P`N^g3wPZ-nNtZ4Ht2xs|##Bu8XEGC2`=EE#Y&N$?A@`@NlS$ zj@B6oEyW(xV~QwypxzFA?=(;+tLro+#SrC3YQh>VSyrpnhW#MuIf>g@w(P!#pwTMh z&M$JjVs9&b?=Q)AifDk_tOAf95PYS5)x_k`LFjIbg_ogQsMMlyBx>hozJ>QPXvIaOgxBwo_6Ieqx)Oc4Fo)iR$v)eZYLe5%SJ~VthNJQQic5V_l z^h63>E$+dc!7eiHfh(p&TERAtwRm9HQqFv|HKl*eA@Oblv@)TnS-TBI+`gl3#|5IV zuosn`%W!JH404l7Fe_1$^xwV?J>5psLU9@E@0x6<&Xn zu>VeZ!F-G^hKuHhp;G=N-O{uOR*O#Ke=o5`l`#_dN%;e+$u5FPpI^dJ83SHEE|$y~ z^+w3BETtD7aJcsO6>{a282$b90{!9qlP0#Abf-nEw1z2U#LXTx>lDUZw;W3*+ zWTTa!>h~-RW0a|QcOGqX^d^zwr{U6WeRShY==>G^I^poE3$$(VlTQsUzohU4oCEC-hicgH!NA_be9lL83Hi~^< zD!oRN>RKakm70ita>sCD=r6L#fFDiu@`3TS@wi{(q4}o=nM~K8^Mrl6 zl4$Y)v_~c!?jMZ7*!Mli_0$u_i(}N}W8v_lr=)M|Vn##$Aoza~$7cKgNL(z7LGz6` z3BM6!@!(mEQM0FJ13u(cu{AxGQbPNpwvzstwPbKb8mn&nfPQdWj)`O6(~!T4IDXPR zbZhj6FoB~T=W`SH{fnkQ=3L^WO=sXG^~Yqg&|mcR^e`#P+eA*)red1RaW27mA3f5v zo&4#4O|M+B#W}x!F=<;(@!sEN()D#L?Emwfuz|Kk`hhQt+h%S>+o%|PTNZ~EwMVGN z%uCqxLkwLX0M#0M9>p&`Cg~a4Y^2&6$gR6Z!;UZJEVgTsr;CcX6FoOca#JH-m6k=n zQe|AAF@?AedCiZXGy;_m?Zl@e-;vV44J0JZf^zdl@FHQt9QE@iF&--kae+LEzEaG% z@0kwf^$yrS3mKG&Lc?v2AS&FA`pbhjn_U;o%f}AD=iaSkq+|%VFE0U?a6v2SH^kVF z_sFc?7;5ShgE#jaLcW5-HEXJAQoj>^HVYzRe|Jy|Az!(DmEa@msD$;)U*V2aZFcv8 zCfL;Gi_O`~IEM#sh*Q&a=>F^rrxL8_q6kU`53PdCWCyDKiZU`cdD?h=B>OAx5hOR< zqAfvpAYfz!IA{l;%<^=w{&pCqtS*L0X=9mf8sG6;bv3thpa~PcuFJFAWPX~|Pwx$w8 zqxnDEKGD5wBzf)`jmJ{1F->(z@MldZ$v9NQ_y!05 z_R^e?xtLk)#g>JwhvS}BMcX#Kq8&@y$?v_rFgp1=^v!Rhwd&_^{Np&Tc;+JP$SGjL zGs=lkausZIX5q2=OgiVVI8_|*Bb)q6!2dlD)>R+KtI?ixdXgQchMdQXzvuDWa{W~d&S#{zs?dqFG4sYBHW@E;I>-p8>x8Yfm;4{d~Gc;hQdRuef zT=Zb$zvVcA`-5yrDDJ!&4|Zv7C|i-ql|TLfyAt+b*wML=-CzP+R}JFwFj1;~!w}o{ z=R?z*Hd@shiO=^;Ksgp~HbgmKvcdnsJMQmWo*EJXu z=m*1cwbXR`RMK3uhI0!x=98^#^hb7_yxB(`_@ST^UtZF2fZ1jqSZ0*>pa_^cm24W^@qm)88@GB<4g zQ$PzY2>Zdp`{c4y2I(y8BRHhYkFIzP;|C6K3;Ugb16j03&%{~{pa*+Y-pV39DjP*b}H-*$p2__;5m2^j*42aJ$ z#_~TSV9Ar2Xfi{FPuQ7+5e16KtUie^7jNXH4@t2Dcdc>aqB8hiD2Ah5&k|iP1$MPa z4!{x$<2GN$k2V*mSW*(iI%mL6Lov*F69GLNe?r@M6Jq{E;CHGmA=lb8@#&sK@HT3q zqSHNS`G{tSpKB>(S4Oi-hp)huCnIs)_P^YwUTfSRCd)4Q|%yT%t6v^}63#a56EzZpzzz){Rf83jYWBgpvW z_n5Hucf@4Y1G0K)1?idjnAvyYBFI>ok$yQJ(iPP}mn~Hz`Bx>taET{6S}M`?-$yZ0 z&+ilG@;DOveb9WQ-$hVK<%rg;<#7GQRdlnrg-ieDqE$yf<20(5_{+JHkk)B%%yAPA zE#}E>VU9?C9?C3#{)Ze?dW&aT#*p#6CwBQ5Fe4l@n2BeH=-0<-B*-q88ks1-)YoRP z!c&*quKu2!8?%?#jk86=?ThG%7s+(9VX45G9%d$I3upWWACQZELfy+JGYvZ&xK_(n zn*TeWPUv|;U0amk(T^B7@N+uu47|$Z)|{aY{`H{yubWt{JPSFCMc~x$OvZbW&32_0A&k4vHW>HIi{n7sVZep7i|S zJo3P18D5@ULtZz01Cv9iaYS@5%$<9OnpE23%ZYyIaKnK_ktX3qA?CgR{)m-^~K^ji9MS9iwut z9)mAG!1*^aIL;s!x6JT|l~FO|`=L})BjmQaJ*A-gKq!VsTtb!C%EUiHmfh5`ifmk% z1sWPIWRY_#iFx7`h2`=bp%HeFC2U?4L%E7 zVPVo{@KfB2)^BT|q3{zOd*dDb^T(cE{XCkF?P`UCo+C+t=_H~v5J7hyPQnF}fv7*G znojy!K++EC(cZwt7`rwB!gRtQIQk$o?=gWM>jJWDj@@df*7QQE3(_>{|%w9<@|YQia>zu>gqSO|&eDp@Uo%LhTs1FfojKmsASz zZ<83?f?!DewgVDg=;CdVV5;oyVb$A0x=6AR;tNIavgvJnybBmtDu#za18#hq!KbVG z3Z0;GY%MnoHMeD1%YD-HRiY>E6MabHw>ZK(8$)O}ya6&!n(*27F&OXqMXQ1p!F$9V zD3Th3FPBB&LM3ZR(n!bHWqG(i)t*+q?rWOn8l^r)=qT+Yr>*G7)8;#?j=3La!(LkT{4m;YzdD47=k#7bxs-n|ewM z9Sy|r{1+Bg^Y-ES8-C=MeFa{daS8l&;=n!Y7EKBJ1Uj|Bw9LtfQFOb4D>m8Upi3U+ z-{_`$eilQ=u3!w9x&hkuzrrE!E7)~;8uQI?FYkH-oB)A~4Y1z}bm-(XZb_Fk3?gx}BQIhl7i;zV9A&v=YJ1 z85XdWtD~Dw&4yWdQFQZ#NNAoU3RlQ=QkDCXABXg zMlJT3stxE{jl$@rg}g#eJ{GDZLeWV%ut*sVn#K|^PUxl>E4>Zpt~SLh%S}*h*DiF{ zPC%^{#yDofR16thjdTp~R?=}~vf?>Bqz!duyx++FF-RpqAB+>;Nn-t}sc(w|_W9r}i@9NJ0a9ZdyqSS-n2un0H0R0{ld zo@~3!VfWGkrYj{0PDq}jzOh;~{ha~HkuJi(+5gcoQx*8@qNj2Gpf0X>6H1S-cuKvF z_~H6OKeXF*0OYG@<9+ATaO=Aizqu6PR-_k1$R=U$IKY!C8!*mL55rP-kxl;q^e`9q zJKV;53L4C`|9;bTM?;prS4c zE^U_qgI-B~)dyLa6|7{g;@FD^Jm2BmrhG=`(*SYMP6AnCjlbL%kcUQ+pxu82M#uca z;s0u>^rPJv{b>N68fm}P~i1&}IApWDJ zQT<{vB!qk8H`U27(!LtW1RMNk6->S__Jv)-cWMz{&+)s?!1-)l+Cj*{t_*^{UgC+j0+m5z_iWvWZ7k=wR-(GT~EvpuT%|IQO+a``r z2}chsucvv1;&9(GSGY$c>9(z5b1g!e?Zy z#U$F5mXE2gA7QRt7i`cy0)`rL{KoF9KP_<(t!Ft{r7%(~llBYl7E%~+7 zH~l=>`sO0@(&8n#Z+evs$xp>djn{PG?lo>z%Pq1&`yc)MG>_frLh3uEh~QK>4I zW9A$|b2W9;PID*vQ6qWz9>9+47tw>$1P>iCm=pMcoc|RK$JT~&pF`8Q5o-SMN6R0c z?h7Hy-btgj^(~m=>y1`s4AykZuxk$x*tRtTDqAPxOxyQD7u*;EdjF`)=>*sqR>tgj z;*J?vBXMZoHagIsBO?UA zKJg@)6&?Z^;ZCd_dI(0TrttJx6Lp;`0c9`xNXw$zIF#W|ziNFT@=qUrf(MqlC|n-QrwrBKV1|%W1nv3s41kZTzQ&z&5( zq0A-bcSXX<_-AG~ESX$%eQ_dGjL9Pt(zW2Auunhh=Lg3^lDK_bV=>c^L#u1{f%A;z zFkj$_H)>}?r_nBG+^mC3>IZRI%p5k~?ig536o=9?Kj27AE}3;;9Oy0-N{8%Y@L_N> zu@JnpH(Ijsg|RtavD-?xooR4vERs6uL{k3A2OT7@7Ys}IVPn3F{ioc3(;(y_Zpm|jZr!-x_^(Q{THGMCxGQ*#%znTsH zf)2By+Yo#t%a~uS;%JVZ=YHMiSDMnk>~|%J6H%?P8-whEunBQ^)Vbj zF$!Wz^XaoBU7>qv1T7jtz@p#-b!%Bqyxc7@v#Aq*Dz8V8vd84X^eVXZIT~}n-GV75 zPVl2Wh$=2H0j~>}>C=0um@1q-0{j7%7^-2*H-ZNg3h|X*fRI;9#7l~v5OH`sEPQFm zv}BahxocCnl|mlzM^+`4N|eK=g^yqqv4nnyMM7@mB$|%-hTa!Hlht2}VOf|sANyIJ z_b;%-V^4j|$F?kns-8L!KR1v6+H{0Z^jk*XN&A6aOA_boxD6s7_t5%lb#VMuI2gRJ zEuPYOkIuWhnrLnq2fGC(aM$@<9N4`9uP)0Vx9cLv_h~I?*H+FPFB3S?N?(b*h@Ie7 zoyx0j^5f4PW%wnoqS!O243a#H)BMNKU(U*`^Y>>k6!Q$qqnp8dX$-VxX|BtIvCEcmjJx4EB4U3}{paYae?>*b&H^W_GtyS5eV)LZGjE$d+CJqeByVlh0iBnR!TN);bce<;-79q8T}btL(pFox)mL zVKah%vrLk@&b&@iUw`4^F5V_$ljW%HQ-MQ@4MhQ`TWR264Y`z~Mfyih68sR&#ecsy za;M%-rLlS%LM8&CA@Bz+8YqP%VuRxOiul?+pCnH!h50#(RK^G(Zk8M>ztk1Dd4KWC zd1Jcwwj8NU_(I9%8mQAy5j2#)ROfsZ^X#Awb=%-X=Z6&|P(8)}E(C^M^d%zxN;6 zT=|X2x*ep`QoL~4pKRhWbYJjIrBe~FYT7Aq!>pD6BZ`@EXj(spV&n`Mv-mSj9QsI2 z^UIjYCdY9oXe{{tc}r?NRJjKSD7DJ>B)8TSfkkE&s-5{q=K1fVYNm7XkPv+pK@hNo3 zA&2HE?t=cP`*efXQb-nk##N1wz++LfA#L(%T=#L9h8(Iy^{9*JJfWP}1|;L&%TZL( zAfMjST8euI-Qh}84PCZ;0T`?hzAK^gF8k>XGN3#ffoZA5*K!wx^sV8jG6ne!Dp~x88x5S<*O7Mh6?8FT{Jc zK}7enH`%bef>v}LCU4s#Y3}eRYW3qVSd{yLI4w^RYox5#(VN~T`Yb* z8b)2uX~5V0`*6C)Q@Vi)Io5zE2u+*}lip54J1G{lbz>px=70FwqLVs$G|}u~PfS04 zlfHL!!CSY~;6!B(&R+0_R(edpi<3^m!`Os|ayS#Uzw?(m@&@|E6<% zbm&ysacD5&62?7W#Jv(Sn?KKr;-%&3oLHR+7S}sLPQNi@8ka%BbaG(#e=}ii0)=9& zb)+UgotskI%D5NJG-S+W(Avzj)m)BYMm?wv^U}V`-!;M(3rj+ z+Kv;)-y>Y?|6}O91F?F;Fl>fU z2oVycMJcp==Y2jR4e_J2wA0cel@_uxQ)Xl&5ei8}ao*=BN=PLUiH6F`C@qqH=a2p_ zpT2Y6_j&I7y7u2lrz+#UA;=^avTe(W-jT6nfs!BfXy<$6#((Lb>=3f^pb@wDQ5<{f zR(Q81&73^!tZNhAd#oQJ?d4(9`_Ea{doq3;2Cf<@Ci+74*#m+)9m}q5^7s7i9biaLW@QW>~|fw-ab&3ME|_dj z7M422V9c)9WPh&@-ZlzGClfn}`KJil&vYO>!3pb*=8{G0T}jH$R^HtvOI-FX!=4S1 zWP?I1G<9C&-?1w2Y^D;2`Qw?pe@3XWTp0Z*e@Flck}y}bj(VS6g2A`^cy6Qw4H#zy z6=SdA)s-i(?{5xW9rztKgEA^Rl+ddF7`zy3fggq7=F@N1qN*QXMjGNu zJpn|ui&M#wOZaI_H%e_$7Tj&Viu>>IT-14dekeJc>Z>GyuwRixPB(>1r(Nj16XPMc zdOf>eriQKi`i{!B@XpdjHrV6YNf*-(P%%x6>)9hh+(HxKQqoLZkf^}_UDGh$`Xbgj z4bu*@FFaHCB~6#uMEyLDLQV5STz54P%a56GY|SV}#mEjH%vz7%8gnr}$q8wC)S}er z#ezrW`Ec_65cP(!Vk0ZIv*JGy@if(=p+ko zC<(Jf%!$h{4y7$lpkPNnTOvP#rlAEWb&q2@Ch~lNsUzft%xF%pFAAq8O(gz5WsrM% zAC>M0LX}4^ed!v8iDehDa7z=F-f$1?HXr8Jy&b`#`IW_|yr!d$nm>k{?S;+P#|s5} zcEUc;AerhI`@d&6pcZSno@@i70eFD2h64ZO3)6S zhZ|y)vG40wVtdX73Om-5#!cq*UUe!ayJ(Po{|@mxc`<4y9tEvKV?kJ7!03MIB_B+3 z(aW-oceN=n?;d4Ri_%V0xouAL9iNT9S$7egWc}#EF}f%><^^de{79rzE@7BI|E}l@ z!B^vS(Dq;n9W#CmmVf$6``iafTa+?Lo%+fzUbVpPxwSlButLY z!((lCA@fHhnZBx=xR^MQgB~q}wajE+8xhof9cMgB|b7w(JuSGF?`GiIP?as_&P0^`UM51$VB8RvP(emj*M+^K>hnH_?!3yi^g_EO?*ZiiFCN8$WK z*I7Nuo{|Z2miU-I>c&o5fa#Ufp;6}mPG1^I#;ZRfQ{pz@QNuxg?h#v}G;Su`)EG@> zJ&;3#(QQmqTqPA|o)ccA59wF$KsfpG5ZH*?<4DnH*uQ-V-jH7czigxEmGu%hs4d3j z=4xVxlQ^UnNW)9DIRcjmMF3G+j>$#XcTJhx}@ya4f7i@kTd|%$25d*6Bdtd^MVW z!$Mg1F&(l~62QoH3U&XGK*wkM(d^;^W?WeYSd~YSGq3&#`r4KtJ9;m9D-(hZz1le6 zvW3Y?e#J+U2H7~@3}*0DE9+T23P=Aj!5y9%)X~}jsxB&XW%E5Tv2X&s)tZJI@68fQaycK^P>TNc#WqES4zPfBT1Bh)l94_Zjy`7{IKCc6aD8C z42MTn!wZ=nBo2q+(W?m{qWpt4)~z6h)Dd16jOAw<{BHjFMjE=W6&%d_>1*5VB-AyR zbuair3qm*Jx3`LDc+CkvG^7IhIaAfPbjCD5jBLDXK|B&J;a}S_YIq}^b^N!V?F+L+ z$uaAoFyf|M zj>L}U1|GK(50g&vEVviDaxCyhR|HO&Fc&a>2qvWI3YX0n&{Og2V3+?>nDS;Osdwnd zzn}Bra^5K@OACjhIr@C{C6e!SE}_94+v$|h(|GM)1H9k17h9I9a{eQpV0(15aG=nR zI=JV7^R#=6SdcNEzPS`%KNQ95O2L%-8btlVM)2H5Uo6ph0PMmOuuS&=8;DAH^3HY0 zogPhvD|)aj;3->rVL#p!&cTWO@z^8d#}-Almn<<67jE0NkQw{KlpE0A&(&F#(yvu^ z;IQ~Q=9wixQc)LsKmIPLM=LY(6BJ;v)HIlN>>!wqxlH!T83?8JJpkDyvS5|yDwO(W z2AQK?VUDsU{SmhquLwR;+0M<_H1H4mUUBSgp7rdg(uh;_*5QkA4N}v-8TG2;V7Wy& z9lGTR%~y-*=UoR)bM20TP0B%N8f}5EeG1W#A7Op&G$h1tvha~eEB$-nDKNZMG-DhsN;pcze7x7{Ghd_P}YA%T(-^p74WrGSz3V zV84KX+`>~ZKi?64pNJ*>uJ7??;W$nuoOeEQQ{YdWBeul60Hfs#*@TU6sA5Y1pT8-j z-NhLwcdG;&+F#+yxn;P*xg74e>j+0>SP2zx9)bdo$9QnsbE2qm7jL`DaLW%^V(U;3 z9x|_mthv*1hSoIdf+;nLX}s()Hi$vs7vUQiR&kV z|Jh~yd?*_WyBtasKV89)%h8zMAd1;Zld-f+4S$wSCmRc5h^u-!EOYdsdP+7RG(vq2F6R6SunCK7qeSe{H90b z9x=Bst)=T!s$u7*-R!>S38cO&m~`;ZFTjo zwgJpT_x)h8R||r3Dru^7B3Y++pPsv83m2C;f$if@p4s z0%{KUE}C>KOC&qnw2SACw3kGw7^C|ocetmhM!#%s;#sBo(i@ukY zm@hMea;MM4a$hlx`=SY*)lO)ktu7p6d!6p#a&S`2Co(w3221E0vTVQ&l0bo$Rhhv{ zRm7b!s>0l})#Oc8EK%K|h25)z*GGp-b_+(Y353nhS0P!I#T+96aCyg6 z*xs_3lqTijnHSx3yrDH}Ml6L??^ig*a~(hPFo5EN@@P{&8~$|!LAu{$ESgY8s~1ZM zU)`N2RR59Agk0c#DQ~S{Zoe7YxW}UX>NlV`p^$05`h$#dCftPtF+%0(+Qdg%dI=0=ggOnq`xIUOf`IZTZ-+lk2MnYg>N7gvd?aPIEQ**$eB z_-*n!KA(~T|NdOVsKGvRrTaL1m-|J;nQ{29#tP4Vxrod622)H|qZykf3lDs1;AdZ+ zSad7^Qg^Z_9k3c^4NRj)WRyW>_c-X9$N%0`+6X;)gv`CM4jrZ`3a$2dq3rMZw6it| zYh#9Z$9x|RE#+sJesh_@5#H%`aXvi$dm7(8TS_>$QM`+_4)r3>;Ld7g;jfDx5UC_b zUst-)xh5YOcMoZvl_5^VHjTo?2Y!>%a7VHtMh-^pGv(?Ay-9rBdPth*1Z@HKlat#ds)j@W>36umk2&D9~;6Oka zZ1}GXD}$yH`|lHlp$Z0|(wISHdneIVb2G@fgf7-ZTNQT)4MKxb2Yfi`K))sOUXH~+ zXj7{QKH(O0M>wB5-@u=3`SqC;mZtPK(unmzYMRxyD( z{d6Gr7Ht5xCo)32(z7VPJ`s;Eki)(c<@BKp&*^-2hFDpNuq7`{Vbs(S=7GW&5Z>u0 z+Kb-P6Ke19fz|}r@As8FVg_N1O9=XYH~|+X#F6ncb!q;D>2%4eeXw|^A4fW(cYkT_q`ed7 z1l@y%cg9@GuSnAAWx!Q6jt46~ude9X0S=M!!pfP>)Jb1J?Z)#xMfM&lvlGIHwUOR$I;L7zu_7gS4~V-+UjvPq-KKELgB*FE0n)fx%9>GIIyc$}sc zPe%Vu(X?y@!PYb-p>k~nzCYiK`8EHD+2cA0HmgCGIxU=&@PYq%9We1)06rhoClbXW z(7EU7Su*{j16Ii5>KpAP+}MI$=o69s+BgqQ;iTsNZ=B z&;B*#4%DV&^XNF3NQ0@F+X5VP+>PhgUZmxRba3EWJj+_HL#LQsjGA}?nw%8}`*)VO zhD`;(mc3woL>;HIC8&|n2OZ-+(Ac^>u!%X3F-kXZ(E@ESUp0}$b!%{MXFfm+Sw$|W zv5tEFQxKXdi4y8~i8k67;pjjaPDc6wwmz`~uN#}ez405Je_f5(POG6$HLPKO?H@GE zZ9w_Ohj5Bo49)9P5gtz+!NdSlR(V<>9@lvUS67AO2LHEExqXm$x{nrSjp13QCfT%W zzN;CyGp(0kX)}{@Y6kk}B(Mi>rqcf& zO(6wxx;(e7!?EKeg`uGH9UXeuAZg&hA(#Dvw zOWG*MVE!q3_Jl@O8rrrF!yU$1yC5FGTOIicbpg~a;(Vom_)5EXP;02}hKgCI~@m?|U861WCgOpH2nrHs~wI&~2LUHzp zs6hFy49=)BU{nmop>J&faXP@iSMmdijeRolh>C`mm}=ZSJR5f{9VC8g%JBT`5K&3C z;4`sBv?1piu58jMd44_=p);Nq9vcHkK0&g$uZEY6VrJ--w9%1ZWGq4zucbN9H{*=4?eJ zsEoZwHuBuDkjhNfcuWB3sQKYbw_O-?@jH2_5`cAfiPYtX0RHUEr#EyifY;=E$b{df zetnyGUyTE1X_SN8*;cw|+y|WaYCK+cR2SNt9DxVF`%qbJCVR0t7kBFO_u0M|kVq&P zuhHUC1YbcU?+w(yQUdK*{xel8LgT`NBqdW6QlIVyzy11TP<1T0H|WCMD+>6ZOFNVG zA(V(ZMS%IU1juq#;(ir1k?CLFlH8PLdM#=h34T!l&pdw9S&;$w_MfQGJ>CpT$IhiM zG(?5{i}!%zmzi*=^BJrC#|s0NOOn6@2jSI>V({BiPlsJhXx$?m}k?)AX)DK8h^+UeAxdEKb+BpTpfA* zq?<@ek}7Dg=nHb{wmmxDJ`JK)Ex1BM9?eGri1Q2;cKug9Y=64~ZkVJ(-5O7-bWei# z_(usY2SsAuj4`kd;9j|7g5+pK`Yx5Xods{|yRwewMRKw@d!btTgGdOzv1X})>4HM4< zAsUP0)u(bongz^zOz;baDwMnIr<~ z*3H}*Is_NWG=aSlNT0uc4);o8u_Zo^Ec)k10tVG_eJ2MoX1B?uDkpYeUKshn=M2mIB=i$Q6J0$S&bFA5T6?!7$VG4;MB77(3^3ZK~8uJ?ugpcQ#i#^zvvJm~htpvX- zV>w4h2F2^I2#z}cM2ocw!tZy~g>QXU@cv#!;ns_f(PTqB!rDUer8@*qt!e?ga1oqU zAYclwJ~SN_CyW2x@#k~x`M7mMGD+^-3F+!{!03`Kp1;}zr{+zB={DNl(@GL`Tn6SFO@Zurd=@YzyCk*88OM=4JM*l2Sq=KrWIl28UJbEY z+CsCJM(okUnzR-~@bTIIfbEZ_l{>bevK*hsO$cNJYQgxxV-r3&Hk}H`i^F+-rmn0p z8ONsi(Z_dOTL zf};`eqID;>|KRUhV)cAS)R0LPFfgO{It({a-Z!%q(ojHayZywkfy0SK5 z+Msly5~uB{rH;#T;N_}tRz<4_m6oo7=(K03d+;`F3wy=PHmrs};~(MgZL)k`!4G!) z=I4FR0}#afy}eu+@EWhmS&ix-N%m)1;e}Rws%($GGOyXEeAaaKj8Yh}GJ%`t9%28f zNcbW7no4+IrcZ+nA@IQ@uCnMAiMq$TQ9|{x;qH3OsA;C%u1e@-a2QTCID(E<6s_OU z4qt*Cxu>3%RQ&ijk_K{k^Jg^U-0Y357AuIsgtJV~6pLmLo3y_Xa%Ux;ttMlee+ z*3s9)yn9P*4{6S;B5B8O!>Fws&w{YQn3sKIi&h$Ytb%6{rUk($kIU5NX}u4fP}E9(ta1Ricj91|l>}iI z)7URdW>J$64?LDSNM_GVEjhN85bsUD$@T7!Fe&UkzED4nW4Eov|APFJ{R1*W)Uh-O3j4hqhhCTfI*=7G;$O>qI2dR0mwQU{lDo#N>7EgUG zy6H@fIHvyIGwAJ$VI(ySi1z7l+&4}MDym~X{tPgXO3 zS9!tArg}2JRuq&b#X|mQ3wYJ@gm!%p$Da8awCU3|h?{&Fe3yTM^IZsu_5F~O7)C^= zoFbpHOG`>lxxhYMZ~O`~pmI2wytTYfCo$^8q5UPh`}HpZcS0e5OA5`{vKSHva_HB( zjd;Lu4m!>^AqNdN6W5-@G}Jy2Gd_xvn%Uo(4QUzl;s+hfTc=O$-rA6l;ywuPU18L$ zizr!HK{~Glf$rWw0SWz|1I!85ci6iBm#9wR!ZkiKM zy9=|KA5l?Y9+?LB65r#|jBq};??=?jpDkL|X$%8NT~O_Ohz|ACgM|KBD7wmXU0jNx z;KdXu+9yMgwdBxIp<{$2N~VI;*0-qZvIqi;2f#$ZfHwM+5U1|XRJb;e$hkWUc8&=H zo!Vo}1bK1p_%ePE{&FhVim}+Z(FZ10oWMG!icI{lfeQ)U1Gmb&!K!zJH5pq2R}9`2*+=Xq$VHULLFqn;J`*W zAEE_f_2PK4)EqkelJQkS8B~;ig(ZgWU_OIo%zgf#T04W<3jT1tw2*E4B#*n#k3t#s zP(0}}5uD3&A?D?D&OSsN?mS(>EE*k(TkG{P%E1wYx?^eM_dINi?)YUoYO$7_eQLa zm^3VxODArNHpA_eS0FHWC9K>21FLRUkn)}df{7iEAZODUVaTj(HeOi@Uu>k{klD>z zb)?{6;2FBMOqA1~a~)UJNfNWCx4~kXD%bk$DS47<1nm>H(;fC`&*}IcI0Jmil%15oD@!xKu z^N(RnT4uz3dshHA<3`AjjeBvS@m>(h{Dti`TTluQVp9i&r4ReSYj{4h#UA0(+a)yf zp`vjAA3bayIRXBuZP@JC!2Fo@o&^2q!L(gAV7Yo6ZQZ;WeD&X9z(73sC{z+>CrP1} zD@(Gx;vrfw5)M3PiOluSWOlSR^hPg3huJsD##8Ii`R{wObcVR_uY)b5T3^Stm`WWh z&I(`Ua3mq12&`fTV3oW((gqWK$#7_%3Gh$EXQ49;?70f4=-xX^hP|Pp~;56dCh# z7;N|v&ke7lrCYCIv9B{U=(oTrEiX7yHwSee){`{33v5k_Btt4;VsyO7e&ZSbOdF0~s>q<&kZxw9UPxZUO@UcTnWm;~I# z&;DECR*EO2+uFf`+DUMWRp7+GW{_iw<+R(t7c{D*xoqwCG<9|iUb0lDzUpF_Q4~M~ z<~&>Ww;js$7Bkk5eA(+Odm#3B7#6JUr}n=a;K`m(G^lAY%=XG7U2pbdRk;b3loP-_ zHzz!+BZ?#>fDFzlWgV+8F;?!CP0(~l%=YNs0(Z2=FxcpTn8L<>)afSyRX=>k}YZ7ZhZ zlNAOim^y*?;2A*Hc5fJ1v4D)laM=8_5J$w9qGsJ^_PpC=@~egt^Aod~wkcsa&G;hi z)4NYQZ)(A}I4A7(tH!X|2K>(L5)K!~faNhEWVJZrsZly0dTS$H^6?sJ2>nD)>zi`B zf+fiu(;YD9vncC!_XXV|m1f>S6HBFhWQBjFr$AL_DBCc-hzXCgrRmX1T#fc@7?^ehOV>;zrJ3X4zv~al#P7$+ zt#xzpzE3?^Yj@L#eU6xucNQ!9b#c;FNp5BKdy?DL%~V-kBCgX_!QjmTGAgx_ObHnS ztwr8+N7q^~$YZeM@m*9a$igeNdZgA+g4tbn7flcUpbw5Nr+e;CfN7TnsIuocnak%^ z8qTmJ&pruoRvVL){)NtHGlCBh-SiSaGu@gg!gHekK-}v|cyuJ5bXV`AvxBaab-$Ld z(Jj^R#o!XrU;CS0JMxjHpF2q0jK<-|e`#=Mvk+tzp5d9HJp5378$T|U5_Y$J#}yrq zF_7oabv;T130ZMrn9l&^taOD+zUz7K!&@?%*~=VEoQc;b)d?=i@|^jiNgx>>jI%#W zV%b+2+-7nPK7Y<9XO0=*jlOJB^KdlLS=yg-d!U>7Rcz6Q-`*LXB3N3%9qLc;b>wP5JAku523X|6RM`Mu)OaSr}|$L z+0-)~o46WczWprJ__knHmOp$uCxJE!dFaivv}N~X(|!3z;rE?zU}vsGm&&W?>J<%^ z*ZO&?6HI= zfdLq?eI88vQ%qj^yk{1cDhY$WwK3*?nz(&ouAsJD3zp5F2RHByf5(;tF&l449rsnR zz)KN`_&Q1a(BezCV6XV&m^_epyd=TeEq}m|(x(~>2 zmf)V6M-mO!1ga!2$90&F7Ve0Q1he>BWOv^Lu|_*y-gk#icw~%k_Vizm+C`dre%XBbcH87pM0pMkr} zKk}w1j7*cNC5yg=L$Ann>|84*ymoesaLVn&uu3ZzGIZl$-Qz_NpD;+m>py_RNd*%9 zWTG&}fJNh7{`Bl?Bjo+LVb`TxrKKPFACoj(Dz^YU+ z;h7u_4Bb3HBC2B02Jhp!>Q!i3Z$LfEG)Yu0&vSDLyeAa-0#Yld36w(lEX%MvHk}jT zm2b*I!&Ck6LT5MRSe7wf*6OIrT_Lt(oyoiE%|iFqPc$t%2=**`2lD%-gWUoR;l#on zaOIb%P;VTK@uh~8@^MNnqsuaMyojSsf?q_7p zsmE08VF^rs@SYeSQWWm-TE?7TB`RFpn+LXY!f4s&aycqY&eFHCv; zQDD;%K(_DlWRs%C(7Bt1G|X`!y_?N*@{;7a2~0ofd$AZjY+6ZNttkC(+p0ES9O!08fy(n8^vU{4GRLchh8}oBb)3xT z*E4r1=Nkv__njxHhjOUZj7-o_aDk0iuaI{?4TLF;TJ+smUm}??2P4~-5f`G2#bbLZ zR91p#av3U=hvEVLxo3UJ4diz7UhQ8~@k6E_Df;dT&z78LT)yw4mbeX!J;%bv|9t3k z8xipTJb@|s(N8O7meZNK@9D3~d!ReA8&}yWQXQ{xcvR*qi5q>N__psQ%jZnOmB;Us zc7qf)K+s7%-a4a8d?%VweU!BcVK%oWzy>2RniXV0iytX~Tt*yNndH(@j-I5^*94ci z^Y-4_fAw0CtZZOTUL}5o2ue0-dWv}XazpUZZILI zvq+0(3BC5Ckrqy0$9>PfMlv*2@rech-H$KA!sBCzYx+2{tK=ZPxl^6Xl?x)@GY>OP zGec0OO$g~>=ZV6JK+tqsg=@+iV2h&y7>?VF2knO;P_zh6%g=+6kYDuA(L78@=tt#y zB0^o8J>a3(%1r81g$ZYpFec$K=!|QEOOu)fE%%5veigLpB zAAjK2E4jjZ!_o9^&n0p*xr0<(DrQfPS&o*=#&h*|CUE;-ybvhvON0CVj@-vTEre$1 z;J;2O;hLQBcxZk#DZ6-u+Q?M1ww2P{vY)(rw`&rJkDh`RvlCgH9|>@|<~!MyU&`p8 zkc9QRzBFx~52$VTfY||O$(Md7l0N4EtlyD^D^w9Y6W)=*kIA@v;c^IakHQOupINC| zXHZYBr=dH&aL`GoOr6OcHv30U z>x_jl6KbG%p+625uH&55l0oN-6}^)?2Co#{#``0+pyDzg=H&2Q*J2@hA9u#xX6NAJ zJeIZ{GXWFHyYRM83I257t<5Ve`jLn zc%$FOiJZCZDqO^G?!z{o1vH9ja&E^v~p!Q|7@Q26B;KNB&-Vd-RSNC<`beSB}k{UEK~6^u3J?PM{}QrecbK| zksjKcflC?!=>b8V=qO$J046BxBf;`WT4` zm6V!LqP2}2H{g5q$}&9Tz8$|-{R30|B9X**NbhUK`Zl^!v8Z+bg@s z<)O{o+psvCDY66Swtc2Ix7l!CDq9)-I$2uinTk5HGvL6{de$Pk04p;zdS zKd(FDpz29%EuGHk35wxl_Z)6v*<4s=@QZ$mJx^|pwZxN(&1Bnyi744@2=1xVAW1!p zzHZ`~7l-vBSV4q4keNYO=yYJ<8lDmFbrw86yW!)X9DP1h5;HD%!CfaQxMaT;&(s9d z#sq&X!FcphuoJdquBOue;vwuQ--oDB6-GAkev6AOkmYa^l^pGvJ$w1Sjj1}mPz@uq z#7a<+znkjWr=ZJs6TBhWh!5|-RZl>viXH;Yo&--rL%19sEic^)m@x_ki03n{l?%D{kH~r4k zp4LZ^83*XRkA75PjT`GywFb7AUxAz$j`q(gWEY>x#q&2#@r+L?nE2%nDY;u%;+IrP zkBz#Ful7#iHi#9In2bmgH8lddo=IX;>3*6UDhcDlTIswlelIBIPZN9_$kzR>^x=MG zrpKxlepZfRMMYFltLg*1oUByRvNa6qk96SV_fZfR62~s>6k|JL4M|%0JSbmu26Db{ zB+G_8Y1f2&cFQVzn)LH7&Crab@!VDFZrKP%@KsH?A&%O7r7s2_NSIFvtpEPA+EqOC@ z0az=`aF_U#LPv-Xqz|SKByuK=)No>lcnbv_M)5#PE%Fn|Jxd!T_%fOv% ze*ZVE1h*{oMtx6deEoV6pChk=+u9;Rwni3@HOH`*6a(;BnJLV*PC#w}pSKB|fGIIR zTJ&|X!pegrv(A3|!do z44vmkQ)3e~yv*;7lwA0CZ`&P`%XipwR!G8hzf5-Aq)f1f&;0l27W2GM7hRfl1P01G z=zA+SdTdiQU2bEBbEUq(p}6^^CY-Vxw;Tum*Sd7YG+CIxS`(#>Msc)eGv)~_u`4Z` zsvl|*+bD>t?INV2G79YpY zYQyx-$}{W({z0j;{ya&y`-fNexieeJV_+!0lew};fEVsmlf&OG;{09iOwOAK+S#;o z(dM2C%A+{2z9131bP8}_`%U8K9tXSb>T=Q5`KT&81C@3r!Ecd;WJ6yBIab-sn(fgP zUd)RiN6wZK$+_p~5_U6;d7}Z+T5IUp?mRTrG~}JdG1N_i@BkE!PAyu++=O18p5XWeCF!IShDs{AsK}` zLHnvGm)UrX+&e8n8?)3=UL1ZxD^xUbtJFVSs{WR)bB(0I zuHWgwXy>a z=#CMZDcZwG%1rwAp)wre_pB?_GO3;gFYrv846lQtNGr_ZQd7g=zLOL;!e{yp%zH}C z*v4UR))0d~XER^N=E4Z?u#fQ=2c~at(G^w;VAtL!R5b7&s@1>4L#;wk{dpG6|INVq zN4jWn&j$*6=5Xs;KcdE12lSEKgLkjD!qdM#xTUR?1S&0n9SMFoDd8Mlv^Na3&69DB zvnp73wUDV7u27kT$Napqi8}ZlAfFB1l3V5kTitkvy6y^2s;r0~s{5Mtp!k+XUdLp^{q}6dg55JgMrjl(=pN3=DiHE;B}DX}z?Gk(K`^fju`UA(N2_uVZY{;{g`Ze0&CTR1 z-)EW7vw#~pw~egU`-jiQmSb?R5UkuUg4WMuoV?VAo1tsY`SlLr#>gW2+Ncr-oK<)? z&s?tXO&U(hod}PQO+h!o%;E=?9`MEVJsO2d(gfXh)>7ONpX>LcL(W=SlsX0H8nuJn z$-UIkLxBsi(qR-s8Pq-ghW+aP6jRJ%G4%_x>;b9XttxMYYTDY+CD`kd(WnJnFI>ShH^N|6 z%~~4%Fay^$UZM;B{fAjk%s^t10>&E3;F1YKI_{?{=Ga|DPZEiPD#5Vu_z8?w+>YvZ zgXxbghR|U>i7xB9z~|0xK*Tyr8p)OmE-i>6v3jyN){-Or|76hK^bd1);!PT7F^W5U z`4;?Y8z$9%9>8E2GS9R=P|4bj7`2V(X8+w*l01vWXTMhvsYr3yzBnG3=_z#eDsR+Q zQUHVVH`yrH3bNhSmdW`a)!U1y!eT94lPSY9%HJ3FtnZ;8i(+Z&{ZX*t(k&G0@5Qx; z?MbxOce>mt5ANO-W6VZ;fTYiZxONsl$={%6@!ibyH?ssoCoYhOWA;-SS2qY*}GI75f`9ax@#@l8hLo0<>(FvFd$6PHBIZ71-XN)zsG z`^fj2e-fD>CAy~I6VY9}9aVmAf!>u)?3vaJd}k_{nYAv5WMvtHwxmDWOy)h?dOl=> z`${HkPBlHBMqy@M4D%;&3BBx|NwVVJL-yNfLf__N`uja3claS{$CQv?7H#Mqs0M2S zMiIe-VK&C100utllVkb~u;=+*RyJuV(N;0SCI8N_PfrA5to0EbXQV-T-5S}2vVg7^ z{c-eN6?pg3gJ_69rQZ_F>B+3^Q0i|>pRbvUMq#CBxGVyfM7!WK=eM+X#dY$gRT-@f z!{{f&Orl*qjxpqR_os{ZBw|?-VA`3$pU*{w2vi;#?xY7e_dmmw6 z!8sgWC5NqVu3&_Vk3hE}BOTvb(6UW;k`XMpQjB5iYzD@b36GbaLfB9P$dI4L9FG zi1B>tJywtUUaW&JMe)S#b|`tpZ``R_AW?dpjvLgHh``|x{P$oN{MePnep`PT&QB8) zZf#$LE%F?dYKkSD&pdFV+;T?b>osb(D1)3Fbio*zIozZQp2OdD114?vW2Ek#!4h`{ zRP)lYdK%Bx+!8|f0dfCLXdGZnQ~u?Xa(!utanTm0M+nIg(}{GEZ5eE~mxU@g3wE+Y zG{rTZyit{f9l?BsIE{sE>pbhjc@-%X~mk z)GPt1lBXHh8)>9q_A9Jxm!lKr_K}4C3$#V+0Qe0Xqjlov5|f{nM0HUzzAW$oH={}- z`7NAA-0LK+sKG5${N~Mb_sbID**y*F z7w>?_Y8agPi=UBC6(Li9^ZDlN$zcCfkEFZDLafjXwC7PesV^S`Z2pqldV%DtS{P9n zYfk*ONMdiFA`Nxcg3OO6pqM;k-bPIz4_eC6sDj^j%$O$h>9|kRw>86gSskh)a+Msj zRu-;vzDp+g8*uTwf8p8FMHoEi2%P5Ufn#*saK=bDdi&}M-FA!trzz`kO`5|;hgsV)|$!xTqw4Jlh@Sryps!A|xBk8)MhD^E$T53phW?n|Z{3#c4rTivx z<5C@-J!J{RV<)+uk&4RMR?xb3wXmypl5oS>Y8-D6g0n_Opv1@v)7sVWxc^iPS(eNi zRmHOkzpoR6Lx*8=UM9GvKOk<8MhV;B7GmElFK`SkA=eL|6~t=S;{%5lyt^y?e~Qk- zAItBJ<7H+fGeU?;vO*HieXfU!C>bRw8nm=YMTzWDGO}q%LMqD0y3h4QODZY8DM=_r zQM9!C-M_!#d0zK9=ejl-47e#8~%e3A^7n(yK8 z<}q|u$bcbb9{-4&2G(Xv32w?okY|76=*EK0D9_yiZYfFQ=k*TY?l(*gleEx%*)vk` z?-Ud+73HUle}SuqLt*oV2Vmg?U_P0fnY-;E^E*_)K_-K7mhd2JL{`!5E-x`b!U1kv z|G~62C_t6^v3mZ7Y*LW1o`hMga8r9Ugh@Zy9NxHKcY@H`WY;(&k&#!Vrxp%YB+p-OG7Y@?xDPM`dQW?B; zdyC&}tl^$kMO{|x5Na+_guNp#87H$>RHvEiV!FhG`X^8R!-Y{~Nvu8D`QRm$4^D#x z|2YxKOLeq%SON^y?C6)fwdAbzZX7=lOxJJhq8oL-lGa&{C|N2EvW{{nPL((YJqYgI z4M8Jb9{apC1z%t4pcandQ0AJ;Zk#BB6(W9E&=?7a<=4@rkFF8Fs3eLyPe_Yo50~*J z^o>$7U3fU5-hPWV>5o~-&hX+muH!T6eeE+@{lc^K!hw{}uWwvLykREfk4InqNW*Fi&g-_&4R0L|LihdgD3tw#l2w-c#kbXo^NPZAv zgM$y@B{>t67nddZIX58OHlGBoKaEc(Nbxt_6v7-Y0M@mGuJdfevKlRvTkHdgbMMjU z3G)0rZVx_PqX!SzN8;ul2WDDFG9>pKkuRoQ7-SYoc1pxTlZ64_(&HEoyqwIBc^pck z6@uuf+dT5?Oh5UutA>=l@}Ydy7j(@MEr`?7#DQheRI6qgWdHL7zu^b8@!2%k6QBe4 zm(2(ML{9;8X)P4}bm1(=>0p*$KwJ8XP{&IYuGMqQ<&-dNI=7Ua!1;eRGpD?ekWCcH@5OZ?i(A^%GTu1sO^W_DD)^kNejTxV~kx;z#n z6XS_l#xtVHu|e~?`sq8#L!h`U9TYy?BpU=%z^YdVZMbviz5grP!UYUt8Gu!FqAqz5d&K>5L-wS5;rf)F* zRw-7zd}p<1u7L2@*`wg4FaD4>gKI05_=a6Iq+&)S0kb_A+n^5LCmBP=rBa+&_>uEv zlwiBcXWGzckAC`gP;cJ=ZgS3o)ebW-po+!ocXeQTupG5nB8EpUUcx^*-^d{a3G@}3 z!q;dQ7I^GX;fF-7qjw@~;X{N0t`+6(Mx*I8bml{PD&HQwqqFhIH)V44;SlG{reqwN z@wFrhh{gLf*cEe}93n1g-&%!AAwh84e-In#K2K?ZPu+>|GO-fwC}`5+!%jrB)q@(E#SmrBi6GG@3j9QU_I&6w`ln|f?Y$>Y zcU#Glp5|?I-_7HKh{TVmdZZLw-rphm+YobqOX4WE2k@s#pe0y}g2Xu(XI~DB(i5rq zx-oF{>|-l6)C4B$Vj(I19UU9L0PF+_^vB^;P!5`pV{rlabJZj2@KHzLcQu9av8aL9 z7B(d9&r4c%M-k@B%7RtDFU=b~1Xn2^);_YP&$v0y$Vy*u9Y2n2o^*^(H#knmIUhq< z_L(I2KV)4hj*;AypHN?wLu&_$sK|<`c)41S_y*Y`5gCWWXMe#*lX-B~r4$xCZD(u0 z7g|2vtIiiBjgXizjvf3;jecoUq{okVQ2w7o#5Jb@-kf@Znzx6bcz!-Tl%Pe21DflC zUACiF^<>zkqXcDIZm?0+O?}5)hA&3Xc(S6 zSV8!os_?+dByja{J9n+cIo5xv6;3Ogg@$H+bl%QX8lO0WPlO7|l=JEE$Jvc!ubxX3 zBBnxPUlbW@KEzbVa&P|RI*wyj#dQU3khgswn>Ad8?H*+~A@523+V`I1gl7Sr61k5~ zKcR@n~xXbNfPjwmrB_5 zSsqMGJ4j}~7JPY|h`0S_;$T%8slI&-o&MvvW;6B*x>HXgn|=(s3Ii}F;1?-u8>Omm zO2PVWJoh%^SZ}&@@H~`(ZKVtOeSQ`6(*sFlE`_0Rk^{$?xI}F8RmeV-J~W%03GyS& zIQHW-xY>SVM&1-4*fp_QJObm5h2XGiD4aMDiyM#SL2-RMiPLc>_99mRK3;@t?iX9=pIwja+Z*)Z<_fNhQBC*i@1@;Go}Mvk_#{$UO{Et)_m(IhgD_7ro>XQOw_) z%&+^1KNbzbkj*(rcos|i6OM8U+!mgfv^TA`muu`w^=#E2NE8|RLPLtfS2Y|tia@4JHab1(S*G#U>}zrx#jzo^2-4QOpI z4}%;(#cXCNWV!?pK|&%DbE&ML#)tP*#>1z+ zD{BP&Rj$x$ew=e-wkyu~{e~{kvjaioLvp&!7rno22L1J_#N%Z<7Mq@f?p;YRqiuWr za<7xraOpjmBVWh}HJX4}h?wB}_?e_(!41-5|B7Y|`oWLH?R0qF4pe_%!LBGh4f`F8 z;qEKWt@_E4&My2(ws_xV|C^9T`g9NA=ZceT#KBqkF2@#ylV6}}#$#glK^=py3k%MN zMuF^qSx~V1JTE%@4V#>%2Q!t;;aZ%fe7(*D1Bgy*Vc32ec4y|C{RYQ`}sB8AZ`JZ18d>j z@r5*Leg`SNq{^|_IWCa+5?G+o%PUD74~G{$r~ITFbWzVFI1yjY%s6qMxgL}VwuhY| z%IhiENoSHm8C4pWc^o(EXhPVA8)$Op3zg6)g*S1QOy7Oi0Z{i>A1GvXFl*}8wN{&XC!mi9%Jd`yL%WDW< zGcTN!Nd`jPStYDHvXE|WFNEP^$7t&2Aatudghh!NSaiD<_<_>}3%{=v@E;GO??ppg zQd|MX`e*3-NO_!2cESnIW8m8#Ktru0VDF0eaMmN9dA~Fs`3@XArM{7P9f*X*n?FLI zN)-){>LimF{Dm&=-25Eoz;|E0n|cQ=reaGT)6<7;@qX#fW6K3fB*b$f|Lk%zzRcTr z-q{29n1DYSD7U+iewIrlJZ>la+CLRif+Wbpneu#(l9QO}9!RIpeMxyjtB7dk5Nl%) z1EZA>=*a#FB(YcsyyHCti(ICYaMwU0q#zBktPbD+s=Bn8aV& zFh*^371+LugH+Ne7f+||C0d6gXo#$cpvvF^nS9m<&yBXTrPI7Ha#(|`-V*@}jy#7~ z2XpxcJ#1l>YK1(Opl(|c*!yOp3sGg3&EZIrGGk)LX4XwU2i`^Go#)!PJ4^7 z>Gx?S%YOz_$EI>z<5pby%?@=F)WPiQH<}_yL;h?^cOCyre$BgrXFVsuo0?d7I(!rY zo-QCxrvuQ%U^P(JTcG!k!Ir8tRMR_(?&V)u&t0N)vuFZ_2B8cFIt zO+nA;|CqbqSONQ41h(3jF|8sB{J>M6V8p5fB4!y8S$7uK=O2W3%A2YE6c_N^t_Dlx z#uH+YU*Gm;4H@~I1@IsqbW2ZSt9L!qGddZf8q6>{FC6!A#-MX60!e0gJqXq&ko1HU zEOx$3YIKYs{e22Fy6CabD>s7O)^I#Fa2s>|Cy@y8SokGx0%v&pY2x8XxW8>Jybf)` zSEt|O@v1l?D)#~sE(l{zolT;}z0%~SbvTaRvx5Cb z1mfRwZ{p4=IQ;n;Qx4a`xO14z`mzeFoLgDF0!7*xKA%;(zn__XKpw_6twCq2#$>>G`#^TB1;Comry&N5~8zOd@ZLs;%nLw3wFA&(nRTiu$plWYr6VHdlI*87Ic zA^mS8u*^_EJ}JAwxQ-`qO-c;DP4A=Xk!PTo;{YYAjey*ZMI`m2Ds=XopcVz%Bz6Or zLA#g(JG$htBVwT-L0b`Uj{NL}XHiW!I z?SP$3f36VR_*n>!9;0wkMi|_#t-y<~!ohI99Gq|IVGq5^gLE!Gu!+k-S&2NTA6K1E z7Bq4G&!7`vDf^6@0lcB*Njz+e_Xqc}c~E@4mvoMMK@KgBB(9HtkT^FH{u$34G+1bW zxkZ)`Q7$3awo96yn`urw4z)n}p&2Cl?Q8m1OPhW3{5z6!e#`|wMUu20VCb(Z$lms% z!r4OPhhh^ce95uOLK~oa`D9pZf0yOIwWYFgn|NE_?u9*HztYjU1@vm;ciQ}AJcOIC zv=OvuY&1{=y zCy)mkZRAGSJt|VRjN~LE)iF8DiYzUrf!+&Xl0-L!U~!t?pACzg!@=(P4Q|Kcf%?%C z&>+DH#`-&<%wq~275)#8Tu6iBYmStdYtTm<2G}DU8TZZ%b}aMLhO)2#@wtIzMTQ{!KoH#$Y2zFRvg?91uLs zYz6hVdxG72C@Ah!9QjpBv{2UK@&n8NcO)8^h2i@nigMW zTh_Kxm2_!*b6gkpwMAgw@f{?(^f<_!8Kf}_Z0QpJ6@njSv#G8?75ZC}QO!9HwrLCV zgRTX_+(#Lp+p?d3Z>l&F<9T??>NhTbcpM$|yx?21FgNJ##7Oh~@MlnlYPC;=r&Cj* zKG_x)R4IbZ6f2B5oj{EbR6vRPXL$Q{GI^+d36&3z;DyFhkegwK!?T5;!oCzvo>+%w z596u)oB`^%N3uTnw>H>+s=!IU(wO&|V~5wK!6{EmD!!51mzJx+v@d^&W?&R^N~4kq zYpf$xT<*0nxq&K*+(7FMFIltkg>cmLIDXA_Cg0kAFl~jY(5Z5St~$rPRqpD6A5DXO z%|O2NMv@sWazs({6?A!9kr$5Vh>NO{pymgc5#{FKQiFC-_sJjsTiFY}>IGKKD--D3 z{?BxoYY>VW3*$Dq0Q%wk6oCh~yL%9#B``a(iJLjdz}G1iv?(YRPbY~BB*j$tzno2A z?!ydXFr$y`mq~;Wj)SrIs0rlRU!*dh{V_;0iF|o8f!|zI36CNt@DGN?;6U5~67rhM zv&U4UeI=Kj*dWd~6-z}Z))DNFIgY+3VySEAImS;_9Agzv5#vRj@bkeJY9z=4U+xXN zJ~k607a0p4*EHdS(L8Wlbq7Zui!x2MuJn&+w#kwzbx}~9z zD*l)Z4Tgb(#eJG28i;;nhLAJJeMsV5$g0ZssPIz^*3@T%k5>$)|5K$mxqQzo{sdZ3 zGypobf5GhZSrQj>8;flw)i1du$q!K0#*mN3f;o;t;Iv^U-gq7cFJ(MntLY;UHRsOy z0uA^vS_+Q(m!N;26sUdn<|PhA@TB)8qK;W4{1X?;}7$VF-hB3uEYy2U2+8ZKYI)}geikiTvdnK;XKzO-mE`cABOhj`U z-LhMeYHz&8uGAf**=zW$q{a$Nt9pQygGOX^cQx|HjgZmkwT%AMrNql(GrdCfh)Mnj zD%YIAN_}<1WwUgwRA!EYz?pscJLn)$B2COM1ukzrM;D~M%P{y@HvLilo@Faai1A=f%*`)`Vm?W-=oKhSK~|f6RiDXK0MGAr#%6dbmuUD zpWZrb6TND+V99IhQO9JVRQ)C)Qa@Z-gt`bFuNgHMH*C07vc>qLE1{9TJv8 zbF3mO?`*`pbt@@8y-S0WBuNX`hY^{T!+Sk{72K8Kyk)U-a6^$b>Oa+iWo7rssYey? zSwtBYJeY#|@y6uHt9X3!pd4j>y@8F3JYnQsIP+oi3H;E+vP$G7owMOQW3tng3}4KI z=N;SOsbL36>s-a<0Xg66tIMSCz5;zA8btrb>cBO}O6*ZRMSrM7(ArZ@c%$L~-Szh^ zv*vsh@!m8aryV#?g(_|kiHHDt@ZD;fI8;vsQhS+awGYUf?5iZ>fhqk^`-Hvn_cU3) z-2-Q)-ofCfv8-37B&Fw8f`Q%)JleDh*9|)1?$mCcq!X7#yjV#rlP3U_%)~{LipiNk zMN+zpW8&?%!;+U#Y5dHBBTv2zXy=mv*8Lte!=cKU7dJ=re!CESrnNO#g%m?Se zr?hdSEaNV7kiLvr2%h((_zQGbV1Ah`-56DdId^~4){nPn+qA!Qwdg4PurH>YHY*B_ z$sMQuRU62(*Hw@_Jc)n(49mN4TpUeaoMRGN{}LD59NNIW3%-n0;*~?OxT0-7yY#6D zF*UKmkPc@!{6Y~$1jYh|P-WI|V=MUVj3kdl=fc0T{g{h6TgO^m3=;;$r>Afn>DSa&++BfY7iLn$k4xP{Z8J*6C%oRqx z9XH5?k!+N_J4AN;@k94H$skwe4`ti~Y%LUPaGai`Oyd46X zvq|H!g_u6|F1zUSEu3&k8#;F1q-nO?Tz-BdI#Q1Rq+QRrtyZH+njG7h>S9SnBxHV> z4MMzfx+5S3&W`reT^s|p%6lKv9_L97>o^7;SHDf+{~-SUhat_EWAkiViS^xkFfQl< zb6=y3uD;+wN`yz~+>bS6UP=+E-*|j@2P8XRQoIF=3S-b;4h7S0QnTDTcqNm7Yn?jj$4jwzElXB#mgDN&zG{slvRh$O zK_$jj8xT+bWcZI$ji+?%f}01dN9nj5=5#Ba#;>c6#Y-I%fyQ4Z{0<%dj?pe$io39VL=_JUQ#@mKh3@n7 zq(MK`>(z}tLBH4uf8rSHyZSiUkFZ5{ z-$3Ma1XlmDh8n}2m?~Aptf`6;oRzU-)%5t_nxld9GyhSm!TU7$wK6kn>=!R&YZ`qO zSwPJb;t`}yK+DuYX2J7h+^?Jhq1-&DB{cyrmfysl2O{)q@>0l(OCw4rjmg$K<8evv z3?|X8hf4Mrkoyy&a4aO3Au&%=_T zBrKAcg^Z;)syrUUZPVvb_4cFnepHM)-5qXoXZjtEpTgVLe*|7f4ZAgA?H%{9iEpQD> zhi~U)Q26<4HloiL-HSc3r2I6REIfcMq1rfLpo^A2q9Cno6`szyK&Zq-di-NKp0-V= z)v=ebXf;wV??<%pS308Rd2HArBd{4gi5qHQv}y*R5AQf8-{al;HHe}f|uut@ZPaEShcPRH`ol* zj)x)Wb2A6E?57Acb13#En9==pcgdA0*Qlq-0chB&4I3U(+@QjemC0FL@2-$U8)cyS zGDCXDtQ%`59iynB3e2b_*w1fg>asM@^vzamTVH|~SPlB(z9}6@xl6wGz9KQq z4RU@_3dCxitlQt}hM`!DwNt&UtZN0VZp}oHFRUQz$K|sx{_`cP4sv^(v_R66_K_61 zaqs=%?Zl+A1~%^654N*>A$cO77bqeEQ9?gx^&LAp%Wf8?g&e?0zY5mx%mVBXDI$wz zH!)hn^T^@Wzx3khHjXzx5r202!iJwGiN{udda$7xf}GP?%bm-iPhuksmM)-QzNV3Q z=pl*T26Vr?I_Z&KO`p!S6l~7>;!{}pm_jRoC#j+-GZy~~t+--uf^-(ldDGE(%Y7R!Dl;=Y&1NF+yqFMPHX`0w%n zxJ=-ayeVk^OAJLPNitE@;&?mzAH8R7NS3^d;HUI(eYxO5+CE_keRJtK+UY&34~kLa zHB5Ae6Fs#4y-g(xNWzz%AkA`Wv>7u;PX#K2edS z4gAT(e`K=2RLqFADT%`9UXBBO;UmiA9w%>tWl;KeJ2+4?%#P}&``*Z)(uu>g(l-(Q zEVLy*FZ?9?grjL+h!#0`onx2!nXzL9*QnbSS(J<(W!v@{u(LY6_yHHQS-E5tTopK- zxK>TYwLWgdsXdI`VryxJnjc&ao)0(P8uC3S7L$UYdOC~S%TN024a*gevn_MgVGY-L zN_B3*`rR>P&wM*#_uR7HG`I`Wx?hsuFZW>Ak93Ik&4;U(>$$nOVSV7*m4bA4Z+dyh zRxXD%!n4f{gMC3}pziRKd3Ifb$Z~mX&u!7P_P-3~e4esZ!qOx-{yU6*^>MADCl?kyfCthe%=5J$Sn~`QY+vRA-d{D*;%@^r{?$%T zOnd^3yfdWk>4kbKSr54hQ7G4Z8T^~o;O(2UOzhO|`JK~2kwO`TaYd_N) z%yPlU+Hf>G{0UYGtOQk@(=m_DhBdWs*`HIdlZEF@QTfsUBkt=%+hufMgHtKJ<5)>6 z^$)??&92m?dl`O~l7ZJx31^HLYjN-oKo$YV{lxvB{@K zOHbpS5Lv)2FJbqGZ}7#XkG!2VQ=rZPTb7xf7qm=|0jm^W_{&Qp%Zp~BE$^$cmjP*h8PvInS2irby05FICES|Dl8XyQ*mj*B@wH{D(V(<}-I*{(`?!>M(8l zCjQOSX6U%#5gh(clb@nAA3D9VsgwUF481nOeqH;RO#D5bB+r-%8^0Bir%%sWq#V>4{+c2Dh(E&00+M;#hrm$;6te@{LtS9O{UIJ|L#8R zSI@%~T%_e_mPh!k+r}CY@=G66acjQ7|9cWB_LNAu@f(o%X6i!Kl zFEkr}G>wsEUoK&xjuBPr%H{2_engJyieusNw?tlS9bM@;myhlrXvew)d|`VE?Zwu? z`sNSxRrzr=h}w?x{eKg^Sk4{vG@AG~)xw!|?o{zdCMrw~BqK?jgFP<>GcD9m|76mWa3 z6=kfsNH%Toj-s7&MfuIP@%Xuc%Y1)%jL{1$$*0Ol_M+iu@EI@5w>Q+VL0i;;&0 zi2T(o_`RlxY!Qio!Y&gq{>wSme?R5|`?D}K-kNkKv|?RYC)k|(06|ZZ(8A{cT|G`3 z?j|h2{nQbLRZE~~y$kJ_mP#~F9wlWPHQ>c$YdFDmBSwat(f`hA;k$}lm}64X`pJkWmuXZDmc^~K&O0(1YL7+Mm9W#W76o;qnkT$ z)wWL6 z32M0@%iBx*gZHpqRep@(lN!RtY+*m&G6ut^b)-{74HiC@fyZYa)-Q35L=KPwKi~!V zkDpA1!JlNQ=kgRaVKJfUTG-$q`M#h6U$$M-BSL)^XcVow>b7~ek<>t}1zC|SH zoIP<;Jc{=Y3&DR?YpJZtGZK4>>tHA^#N5b7R97a7{^#otKOI|`Vb^XhsTEIKoA;BQ zOMc+0&sOB{L`n}00U9)M{Ip5`$dluK)axI@GwytojyXk?pQe-MyIXO4kbsfQD5M+5 z>WJ$QAyq?D;Qc-iw8-BMg_{icbw|5Mc1Y~RFV3WhOtRW^(^B*4xW<8bqWRCMZy z;g~xnSa52Pc>JAAEFKJ!7`c6{`rPS)A7^Udor4)jP2xj_o}0ihkx!DnrwYETGR0N@ z6{1B6LpOIFCkD(3mSF3E9iK z@V?IoZgzZN_!g7k;r)~7zVkJ6_GSUGTYQPA#<+rcgP^`seT?kBeSN-kwK7@$;u=aQ zuZ2!E8Gdzz3sc~4$bE-n(ERIoJW{`kcDlv|< z)O^tm{N?@FN2W2PC;J*JxnT|@_N}1n%azIAv_&xQTsCrBd~|ENM1Q?ofl2$asg!Rh zy>)1enf2)eXe2$Q`zN|H<06z8>C1ksXLbp^e{lnruhS&orb&RNdkoVaIGcuVUXQ9a zL14Gx8>@M-mj%WLdZzs*5p_=3cKslX|1befhmP0hAFPI+js8U(8(T2(B}TXwdB1@I8z(R z18Y+;FwQ?mj^9ls2W0z*-U)4-H|`#U1smgwPic6;BZXAu-y)8TGMN3UrPr^RV4atS z;NP?|*m>$I6)RI`O}+}DgSi2{AhQ~`q~^2T3lyo-m;*j7>7*vxIw1YAJzR3TOs<4f z!*M|x{19z~M<(%PZ|-#(%sSEjKkZcHMk@L9kfq0U&ok?5U2)s-3J7gi0{PhW@R{=( zN7`jW=BJZniqUkMyIcaYoMymwdr!!GWdLz1cge_%Ts-T26mQoZhNh?pxT(+aju%8j z$aj5`ke$z;H~li_8Drp!=Ut*z5l*Yb%=vE;^g%-EGlq4}!;_27Q={+iKx>T*9RK?O ze~-%24L;Mcylw-I^+{t(p@M)+tRfOE({bWJIz9Eh5@!2UgIarSeNe}A{^f0cD85*T zRMnY*jLIn(2s%J=Taj+v`<^Z3vcu{pIG5Y6tuW4mk}EZzA?&|MP)o}vM&q?m3QI5gHvpxy9?OM?Go$7C7 za$5)FsNAH(?=tbq4PVe1_mjpP{zr(UDLttCm2`QBllgw4sE{)QcCJ$Zr(X|gZ0c%g zw6vf}JMP0bgJ@hnsf`2#i$iin2(IB`cj7&L7%4sn-)}L*Z$09mJ1T;XD~{m%b zBnHk374j!Mnn~%bxvXVu6Yzdp)1@&>$%c|kY=L?HVU!lOqHR8T|zwH(g7gssxaBcX!dD0#04FA7J(F7+ZRHjse3H+4~k=3wUF zaWh^^73W&nZ;P`dLh*rb9MSfdL!*t#n18Sgo&3eXAxV+X|7(sq(@%m!q!NEzN*r_I zV+qgvd;#9j6{kn)n*}}#pFo!D8%S~%;&1)e0Qb-w1I&+uM(}>pE6K4|f88U3uMQ;r zz)1{$>xuV7??PJnH7qMK$2W8f6!rUo(xXV6+8a(-<+pf0BpZvg`&j*NS}4V3Di)mN zJhXnMFuOk!Bo?;8wckxd`ZNz7bQIFgl|HE9U_ri{m*WJ*Os4VAPjbpwjIX3vPn~s z?HGrXRr4?cA2aLkyd<(V>jfU$s<4>%oqWC5On1q&5_@iU)MS@NoIVpA6FW}kJx_wa zR@&s4#x>rQo_mDW7%?Ng9Jf5{3%h2)WT<#L2hzK*vRAii(ZFqabiTeW741C++L`@0 zceyop*FMdA&iU@r!fRo`+5pdlTTp39O}0mFGs$0A1CBAbiSR~4GX6S}mwS@XDf>0` zc3sIPPTxuWnL#Wm$^%`^NEpo3g{4NE>-6;nYUs6(I?9E@!Jf}h+fzhMMn)l-%k>BU zZpNhRW$57AO|zXx*;vkYH?Ms?;-jF(d_;@7=|00W>D+6(z%Tm}yxLjE4 zR2uW^9^^-w;+V!_`h`x!rTZe8Cla5@@@-FO>A(_L_|K%?EJp*jTC~!HB95!<=8GHO z_EVFE^|Ww+yZZ}vV|jY6z~WaDS>QZ`znb>o;mR{Kv*-~z+eXt1&Y~Fk;WFJU-dOLb zTSiy3>CkN7A9Up*3$j{Aguen0)AGHQnAW0&AJa|{%h!EYyhEHns;UEa&pN}7O}oTA za(+%;2(^<#++1>jTrAtlM1jKO-K-zeLYd#XbcU&-W?^`z*QJheY(fs3~2;GC}y+1D@PVcw1~@>_8uxfFDtb=Vd~ z4rg)w|E2HA=`uB(J$DOcs|`Y4Whc6Rv?ZBZJiM7i${+GV$&ku*&BM{IWf(RwnkipSY5g`Hj)YBTLay#c<$|jyXe`7-YbBV) zMJe=z@*eo~B8DbU`$W3E=E4@)=ivS?f|1*!2|XWwA{So7c?VBIsMjTslQpMlfm@i0 z1G?m}Y#dhXa>0SEL&WFkL-s~$4E4V3&i5N*VJJ{oFt>s0Aw(Brqb2bx7lx}=1{MW*Uajm0=h;&72|#RILW;m)OH)9 zdSoBy3|BC-%uG|R6*3%-N&rf=Ons|fBSxj`Gj*v2Xcg-XHU?$1&2~RMDL+vl z?IJ6XZm^>tJ+48b<{fkmN+m*8Z4`Gp5Hp<%y@#}ZRLwjX0W&6ruO3h>9iK2(Cc|KARJSpPY$2i}IpfM?HL z7|0dpdv4o|olh3RQG0(fgS&g!7)8TH;T6EFzKq&EKDeWC2JV?HT zTHX9fR~9sonU_A$b-J?r=sk)!&ou(Aj`f1})=W05g%4U1E@bYhte#i8i<(nZRjet#WW)|epaD5 zf63Z-`g+w~w5(l>zdJ7Azf*c36t)V@EsA-bJ6o_|t*StHA|aKg-e4P*##pR$rOd6B z;PT1=H|D0GqHHy^2>ufK+J~_`&9Bd|+}e zlgyx3RLHjqeoXJck9;{ev$hdi|H#63elj=L=Nz9goM%_J2vpO*!u`A@tkYW~jB={M z{*-gnttSY3-BRmaFN|m1dxYw)t6rsrVY|3*-5eOI7>AvUOPQf7{+PM$G}v(*%m=%x zaQv!y^zhFv;&{7(4O!Jm%I-x{hcZXB`drU=)eqCCMKUzubUv|ZE<IBuAP z^(Q32=S4cZ@LwI7yBvUx~UZH8Fq2yTArw1-4`YVkLEdC?oY*TSZUOJJK~3Yh#c zgCpz3@aIz&bpuC9yV^V~b71Qyyu6P>L!$K^$!E#-?oKju-(FlExemI+8cAB2J4XEF zlljedSmanr*^?z?DUl%;KgvO;p%_`IXUHtbZew;_@Pt^Y0OEC(W58H;GP9i>s6~ba z+wS(T_G9}mX0|yCkM_<&`Dh-o7wu*xhfb5Kt!wDr@H+f9%txa?OUS<@Q${CtC%jTU z08c_Pas0y7ij2Qw1}*wxi}QniQNayQsyUPbJAW3R0=#fb`RPm-$2D75&CQL8l?88=#F7UfycOG5W^27;``KL`jox+>eB&O z;5`?#MjoJ$ZyiSJJY}{QxuVJAQScm5B93dUV9WNU;CsQ2@ppBj9?cwQs^>CIKl_yK zeiMVni$|FtsaG^><|KI3+<}R=+PHqU8!k6dM(;JJabLSGx-7d1JFUl_+%fWXW)U;qeFjXq zIUa;EBgSczr7l)Z+8Wy5er|=wHcxy)1$TFHh3jx$Wfqfi-l`9XWQAST4A2 zn8* z5Ib{=>$abvrd9r6xMLQXws0QKpBoN(QfqKV%~>K`Hc`+!Gl2CJu@?A$+KGAZgmD&d zo)VXMS~!?N4z-OiN|tx2dBqmIn=66I{TIOe)ou(uTZdw1|6ygY3S2msf`{tc@Jv)P zEDg*d{f?Kxu33fXJ($3>IhNDw`m@G2m4M? z-a1WK{viyt9HQ9gC4_M|lM^V?Mks%x3|l0Usq8y_wAOhC1sYOKyA zbe_e(s;2-QEtjC@L^Sityaa=8?u63K*Rge6J)^o|KF3bB1%tFIik8>u>NQ>LRKgKI48r?iWTVi-vx5~ z&IG>HqC~8g5*IuaISIedpFoF`HpDq1iY|3M!fjAo(C2p_TD~}eTW*$Nc&Hu9ny<#r z>m0{>Rv|q<$&R^Y#urTfxeyh{N@%LeC1|e_75HX1V9oTesONhh_dL2oZIo}5AJtLp zn5zOT+`AcF^i2duUwwg^LT)eeod}fX72$_RU%+PJZz4!LAgC>pz*;*(=j<{-LC;_8 zG8e)Ur8+&$KLDGH6M;PRLz{4FXBp`VB8b)gY5b3Lc@Z1*I-C#FHrRa?QL zs=rL?i3qM|)JV6Q2+@I@aJ0Lu3UOE8aZ4X&9y)>- zMHdM)t+wF@yFx17_8v9j)C7;j=A*KbIzM|j9Zf$_{F}cWx9AO0qc&6UUEd31HYv~| z8Gv4@hIntcD4YLH9-?A4(Wojo}D(b0 z6MclCx2{md)aQ7oI|dg8iwcsraBiA>Mflj)gw}u6X#2XSG^=|aChI>S?Ps4tsEvn$1l|0lg8}S=cnj`7rW17xVb-}1q|#|GJ|BsI4H3$4zAKyVn3_d3 zziq-NOOA2K|NA7-bUdxNl1#dr#jtcm;K|-@doUkq37xZmE!*>IuC!W-ZzdXdsT>xlI&7Z$$FmqdMK18g|xKL5Gq>KS5{~XHlM=~QN8#kVq zgsYh&kgW2TMm5bO0q^or@lYIO797Ng@tf32R|*sGIofDW=S!OpldDU%vHUMBC}MwU$@wOYno~?B4R6H=6LGM)dJT8@8DVEsD2m5~V5iY+oaFV6y#MtZs0R zvMz{1l``nHoJ&Q_BRT$EZCr694sH;SgaW-#l6xZ&E4#juXs-*@!n}ceIG~T22c=N~ zsF;XycKRsQ8XhIev=VMQwgDpi`7B$nmjEU>xQ&?cVcv+1OJQs1&IgG z!_K*fY0e3C{=uQ&oC9lX@K(OZH~CH17)5e4NMMFVsdf;!UYk?JRhI zDW3gy1MN7n0XF(Rg5;(D7zsBi^!gnDE@6jJ_RD{4erY!rL>6I}&j-kMXrqrClTh-Z z7_PV&N&Z`?j>1+&#CZQ^oXQAegGe@3J8EGs>nVNmPMPnwK^xo8WTTPLCFb?g=Qw-c zObl7M5Y7lbrNbNV;q8`3#BWv{dRoT9?)~HVvss4!%yT7l%^b!msU)aXFk-A;&7sF% z1yZ@<=Mc8X6{5*DzU8Vc?()QC{L7XmJW-=g(ET?D-4?%wM3qc*7Jf}F&embir5Vs< zT>(Z>lKf5DD!fnG8PHX)z&m*8X?6b1bKvT0KpMq2qW%&Up6JwvG*Is&G~b@gFWun_ zIqccC(?=d{}#a;2PIx) zt}UFg9;NS_%BbrfZIB5#43NooDK|}r_t|D>&b|{(H;>cM(O8n#d<`3v%%Grt25RXX zqSp?K@Q$#ogD0w=@sW2n9`x8we8gBbx#B3H;k#IGli70NoQO*hkR57=*%qCVzW$P=kfO_VyD$F+R*kGrF`m~PRQ z<4u`k1c%~ok`H%S#^9(jbe#FkM4MXU#IxzNu0;{=DZ66Ew=$-^_68?~Ifvfamng@k z2|o({=K8+y!k#y}bpL#D=sTK0yxSu1cgtz$S+fnpD}}+%sRP!Z00C)WJws;|LXvK;rEKvw$I0z8E*LUKSx;mDw*Cl z6Tqe0R-moGHu|{s7WMCHB}t~OBiyuWTJWA-^>Oyi^NP~ptT0nmM4fwwEFzNj*0DISsGZ8Ny(lRy+ zG$TrckqX&N4T8$(zEz&|?%iSH^W`+!9QS3N3h&6~x02+}-$YcljA5qVDThUCc~H=$ zi4Nvo#MvW(8PZOxzUY4d<@BnsknJ08)!7P_TYtiwKZPvYIfUx4ysSQrc>2e67tYV| z0ZY{ZczvUrvoKeg*O(PU-6mvc_|_3db#M`PY4TCrc6kQAT`LGz?WRG*erHS)<>FF- zCUUO)4>j5(42NHhflZ<#o}{y=mi>h4P16CgOgZMp)d^xRH zX1BI9v=A-?*0{mmBxAfG6OLo@+hC_i$9H}gu`$cc-fGP^$D5}Q+`FfNt7 zmkOcn(~9aH%^4V*vk(I-RwBKBf?T*Zo2>qk%x!7h2g|ljs{V257t}`&632o&^wMbs z*qyHgyzx)upX{IrFZeTff#wznFANc;s}3kF1OKT7M?UuKy@~Qx9ka} z_jZJn-JLO9sTv>D9dy8u<);a6iVFDKN8=_RcAxLQ0_LinCrOP~xWQkM_fWkE4ra#D z5AF5vdGd19+Q*~qqvyz%!!AEvrlkmO9qnv zcxlk4APbB(JL3@5hgW+6R(&5PCN*ba*3Pwf5Wc|vAHm#Rk~|{uY7+*`4rlpC)A=qb zDNtk7g)mXO!Zv zXf0TmeT+QVoDASbnXB1`JjDkoP#OLjd`zC;aAg%;LpI=N?HK$y&7F)jnlPcOG?5e{uJEP zzLgk5I`MS}n8C9=mk)=0nuxi~EG$@c72fi8Lq(|) z{QPafOH&Gjhovi4)WOlD&JFI~RKxkJZ^~QF7^>epX&$UV$CAza$gTaqNCxP(GjBK#K$Dl&A|V{^2NEqbTDZL z{K!e-p6@@39S;;>rcfo+NJzq?vLG;gaGq8_nJ_zRXhQ#77lRS&36kY^9VHuIk-H0Z z;A8P)?#favl)15iJl-Zoy&c!$hyT2Qmd}T?J~{B*?=~8CjBr~{??KKoH|~==@kAu1 ziMmCYQfFC7{I+~LS#EHSh8wVf{j;64Up|^lRsBI0OYltVh&28?6*yvHl`Au1nn!dOlklG{Oa-E^jGPUMZ4h33xl4L%2^kgin?- zz0w~DGdF}ZN-YN;-G}rEdtVlJ6(izxeE3|D!j($bK*@9W7)$nS^jKE4`n#nAh8=vw z9Nwq`e`jb@+wXHgIzgKk)~Z4F4a(7&C?0_Qd;mzJ+s$8?g*m z+9!-$4|Z>%Y{1U{C-JS<3Sj7(Mw0g|9nc$aEc-w0}6=P@Pdm2)} zvAu`%OHSb%ZmokwhlfDEyqrwmxe682N{PXbZKNhy4iYY&WII2fiA}dY9URoa3&rtp zRev2s@ir3Uwrb+nCbr~IQU7GKd4-TW~=)!e9|&I%@Lo*1mOW*KN&NrMSuAPu#o45 z-^QOYKNOXz|4CzBYo{?!R{Rh3FBl}QPb_$HH!eZ$r{84m)huFt=_%-zRxtjnH$Zh% zFtf0u1y&z^27bCzxOw&wG_K?}RZhKx!kVX0S>Fui5C6jHJK5c?lmJiG?<==0rxJSQ z#jsvc0k%h)qIU60^5wQAOn&H!y?OpzZ|AGkYfsMN#r0L7%3cxvUyV7~RqzK@{@kKx z1{Xs7H&0mpsSE87xj{PX{OeldL^>?kxxviy)ZLOpZ?gG<{rf_y=lE$u;=64y*SVGK z6YvLbm-9HfvzlxyIgW|bdT7`FTVSl(Kn^$g(A}!`pwN1b*nkkvbmR(N@_$2ux4p%f zo}HNNvm0KRkK?kaB;u9uy;{6e5Z%7dA&2^xW6DYotP%c$y3f3*^Y9&GXXwi;T~PRITa{{^3G@F1qQAHpWP1}5eMXtr_E;2_ zr96bP!;?{{aRr&NcLZDd1))}UDVZr`1^YjH;5`LVl3KSG%8ni6_1vBeVKE3-7ft2M zuiZgkoVH|{2Lim$t~yj9SRK7DU*+-w3IA~xJJZ-amwa)x!=Zh7c-JWgekvWH4F`ka z>6Z=66aFk-+4L6bwR|nKO_k$44}1W=DenCJ2J7J3&U`F5Ymar(-dGVh3h(u-;NYKy zEYnPa_po1_|HWPcFY4GsOE8c2+_=j=A7cEhzxK#I6@o1~X`sN)wG&s?;-Y9tZt8L| zUR#t5ZWz7^6+7RO zkO$&hBO%1@Fw4ZW=0Edj0R1a+e6!J=kQ{Bq|1ugw?>S!P8vCWfp^QGb$zkVnxBDUO z@l-m#*$y?+UxDNOa>zV11@>L@z(4j$RWNA77dw*yj|$^aZ$$|B#EYZbp;WH)LUs-~ zx)2g(TH<4g3aI;7NQ*pm@K5mp(EaNNr)-C*>cSaV@Aa4(?OX>t?mZ=97lp{O*@E2C z$M4Z3csZE}tp$UWm&E(TUvBB#Y-&3rpS;q3%ssIvi3~qu*zSZr{(BvaFW-q$^I~&) zX`U0|Po&Yok1}M2=4A{rdnQ6;5>zLz52gbxz))->Ktzgrn`1yjI_!ySNpz%IRBC1F)R4fU~R2gY-?;f*FyW+*4s4)w-=5 za>yWn{8ewJJ!u!21v{PwCk# zCF-ep1^g@LLW%+V-Mqa5^zZJWGx8>**M&klY05_2E0PVuhAZ)#<1JjZ*bMJXwF93i z)_7u72{pdQ=HYcBv9kRPdaMd1!fP_&dt(MvUB1J8BJr1Qo#%b6}+tv{N0DYqI8cBeA)4(al1fA0sqgikQ$vYg%5D}ju4A)LIm z1)iOo2LqMSH0<9r679MfAAMW`onjH_Ewhs-7)X$jNe5ZBmN_`R{z+(p9Dn36+xO|d zMoOl1lf6s{SNHZ8Nb^ zBGMK1xcR3i%}qOtb~#(2I5G$-mD6G9kR!C)J>zZ~mE`}&w}q5pIbQp1Gq~JdO1{`x z^G3IGiSu3;IOe<)tA&(drdtVZ8qy_sOJ9(eS2HlHQJA0VQ%6M-u0gbi4Vk^u3T;G< z>8jte!F5**%-Zh>Uj+8>;|`ty^)=-n^xp`TzfenbF8(CWB{v`=I+->+m*c;vz7MfK zHW5!rF^FaVd?8=JSqZjS@WGom7cmPCoh8zSp3HZZNHAWW%k|9_Wqi(*!4f>ndU0h@gu4g+CEQ1G@ui?`?8Ig_ zj&ZlfCIe@VJdbC50M4#ggNR8tNW88H&XEbkX8uxoNB1O7h;0V9))o>hejny-KZycH zAz;~go2F+TCd+6c=zXxFoot@>sa+}SS`H`va$@w>t1`yp;ZdTKaRwf+tq}cPxG>^1NE3!111m7)?8sk) zo=c{{fg|g&W=$3io+AOe11A`b4}+wFrV_!9BsgCCoYZU*g~*T#RHkf@IaYTSFHG9Z z(;t;WF>_&FNcbv_&isGW%E}nd<*dTNvy{ZmQlZC_W${LZ0*D;EK((dW=Oj;xXOu66 zqeHg1V?iLzcyfi5e=j8aRC9o$qI^96>;3_D&6Zd1rmM+Gfi&;FjmA1>~)MG5RA_IMkuaQ>d@S5dKLR9B(7vkBxD~Z>(aU z>#MM6`#U=Na1v-3PeC`B$E!H81J?_tg16KOkm#Pmo0-PYugc>TO5cEmmoSglZ$Ynl z5!~x@m0JDqq^H-gd6Je?s-L@*sVVLut;FLwqT#o{^^jRP={}KuP_Z;(TT)FXG|M6V2 z%(2F)t~S<+B=Q&5p_z9Y>Ab2>WUd{An?tK%_9Im$XI2Xa^xvY2w*P;J zSFqg`QSvoJ5;RY5C2CH{luDM;9;=^Vd+#2*d!0#oLr+lyr%gQ9`E$wfkTlY#*h-zw z7gAGgLp&5X6B>7(AT>Y4Vg8gEc&5W1Z&jSd1=EqRajemPGqWSdat zTMOyzoxC+*5e`ED<^%Z7%TOIf1iN^l zq0$()HsU_1mhs@O5=w{0pgeMCb~ks&&sSjo;x-rzjKPlkj&MEU4(XV+gi79TB_d9H z;JT88=^Yn6w7$6tADXE$;=U!2C6q*Fe~9B;l?tF+29@EKsxeKdr#F}6#eVvJXLX5_(o4{Y}xrb)LXD6|dG z%%Vp$U_%Yt-%o;13yN_6&n_}2P69wUlx%lg1*c!EhT(*KT=DTC#HlTX&ioG0ymO5N z{~IGtBJ55YooM*@B~or+fgAOk$yUcr_%Het<^1r(*tiEM6L%Wo&R&7&2vOcgg>GuS zEEW3Lp7E7@a}XXk21`tWCr2j1p2KIUZ^~!t?{9^wvv-5GnKa5Jv3=%bkrOOCzYxyiB@&(Rn)KtrG3o=np}M)}TT_IW^8=ebTCN*q|xRC-dzwUG^#w z@VtnjWpQxuOFBI^=1u&1R>Sjg3+D2kbzIw9-LQH=CJyU-Am5Uk@BsfjUOQmH?bmuo zcCQxTUH(hh9__bkmxM4#s4#|*dB?H-K?&Gc4bvOSn(Y4*0sgZRE|i?pL&24=z~$Z^ z{^5I;sHe9Y+e=pxfw?T_-{dCDW8ceC#S+wZ{Vp7Qs}Gv855Z1OhqvM3F|xd(o>;%y z3Y?&LCfNTPG5@BEHy!5@&L=mzmpOy?PvhN^6^!G*2C!*ZQq?@f=Dnl@`6E^n=&cirrFWb`^t~=! z;t|g@NYCNFTfY``Lf_Ige@|B1)U%!HZx5;icH8iebMr`Zg(&!K$c33Vg7Iy?5MRlM zWz4Fmpm)!M}iJu^6we^F7~|OYncJUOg1w~tC-yQWl0CA5e7#U!DXLG z%=&4iI7e_XE_>m{%PWe3k@jKIrSKN~BRAqE?{ac1vxqsVz7nP#TE{<>oe0e2+c@=n z4(Pwi$2)CrNy_t4{Nx!#1G8K~?t21A1toAL3eNDNwAO;?d?DO=Viw$b&_pM{;StF! z)~y%H!G)*7L2^M9wK|uM&B|kVy4VP8rv{R~XY1fnWd*!Zk>Cqz7r~#HD_lD%L9CS~ zyeFVZwe1UWyFobqOPa=u>t{JwEv1OpHV`4*298Wg3-O-5g??oFLDvIi`70k80e6rG zhNTQQX^}tQzDJZN?sACi*xXIBPssA}e;1DaHTB&W!x~|FGZbnef|{ zbtNrW!RGd*pzqg47(ZTtLoEv2)Un@C8FmYKqB>;ExC~oQUdH#zGaxmHe_S|`}yd0&u<4@8kvS;@B1Os`5A6+_zK)^XWBe4oz~c^@mu@jag+9C zDtEybPF8a3=l;WYh?!zKJ}wjk_ud(-8}l4jf+UhO zOJ$h5>^}64-6IZ>pXhz-^VktSLIb3Au=98(Z3~|PX-;Omzmrzu>>mX4k4}caXH21$ z_0_J7RRIs(!RmcZD!ij%&G_nC5Pr&-Mf4XfMnlH{*m_5lcYo;)nDot*+}?SPs(gt> z_l{WheqM{yG%cu6sRmvACJy9JKZm3%a~a*Wx3~|w*?#zE3FhsVYWi*9Ib+`}4eM@y zB{vVQ1i6>OWX$G0)5@}>Z~vM}H#xk*290+_%3m1ws0h%N^kH!sQ|@?~d_HrEeqS4eIae+5bs;pDc(_YCK~+8}+8rFio18Jv0J5;Au4 z&@x>H9{81UCEv5%QR8uRVtaN{?oG_+AX!rJ#R3C@q?p6PnlR+kiQ;x!ahF{I*6o{$ zk*?B^ss5T8{HUNh(WBh;cdyfmG!@7Yoea4vZZS@Mb2zV;#gI3nzc@0od+_}c3!>jJ zMr%%8qk3VpF*{lg$7SuA-|YP{f2}!v)?-Fy&)}0+RjKHuv4?*D;)`GGpL2zKAAwBT zO1n$D~u>u=Foo8j|^}3 zT|%E!f~8hHeROb^+2hfVg!l{-ck8QU&w_oVEpR^$JWGbN@2^+O8fH@Bpn(qzL{aBk zKI2r(c6%?sL%Y!)?t`i&SWhKU^lAs~l?sAeZ*|Dw#y7C{?QJXy?F6gqGIYbV0Bq?F zreskmxwP2=Pp>n8srKVoq~Q(M9n*;Ccm>!zdtcr7uN*#<=;6JIGn5Igpuu-rP}tX% zc(=G?b|t27T19yL_1l9|buJb8mL zb{Qfz%6uB`b^=1S{$pg$eW%}@mePyq9T0ai40e^Dp&Jy6i97usk#KV?9IAIp!PduGI3eyp3uC(ouzdAvmlNm$FHg9^ z$RPHy_v`JpxX;81rWO{n{S9}hc=sIjPgPNW@24oi@+0bBM&L}s?iy}svK?MG5_8)f zt$t3x+6}C4;7cIjI`&-sCjo{;}4a0u%iAEc|kYM{n?O?EfC0i^V{!-lpAnmCdT$=pa3PYA?vISc%n zFV2%!XI;2F3&!TAAL!^cL*sv5v}95{sDysPlF&+;bFG?)?Fxfsk22_r6&={4DvZNU zy1YXjCqcY1nw#Y_855V!2lHhgU{iQ4H2h97>#}(O{2@F3tSFWZ*sIG=_VUF~qG70k zsqo`L6LWaian`>@cm;bSjlw}f458A){i}~^wvFi=FI*Z-}2$V zs(!ls-&t7C@{9{uuW@M5MP8%72251jLaM48n`w!M_$`efej*6(52pt*PNBi&i;-VifU`bgAmz}{t^VT2qX_^{NJ^r0`oNGZ(oeA#E@$;PRX_>gT zLY4|$=;J;rm%y2S?7@)FGHZ4@pvuL6Bsb?6uYN#)E;yG+hE~Y(YA=zq$t1r^qmc;;hNyl2kx@2>=J9K+TKmd-TBW}nu6;WXbBqM&NUwX< z>nbG_v2i1to2KKO7mM)e3Ho2nI7dc?0&y(dhDRiYv@prxt1d^x0v|mgF-M2>Y+S!Gdb;N|%b7~%~ zy}KF&TVBJ;O->lq=?;xdDHz>c57dND+*hmN+!!?&A1S683I%{|^MRNLg8!Z#vVlDl zN83#0E$LYVzt#q#B|U-9j2pn#xr8fpVKOgJrw#I;0_>%dahl3&;+>rZ5qo9H<&ZFr z*1#(|@`cT@w7jH)AHEWoBz5d=ZecunAA|Dt^+asnHSm>pLY?s=Fe3R8oYs7W=X-0w z+{X^w#k--oQ35`{6r#VTviX6VpK-6_1XYN3#UH#m{KTJ&iB-)2EwMF(W09?7kG?Ao z%-aSZtP?=|cr$0Jvp7#?RUwerD*Cof5IpZ5fRV;Dy5;O$I>$U4ZZ-d*e>ERK<;yG# zYIwzzzkLelW`&c#%YtE2i#&?%N}&F+?TpRGQpk~RBPH#1L?n+(pYE}M5`i#$>c+u~ zYcApnyL#@hrxV5=(uJ>{{lva&Dm@#MN5#7dYRozbQ;!~{lMC3)RB0Dn$%(@&&R^)* zK@s|#<-Kf@wMUa*D=eRa7k^Z#wp<#i({{qik}lP5 zi99s9V8PuVs{=CZy!>YM%ro=0g@S(5A zXQKJrWVR0%2C~X$>7sh9-uSHy?r)>$`*D!|D18rYb$ZObm;0;N2aMBfDW(yX*))QsqSAvs0b= znghwgz1a6z9xjjZ;M76`8o-yJol{caN-g`Tk`P73{i3+7;5knDxf44#h|!#-OYvu2 zJyYE1g8qSX_|Nl3xgQSd!E6COR<3k_FM|iU2fuED#Iq5|c~F8+!}A%#>+i6raU3J+ zRME;c5*xzKV4BEr%$!`$ZCrE_W<1+NB$9`SwcB}k;w{gga0~@UUKo053exQoxsclV zk;bGE_!pQ4udR-tq+==UKi&woN|F?JNU>~~Ls;fx1k3FXfl>Kj^>}jvQR<5zvvuvN zV~iKD4DjLVoI8e8_OLCtt-u-_Z=MF#iIt%9x0Hk(jX+by)ez>l51(8;!5OOgMxFIf z?_6YlI1`!pK+`?1wt;-? z40IpALaHQ}@l6h|z?ddQ^13b%P4m`Mk$Y9R+@lpH?rsKwPCG8k+GD*j+i<&=0x!gz z1MhaOf~p68ItAX$woqs~9LsvF zHp94i5T5_{mwY}Q4+-nr>9DE-zvua5I$@JVjzSFfM9hI5&H2Q^fPIb>+vv|tH}O(| z5^r>$4xZZI%H8<&C)pfaMn8^pk~JcE82Fz640vp>(rS&vi+=wY8^>-E9JL(pI;@1p za?_|+t_;->Y{Gx85zPJGSfaAG5x!*Uk$f3F{Ap@PdLA8t)S95`+4b@`P##JD&2gsH zDXPqYC|xk@KZlPs{^9RO-B|6_K#UKDF>)EDoDTu{Xt-AprW~`Sr&w0f){oivc60~$ zvl;PPt#JDF(**HZCklrT%fjv(rjYCM301nkG1Jq&Fk$Q1OjsEgYIbykzHT|aR}@0G zyzextUwD}qpN;@q11Y?Hox{5-myI$`XHe7bAm;28!_mAeG=1tPO7=v9hN&k0o%)#= z4yVu!`4!Y@A{SrO2$SD?4f#V7OQ_hI>v%grs+ucVj7Nhc@#e-QoP{e5_;3Cm!K8;v zut0AYem=7UK5Q!_%RbzNOzBt%_*MZYPd>)l!Z2?1&<^q{-xZ2BiW4FJRV*}49B1GFD`?R|JbD)i7@C+;ZVZw*TD22h9{e zROVlV19on-S5O11D%L>JmNu$7z8<>m&O#@%9{VKFycvHn6= z{@4odiZ_B+qz!CzGb0*04zRK8J9rtiV*88VP*}i2_4*-Nl=L4My@{k#7e1lO_Phh< zo)J_zqK3np(y5J|I+IbsavuJzq3@@)(%)Uq`1z#-6Sp=7BU2Vb-xLDLJC*79YIPd( z#gvG|SHW7A_n4QQ$?*w24s)g-g|D6KKt~~fo6|@j!tXWl*OuZ9zg{;$c+T--`W6LNVU!_h02X=DDBXx<>Ya?_vn!#JR zK^FF$7v+Cr*#_a7yW#Jix12Q#_%!6cFxxlj!aq)mwz=pqew^@3kC8 z8O}z>U7sNAsXP??JBTg?Q?d8+VtDTRkMz8ehogEQSzl}dWl4)Lv-NYe$+;Y;eY&0s z2>n504Nv^m>kQLA?5C+?p7iTtThN$Z44V$Mk|kPg+$ZfdY>qAjR5z?6{NYkuU1G+~ zGLwSX0}=H5pG%y?%CDI4bDRn-Wc|Hj-PN(a!CbdVzqpGB;$T!?84}AmBs5GB^~R(4 z6Rq|zv2r~rVtx_p$P(I5AL0kh#ue+I;G~g%Xgk8r(RQ9-azffjIK+{1-vz|CPM9}n zCk1~mJg2#jPP0DTkJL5s7_;r_Lwq4tPG&UbJCiwDlew8 zJVPjboeBOYB*BX9cMx{pw9#FRUhKKgT<~pT$d_%{rRIRjY>#G7oHaIono5@a4TR!b zd+GMYQ!p*n31i;<#lgFt80BDsH?L`OqNcncap~%?DnbIwT+hR2Yg@de9EUefydk19 z&l2&fYU+B^103^$fxqK2ZAv)_;p)Pm_xn!u+asbpclV`WQ2(9&JGFtK>(4_|#W5_N z6pVVFHgI<{2jZl^RW6NnfrWI;i_Nhs-@=pced)8)q8R-?0W!<)(cFo8${)|h#cvOBGiWkh zGQ|E&_nPzHyVufXjZ^UJP&F!N6th0sMDkhYJ#~)O=G%N=Gv@Q1q3N+3yU!YRc?2jHc?nYtY;BFE>H*8GMb4CTauq*tPHszCI>L;a)LGt!m|* zJNA+Yf5@h52K+J6+YbLk>7ZfiN(fo&2^(A%^KKe`rYBmbp5k75PP0R)~fUS?Ao|JI>;$e)i<{R}->LkF!yW@tr3W$b2E10pUMYt ztzDoQDs+E|G24wbXZVCWcIr!`WViV8HgBgrB#d$FCPybW0fb z_8+9xtqD{_cO2iUttA;Q5!l%VLN8wnrc~o#cB;~jJ%2gY5VEgP&G7(+YB=b z#Avmn6QX7`%HOKPQx_(KQlbwAY~bLLc}vg|!{K)w12;!v=&rEI^kU_HG(b&jZQ?x0sxs_DMUEV?2pkW8eCvOUK0DE>f!m@3?3+J5}v z%A9`1pQd$*yxXQr>VH_#=z7+v!sZV;B6i@jRSW2k+GJ*4=VGEd?qE3L6wV$iZMi{{ABy!uC-3uSpz?H_Y2ytb3bpl=V@go83co9k>CCu<@W)!FR z#3#De{gqNj4)8BNS_l%SSpMm)AKc8CAXvMqiX{9p;rcCKO=h#XU#Z?F zoQvvFRN?w-y5W-+3iXIXVm_bDd%ub|b6W~cu5~6i)H!%;lLGZNcg1-pE64$hZA9g1 zCG4tBCe{Vl$PvY-=w19W=3fXz!9bLOUfwxPL1DPADnft!CNqM;yx=dO{cjZY#9Y>7c zIx-s^RJPOYIkIGs(?n9+%}D+40`5b(n7~+*tumgwvCN34$`YoP3R@HeLfk5S3RS39Wuz-G#|3B2IF$=1V*?c zn%Ng$$o%Y0Br4h_VD-`qk3KtuUXw0j*Y^9QR(&JhebLGp(A35Zt63!6L7f=)dQ+?Y zO611fNmS{oB>tA3iG6}rpd+h;pImaW^_Mx+Opd^Q7ae~1tx04_g%ixnUx!B;9EjK1 zT4v|{F_I^B7ClHDYWdZ1R%?_Im&9PI<#>T!8nLE3a)d!MDhHHyd1L4QC_3+Wtll?{ zlMymALdr-9MP$XfuO}7lB$bj%l7@`ZKq)gL$=>ocq46&*%LXb-_IUsn|8cjypShVDz-RWZGRd%q`47$E11O??H{6v0EdU zU)TbV%;)eb$7|!r%M461ktbvmpJPp5L-F}HStRWs{|)qZ4&{2= z!u*3OQe=_jBYIKWl3-vixz}ey&5M7`p!+y1ym^F5MdiSy{wBJ$d>VXFo5vH& zxQJEbR0SH-b$HE@7G&ErB{(xiAvi%97wUzR{YrmnpxSX5zNQ9m6y1n&X%zpVzY6bK zq&xH5-5xiV@oh#6!!cvMH!KMngz(cNuvglK`vN@=dsWbY~rFxgy>z)!J33` z-zW*Z-NwTXuSPmh$MJ`_{knh3A!2x53ffQ1ffnugyqm_&bjA5MRCb*_@5l#PR80@W zamjyhWAa6+xLy{I%S7U?J_mfFu!*r9x(+Wq#^KfgbHTYOr{HLWF?@P{f_ArNz^Bb$ zac1Kmcq4NHUI!InkGw4!@<)i>@=I8`C7cv}dj^GXR8WP>H@w<2QSg4-J+7zv0Z&XI z;5dIJeZ5hDKR%g5SFRuI+ED>6F>+}3RT|_fn{cc-6h3sR;h60S_)l^_J($yrzg<5u z;~ZMR`=c_?KvW$4{Jqe1Jf}>xHH3n527-(OW`go%N$BVw&iWsBL|$tl9lq2F-5+1l zwHhkCMZG#azt{8eTj*AH$zKs(-?De~i1isJUppJEU;bg%O_Jaz`(B{IpEYsbf!QRb zFCAsLdF8{vml*LxpX_m;1usvfv8@9RxFn_-1B>pE7WwId#N;s0a+9Iw|2zTR_=mJ$ zGMAU0mq0sJC!*!X0g#9oM$g7haK`w-y8W-c@eSb^Xc^Y+54Aevddu#i|7GR5G$YfZ-S)ODqJ{JdjwgZ9)Qc20>k`^)?)26KM5>|S?RJVzA| zUnwBcS5DH;d-f5x%eB~Es{{+YqM-U;DAVGahmoB%bVk8-m|Ql-+om)dQ@8HjJWM1{J0Z~tNyex z^Eh|l5O;r)GF0X~ml^#1em3B{Q5&n?RuSRE9eDV4G9;Mikb3K28|^K5WctWl@Q;~B zq@omCZ{@fd`F~XKJRVhp z$Ee)CB~bR)5-0U4gMQuw-Xen$oAJk!@LbM92q}9`I@}6L^Xg+v)a#pUlk7=6b%;Cb z?uxPT8)R+twtu1glft;wKN{cZ*U-XCnb_bv0h}Mqz|^SwwCv3Z)~IkRnths&=1!sL zd*u&(-T#9+*-Qb6Z~y4$6|$Jb%`tZ0))kC*gFat&G7zYkudX`)S6C`9fp zCmY+}kz-|qx3TX&YhN9P^sqe{Zl4JElZx=8og?bITp{&A0hrS>j_2n!5qE~AfR_FP zoSX8B?w&Ia#`WjXdlRQH89UBFn0^75M=k}K#<%pPuPsd3zX!`dKSJl@*Rk!a157IW z28z~lydAjfJE0PEPQE(Go1{&II{|%7O^5<?=&eCjbT5`lA>QX>?S&wFT<_^Il<)^1^6Z* z46FV)VU|-nb+UJZ*>iux!my>7Ytl#lGYiAPu4ua8x)h@O3lg=Um^r}p0Sl{?w90j+Er3??iTz~;<()7)dVL;*7M}` zxaT3=n`53taV(+)w%c4)wczxkxf zXcD;h>v0Yq-!+>s@xssPw zQw;^broix>0(!an72JF98hkBlalD}vE;^fxy!igWKR4Vn9JVas-UD*X-)DDQ^7yKlgP zt^dKWvKmRzD}tG`-r^>|G#t1-9vnMA5to&sJXcwFm|8qSa-VO-E9^vGoY7fkcxaHU zUFO9zI?wfo`cueR<~Hp8WyiY}zKGY5tSJ~@b`4!}<1p{m71$TC8nkR`iCA0^uv<9) zk*_UdE^>rtZeB`f3!phutq82c(3e?xSdQ5-4vY3dUcKGRi;j( z!QOg2<9AKO$*KfI2al3<{~92;UxN2erkOE_5Eg{kGcf%}KD0WW1EW?JT%X(^Ey3En zu%esf*Qe)_}$%8-YC>GL3(-Xsq>d8=FJ!~HOGyfwda{Wj39j-fKXPnh`DrI^|MnDlVn6iu-UM(0d(f#=jB+IjNODG|X~R>fjt-!Hc9&`FXl7Da3JzMz|Dhe6G;NankSJWRTZC>&i$yc)HE zzvDUCz9EPq)nIo0k}TauVEJ|!uGQx=>qtcGrNBBp=`44 zohiOoTLT~5IY-9Z6l`swU~`mX?C;~)BJY#H>G4I{YF|e5jSHaT%qH@%nvfMC3oz=X zqClo43jK$a@Vs>;&XB(c{#@Q-W==9hjkM!%3+LWFbOv7+eIU6auH;1@r4dQ`G_EZZ z7VN8m_L-?P{ze`-L_NrA{Zv}i7mk;gzN7Nr7ennMGdz4w3}+?C!(eO@1f3RV`^Swj z4pqah04`9oyp;LK;u9ieaItWN_`k}tb8*8 z)@R(LM?8;_{HV8Qe!p2SXfc|HwjLMY0xui&%;ySf2fQ%QYXEUpEHxU`=Q*VwL`~&x zI{V2{+E=B=vzycdy^{v1Wb+{1G4z5zCLT{dkzG*t>K^w@nAmI?bR!OJwX}8rB5dAs z0%da4NWWDTd2{q16dku_WGt5w_n~ouUu%r%*?DpJ^7uc?s8gu@D#6#lQ` zej=V^NmDp~HMXci?$Y;6!mX$D*r9E(U)qe`5f2jVm#wCAqSi86J7jr7$8ORq9=>ow z=O;wRctZc_V!_6^c#`6u4YOZlGsQVZDCCobg&u8;N>wA{xMC^1urma&-3zR@kz){F zrOP|DVkNvf5i3YhTuTiCQsLlrhTIM*#EC9mFfu!p`q_P@!ZW!(p73x|EFj3^E}^$blTy z?!{4ybL`_y+N46%nrmD|!0G04_ObOZ9CCX~EfW9Z?mSWKmE$~~l!`OtHtNt{ui|N- zOf#OFv>WC~zvSyH90fH^1oNhZYmUlVuI-1=16cEI16WWi(vY#4KVu3 z9L1J$b44@c0G->2*PNMXRdEtNC&ZGr{BUB)IVoQaoWxfu`KXqjOxv@^L&55Q=AR`G zKvks?`LIiyJf8cE*x2Ua-|%4?zTy};P-aG7t1Kr;)wPU>;Q|U0b$D~fGIDBv49C2e zVIve{h`O*pghwbr>$FY`urj1|CF1y$?@W}wa6IUR*>J$mSMb3h6#V}SK_71(Zt;+U z>TyyyJ>@w&Dkahj>`C4wOH&pi+rGT(zjBC;q4iw)<`; zXQO3tj^Qyp_`(ik$N2~rahZ*ve~ZYtGzAncTLwj6o$;yPCU$?`YS^;9k)$0lgKUdk zO!$mV`1a@&coMf56W@oSZAKgj<@-^=88;jYHN^v+Kqnepqnnq1AoI26qUj=h|9brc-iuh&W}!KkF`sag`zWgOS`thxVCUo{Wq7pf8>DT>;!VC zItq>!1Q7EoRbI96C^=bThT8sdB(BL8cHDBtubvU;`0ogc-+s%8aZy1}k@NU>B$awQ z9;ZG*C&}5q$tYjdPBZm;NxVu9{WqbRn)y4ESU!^7QWfmf=bJ$9=^C7OQkVx@zY!f_ zQTou=m^hTapkZ$lskr)SQhld`OfDUu?~TgAKgb#CUG+#1my;43O(k!W|B}bwRpB)C zAs$VfvFoY(!NYK8 z)dkw9bpo~5-KDjjw_(1{WY$X8kA43pl?tuP!Mml0NJW1LmS;7>wUcLA=hvJg!bFVy zlxvNawaGBME}E2l&7$(n$w(6%X@U9*d}0)dq2IT_XzzIZ5gY?AH>nfZ!WddBB?J=Q z27=;mQlOmji2Dvaq83JjY|n%vR6=15{gB7)jixH&)_f_b(|-WVW7&Dj`yekx|# zaC`-q6vn&uFcaw4yMwlA7mdKOeiX^ffeY&P@j9d_H)LQs3LkX&)$ylq_mXzj5XP^QvD zthxDnzF82lwO0XurSasFK6QeQT>3|~Ub9Tx(#a%d(nv;rDOitIWgq6Q&8C`$i%ou!WhIBO$%y0*2Rx(&tb~4jXekgFWGBq9{jd z(VS?loC3x(N>Sui6XWS3f=WMBu->O0H%&c>_qXq(Cac0>!?~}hYAP=1{v-#RBU@qC zwFkI&k_f1oF(~{KDIXzH|p34a?i{Z9O~GO(s{#l1jN^T~ttF5d$CW;-GbmM^uAf;~ERj)!P0K>RcyaB%X#R%WGl5mf5)6AOkv9 ztsu=u5?P6VN)VSQjK_Yspr_$UczWjv*`yF^)6LD~$LMRUdGrC)KE5IyKNeD*$qb}y z-v{!yV(H3QKUjYCJQcs8jux*?d8MztFmuXLkez>;m^EnPwNoO33!GzRk%cQRGZW!u z(klMCQ=ee#`pLYH^Cb8?@gB}r+Y4?E{rJG;9~wIw)BG(3Xu9kk(cIk2rrusd-ZUxk z&Po>I>>~&9+p}4M&pN4S#=L(%(p``6t$!tFY{usbCSh1%vvIya}}MeW`On# zWxDC$c%IepB}}t|XQo3-b}8v=QQ*DVl+Avhn}{`1wjj>)B{x&v6M;N| zv(L_frK|%^l$XJfnu&CH)hMI&BNByj_RvOt1pJIDq|&x@#J zELPRA9)bjV^Jx&79km`}sRQf&Y$eXt9;KoOH9)%l2^)&hFrLeVEiH>D*^BdO zr*sAA#~q`|XP>k0U%8XrOJ5NCyeF)T#C$Bw{macaH87*$F5{CO zczPrOjjq{}yR+NK^szQ7u~(72zo>&5zu!{T%5-|dSxXS?=8B$$NHoO{;WDQIqPHmt z1r3hOl)HPj!auYFJCx7%%dBrAL|DpY30o3@DJb^+(TCxeWD*fr{StSvhXLM8+HYW z;pxzN^d#2!bQ#N54{yd-rZG7FY&JA^)YG(I`@s5^1B%bC20HE+F|?1RyNWJyp3NS% zs^A=GUp)b3+YSMD5yqx!B|+?5KeTQ*hA#|?Xyjl9SZMu)gxph5T4)XjGVg=Qk@GeI z-~G@tK#XVkAQqWvuK43(A-FjiLUNfIqZhvyc2BKmtS&OxIaEcoO!A3@TP)sr&v{EW@b)y{a(~(Ka_{U6+b>_0FX?qi}!}~j~5&Ok_;kxxlesP>%vr)`d z6lR}y?}o`u^~S|Z!=y5xOuT$6@t_a>u8eKtz& znFpGNV`R?1RQzO9hO{r7@n7pq*Ou~d`h&|@Gs^v~#r5Ripb8$m^O7cb3BjK4CN$6L zAXxqccv#d1x3ru1O@5(h+IE2$OD}*28+C1JV(f5HRw!6mg!7&+q;&P7FSMI-9Tlk% z&dDhthSs;yR#(87b)Uq@!Dg1pd(6(H3CK>1!tcF`(BI(&19!K9$jTIy*=a%|@G%%9ioD*W`j4VoRsbp4j@A4~E;|1fr zm)ZPnH5ELx`^kR(a~0$F<S8MMSP!$@U3 zU(!@wwa+2^t9b%@))?TD_?eh6WfPRGn8o`s{}Mh>o&?T~cgTiq>8SeUHipao9va+}Zjx0pQ#7QnqMb^35c z6&NS11Z6dGUez&0LI)eDTt@^v%NAht?{oOp&=rc3%ixV$D;1)Pv3b%Q+Mh9pCq8*T zB-7PXeIYoBh>>WZJxL)o(r<@%k8FPux%rQ z0nXB>{xcHg=E>mx>6_r(^V#^De;z&dZ^NzMrlIds9o~>y6g~7xU+~50BzAo$Cr)}5 zDB}DYynahjzdKo^v28P${%}UwS3{`Hs|VTmE3{zsY3t0Q!w~-DAjh;yhRZwF3iv-| zVa|L3Y`Hc;VE*6)bw4CW5@xrPqpQkLEg=^)=bnO$Q90a>gtwjRyt^$lz{-`$L@WI) zYOZ+0NLYNtpwni&nbI$zXO|v%AkvBP;0$H=in$rMJ*tSuL09~J+;OEXL@?m zJD5C6pEq@uEQq|(B{8WAL@DS!2u;6(^yftQdsH5xY#F*XP>1tz#nMe?jZD(k6LgPV z87p7@kc!&>Cc}YN%&}?7tV+BoaCTr;wLx0o9(0l|aJ#_PzFz~W>$tg-p)5x2EQSH+ zr_}8(rBWN-*`y5YqR+<4*s)ocnUjOh$j4tg-1DM>o3ELIw|qJ^Uz~!o>+(p8QzA*s z{Xp8hUXXK~8~s7UE%wmA7i3*BcYjVWfRi2pFxGMz9{oIocVx=hzmYS*DeWE_*n8p+ zFW~xf{^Vvr4m90447#ReINR3-hgSXvnUT$8>$z;YaT3Q!GbkV}VQp;G#t`xu0#+DnSJ;rLbBC*V3(n?*T? z`Et^vehk*%E+JPc`ohmtwo&Ll!)Trtp>gbTP&BncM0{Gm2ZoxByj$IM`VGl#jHtEAX-#$K};=kaEN;)paJ2Rl0P zxjDdl23GUaY~D@YLUS!tNs8Y?GIdlGxBM=`efy;a&u^!rn8zYemTZJ++}`6`lCwbl z;4X}{dI8P)eZp?C2P!xAi6?yQUJSEh?j%b`?SX{E4jFn;3L^ zvLAC3m*eQGOK`W$l-It)kTMM%_iVi}#4jtdky@0;j>w{ zX9`eW2;5B_p;D*IufLlWHHJpjtr=fEnp6Ii~m z8mF$%hQjDVrt`i9Ijoogzm;vs{@%&3Zb<;tFTX`nxj){F_QACXk(PZpSk9HcVy@W9sj zG%KCO8Sx=pj)dXZTRr%q>=<6ZZVNA3+@UY9fyx9-f`$7oUGg91P|qsGrv*ZFCMB&O-S&`2|Qi`i;ouxkyV%5!Lfb3pl6B>>^&X{7wqp~ z?}wZ4>h}fa*P(ZKY1S5fi~-A+^p2i7?eGrM$Ye{c!H^5^2>ZWt2vOr!At~_Y%Ac^3Sq&jviIc2gg(~B zD4b}QYVfW}sKbmqHDtce9K0sS;5qL1ZFlQ0k#pP$Y0}?`dSMjZ5MB!xZF9-ZMH#gI zU)7MVlrEai8@f1`47FBaSBMGEt|At$R0zn^id2wV z+{Z4;)kBrT)3B@A1J~Wx#wTvY81ttbtT%n7_w|Hmd3!N_Iw&F7#=A)$Y)OR&etx+9 zPCB|I*21bcDfBCsf$ zS@>`9RlK3Q6qf&En73bK!LRu?X}F{XAs-%dy(&38(;zQsKN<;f_42%Gx*659?ReW~ zeIl1K17U(>2L|WAfr#br!8p=>qK<3@$6{qLO$p$#cbs3gS#c<;Z-?u3DVZBB**(=iEdLAS<;c{{}MR2)2lhQBEl$-t2FK%%}+d+iqlGsXq-xq;RLc;8n*hqSz ze~>r?&LVXmC8l>aj&UA<<2X~ zRdjlLHaQg|MYQHO(8JubU*sqZHy#pHOIwfjJ!ZJps({>7;O?kJ?W9Q_$dN1VRI6(- zXvAN!9=Y90dP5yKu74^PuL}dA-Vp1EiBIcKMsDC(yFTQ6$Z=d#or4E08;OgFxL{x@ zpR_vWLUeH*DWA9s#4Rttmp?HmHLS)ywv9n0wMwGcZv@^2QqW}`i&HoEvzd*zxQ_fq zbSMdrF@Ex4QnpJPE)74&rCxj{X0i&SVz&#t1%9Zdcn0lh4Sszn&N;~iHca(8^xdhB zA(FcCYJW%YYrC#^*m^gkF4Cam}9F^oB*v}fjvuXIS!W@>|65@+wAJOKI|n>-q=I*Z|UF|H`fZd6Nhfb z`grAm5+2v-C%WJK$gi9ypzvrex+t8)%D6hLbD4u~`=*fO2TyTSA%Yw{l#9>BxI9jd zsGuc%J2s@V#IlpSpY4tWp-Y;A8|#;$&RcWhd(sl6%j?Pf3(HZreig2Ma*WwBxDdW; zNMMk%zs>Rs>d^G*174|{N831l#P6tiz+B_{%S}I(`Zc2$@o=r&~!5_f3cu zq>~<_^WY#PET}STf#6?PY1MCU+-foao=JU7lCKoFy_yAMe&M*t%m|)(odwDB=a}r_ zx5T6Q9$Aqyh03{CLd;WfV!?mRIRz`RbA>8&&d|jxA1nlWo2zg{X&P_-j|&XT-Lv$p zI%w#%$N0y=iauVHjq(znJld`$2sK-c*Ns+S*Wq=TZ5xT6 zzn2K^G&{26mZd|msx;eAd=lJfNJAJsW63c6And4TwAoIxs)VEfLYuPiw=12&B zFnUSMJ1>%d0V(+G$XWVLGJ9Thb~XvB)Z#^koX0H+AMnM3@vu@`2r9Gu*+*aggO+bs zskvDd**Efo4~B-AAySe*<2s=F{uzSf=!C22QNKNxtnYC3}a{ zu*S{~UAXzI`v(;)%N6HIEqw-a-oB+j%8~njN+7~tCd0UQvI6;53AlJX5yQA_`=NQE z(BztC(>K$YV~L8gg%*`~R?-Wgc90K-q1bU+nC4hNq<_wCBdcb7XZ4p}gzmX@h;qsh z?6i=5a6AH|9J<(J)hSSAH%7aTg~7h?U+l-qINW=f^P$yGfdC65xY8aC&F0~->32I( zQAnU~&FjE-UI!UHw*gjlucl}Fy6CGS0ZlP`O7G@M;FSK!z(3=M7dMvTx+6t6maRRh{@IQ!}`sXhzAx?t@}J!5!67C+u@lcY-EF4xGrM22GA^qLQ*bU z^nze^bQzz&U;#z1rL?3xiS%!jqf0p+xBvdjR8ljSUHw{CurCt8tE+ z`z(9Pp^YB?bjzl1`9B-^u{7e}`Hyb4s-h8;>z+x-3W82%!MNFDZ0A1{2+gGA_`PJV zt8az>ZBFNJNQ^+2?*e-0yD!SD{0z_S7NWHw??(ZY3?ve{avH2AbFn~YT)fxVH|(6MYj1iTdB zzV5kP_t*)=`PKaL%3PQ)+y<`wWw_{tI9rer0n-&SsE>IhNy%D|@papDu@4$vbA8}AqK>n$=kd0IFU@Ykx ze|1qZv9{3SvOLb17HiDwX0AcfpA(QATZr+G8&G5@nSC?11>bU=-2;nb@W1fI#DIIZ ztL+@Zu5IgJqm?+d&~IP^l33h-@)Q$obC>+(vco@*O~MIZ+i)m~b04}Dz=4T+q^iXV zPhFO>_7*Axjp7^#yl@hvy04LkBi~R&)B=2!yzom*7Jbz-6DFK|jFwz)t#6tS$zGd5 z-XBl}hmEUwlDzXw=hlf#SFQ>Ba$X_UtX9BfuFGj!)MR2W#fR$Gp{(Lv16soU+y1;6 z#6Gh=K_d(`+3Y;oD2DX(aAh&Oo9yE1|k{4%L<5CbQ6>EcIKZf2OgkeVzF zBQ04w@ItdG##@EqwgoH|)`|kh1qFC^-COX?{77vRj4|Vd3d?uWCQsF}saJmzx%UhO`$v``gk4&`tYwY;%Ax(bDu{bt- zA|l5pkPGU+aJ&(BuDM}=Iz*AD;9!n3SH5D~K1ajmebWSqt8{S5H$y5|Glc6cFX6I# zW^mwIE%entBvD#1FrL1nce`YvAt({=G_Hh6|EvYQPq^+rbC2^?bF+Zl0LJySH~6(C z(}{EL<2TN)^KGL#S#V1Rb#ns9n%}h;%PvOIf2Mdth-2194&$E3&8V`W5)Yr404@F+ z5dAcq+8mjM&FdU-oZmU7cg-NZu%VnR9gRih$lvrZsiG#6_M_*tLv+5uC;kUEiP%+c zC%ND9;K<&u?BD9OIBU!iGzDL%jYc^RT#SdyOcp&gUlFsG&!QDq(+D3n(XJC30*gbj z@WU#fbr7bg+jkZ|wQ3NVcLgXmumU$S>w&!BKoB2=u>Q6vrb;KVVre_Ud0zmNAu)!< zzZK!a(dSrFBTdsl3OB4;jdw;O;fiQE>zX0XyFMIE40mN>wvRB!4l~3no(o8|h8DH? zVh_O+x%2K^Pe^zj1$u7pNoUkas0odv?p+>Ovi~Ap_N_!9G2*F`MleBVg| zb=2k-u##faX(RKNy}$Vn?X4`pVV~=a%fZk1Mu_W2zCS|xtRlJZWGGcSu>cBV&ckY- zMv~d&OwtcWk{KTc;dQ`gDyd(9|HhZG8T?dcc4}O!d@EAn+_*- zgh6nLENofVN0wVv!dknXaC>GGc!_H=a|Eqq)F>V8D<_jqwUzwjw|aEPsxPF{O-o?r zw+FnJX3@xRd^XL$o;+Oofr=jUr>#ZYS*s)owTAXkfzxYx_f#h>H3=u{4hRcAKTCry zStUm3%?e)4ry}~*O`gUDr_jJv(bRqt=QOpxOLq&+CaE*d!#i6e8mhU2B-QJXmaCuX zw0)vX!c={jQoISq0eyLP%=mSih#FxjN#kZZ%jT1qSGqK# zML>@Xdy-n!3Dnw)$%^v9=<{Rc6fm5O(Rk^gj&k3U{j9d_ zCOSH%hWZIp*d?J=G+Q+qc-eEHrpTRO^LFCA^vPH? z_?B#3YzubnrMT+Abt+%ri+s%jnCA8jmMr@VA$R3Ltm*=-=MJbV6BXgogg#=v)&pWS z^2q*I3qX|n-OTwh0WTU$va?td=1_+`%n^6O*=1Mg@KNr4^0*jYXHSLmTG}-HcL?1U z*F!Q2>WE9J8TFgWV~>{J%)Uw#oM+*YSqd-TYT ziZwX<-YdRlZ3ULiy^p=Si*djD1X6vwjjT?nC8RL|v^SYkzkvx5`PPQCofc4?{Qaoi zBMe*b-e=c5^g+LKX_z)E2Y3AYPEY^K;JoUSaBli9Qlle|GCVCzovq5v_2NLQUXyl> zWy8MX!cdf2NR8d4Xi@)Nwnpg~JXV^I>WWFVkC%qSz4kV?Yv&5|8x!VzTVaAv4%iAN zxRz4AVIwqO?}bak8py4yhcH?yft5VviUx~1j ztlK!N`7PWG7r?8RVPL`S_^!)U!fhcPVv=?XCtnlCxtB$e4<{(9*AnefT`2xfL6Gvi zA8Q?d(P+Izu$M1}UPmpVMRy^}_ejDFfdr8jv4%M+3t)W0avK%3^YrqWOSIC;s zOnp+~n3vJF*$)AQHVKc;K!8*iGwP>_mU5E#-pyW$2k=>LGH^0@v`c4Hh9Ii}uKDLFi)4SlhSt^l!%k7#@ zgFyI07A`LDgTsA_P$KL|&I+V>Yo~g_+$)2qlYE%I%uuGU8heOsqA&F5_b@{hd06~3 zgkNzx9JV(-Cn723bd%6;^5K&#S{H?Jp7K)S5imjwpRQ(Q*VMwW_+zG@H^^$2R1g|_ z6!_0}i*t5mdY;G0R+1UyAR5S(ME_qlh^9s`3C18M;4W@djQ1aOy5^kA6 zx_>C-uB+zNydNVodXhobQHA$9A&l;OQ3a3t%IFd+j-7p=3s&dG!>Lh@uN$qvn{uZg z+}GVAvX=ibdkwaO=nOST-|S0PuQ4PJ?-s%@_y+#5H;I+RL@3J-gDwA~=sX;$djB|% zME0nV5lu-MA?rM!OObX7NqY}UeWN5PD|;kSRKm`gj23ptjj3Gd&ZCrqNsEO^+t8Xwf|1ckFN_>avrcn@1v(xY)|JcBY> zURGi}jI1-~ojN;(pDX!{e9Mp}?jna_k)%G);T_@ae;)%=C+CoVO~Sm_-N~SLU^ZB7 z4x$#D9??C6ay+Zu>v?&*GT>tO3`U_=64IyKggXHuymsDw5^B8(6*mP?x=jJ*ueb-F znwJpotP@t+l5ck~`o^YJE^^q%RzJ4#~+s-vJEaJgeUvDDK zJHxxw-$$(S>!8r=Fqt^b2kh!pA>P;%Zv@_i>r=a!mFy(o`>SGTRuy+)e1I}TUc}x% zpW`*JAd{{uvTmEy=#)KcVdI@RtW-;Ze{Y{NIre1`vpN}WB<4bZnkfIB151}V7Qn!W z6j=WGk2J+4K*fJ5aMJHR3CYyNfRG>b(V1NE56_^J%2gny*NLZ7TLV|Jcp#;$PTDqA zlX(9=req)le!k6TXYsAkF`f5p)_{vKPX+ewGI_K*)J4dKdP8Oj{)V2=K-GB2*> zSi~D<;Qpk!(2yTOpC;U_EJ#tKrxcRlkZ}lJ$X@}A@*+5I;}|o?N1pxk?=;`3{~=y8 zKY+{IQaG>2bT&Rk1@up@r9K``c-VM77?hfm7;awnxmKObln4R8kqrP^zewjLW6Il5 zO%&>uQ|C-2FilA!gJ!c~k<@kaZFdAIv`ocp0Tq(8!v%F@)L`}{VPb5Y&djP12eY~1 zV3ZdQNx#Nex9{Oh$ry#N&u&(V7ksDNu8UynVnz1Y>Q|;Bds9$xM?g>hB;&`oEH`72?#?EF6b6E+Xg1 zc~Wen1-aR&IL}bJ;?uiCE@L|Z{yO@T4JXer(=@ut>a`2Wr`h?`O=%BFz2r#_TizsU zBXV$I=pNIwxR2yF>ao@j456krrShwo05N=Ui>PFjlFQ?HP<7l1qB9h6miKjj=A2EK zQQgGg0YlvTyqQj`6$7z~VY0zT4+UE;gR;Rx`q3?j#^g%j#7RZu^w}tQW6q;bOuW&r zOorO|1z=_9Yi82%D`ck_=jJw1Li^I!7_@4H-r5t51D3MPg;SoD6Q}GoH*m~>6Q}lq zpX(#C%HA2TKA1%|=5x zvIU%Py^S0lct^Sj$B>gXf&DJq!J{gVT(vA9kMBOC@hxeU-d?xa6EB6(H|`dFp%eox z19xC=RWV%X-bL0MDdDS@0;(oA0Gn^BGXoZFtU!|w9n`lW-&S$`%i9;pi-u^>zO4g$ z`MLDTzEpFwjp0=H!d{p?bdlPZ`rzl2%edJ=2P4k8COvj+puB`(SlK2|&${HIO)N{5 z&8CCZ!?`q0#gv!~F?3^a6(gqknXUg+M6~x{cG zGhiDoUR{pT-B!F!Nx6);+b~%l7Do?Z7FsAJQd4Hsv_RB3J+jQ=+ z*`Wb=_(%yebN@II3Jye9_7WDBZotv4tKdDq0d5u!5_WkV_LQHj^p{wQ3e7CZyi$P| zMfvpIjT2be@|>x<*TwOFIljQ?6)L$g4bC6BLl^XC!(e(bURl45sh=&0ak1}lqwgys zzU(FDyjaCgIIKe3+Jmt3`5^n`KW*mI>uY#iaSoZ*d=AZQwa~_(3d4ho>6Xzj{zz&M zyFeiZ@BKTEU(a&;jSW+%-i>swAMeClqWKE)Z~Ab&`n~kpfkUW&;1Y5F@(xWqvw3X; z61+v$Y4~&R1-LFT1zyjP;7=7RCY38bkX)-Hn7Vu~^!zo&z>B6Nno}IR>@!Y~Z1J5$=~!U{oyHx^0$1VwOkL% z@(S=})(XU$LAdh58r71oaqN;t(mbo1?p@o1!%4az)!Rr02WF#)b1%o<$fLgdJkT`$ zH*=A5kGQmdB$7Wm*>TMr*4=Rleldz93J=+9O+@?qwdpL)tyvB zwFs~7s7AYcxw!abBA1Wk9Ig+n_>MQ1VVckhTKCYNnc9*@bvd?O{#l?#G3jWu!+=(A z66Cr#`>5*X1-yj%`)JJN3fQ}IP(AqyUK%Pv>(`wa?FG2sa}RFe`El+6O@4RNGxk#F zRjmA42j(#c@!tGVoVdM%`%KQGdj5Vicou@vpGt^sMjG{dyc~PB*~8FsB^0q~CEg>| zSaIkkTE2dZuU5%$`5-`6_d0&$*pIbM2T)tL0X#5=RF5ieq zUveOh7wwqXQ8&gzQU>Omgi~dw9CTl-g)?*aVf0gReB-(jyNC*n$rb@q!8|v6kfU(Cx&>D4%<_KNE9m|BNR<<(n6cObq zUeGds?G^zOCiKJHHC~*T?I`hgEXQ4Htu!z&k;n&3CHB*TiH5uNIS$N;o>$q+!mpYd1+c4iq4({ zm3+aHt`G8bi^`d%)lQc~F+lMYEkbFDm_ja|y?7Y~G;{pG|b z*Opnb=n@oRQeLk2%xHZeb>`h<4$n83rO&*-rZ9SHmPge{uK@iL6e!7|Mh^Iz*S_X^60K-e!B zHslO$pc1sPM?Rkvd{$-q09E}FSjJisDzb1No6ma2i8y(srZGL{=D6`~u z488QCg~@3fCtE{K;eqH-bYWcZ-?%+BuZf_|VhN;=^A2T~Yr_4%EZo?7gFCM+pog?0 z;CRYn%&=b!2NPo;{h>ZAa(_#tULQrwD8bQKSyER1yYfkn4VMGwI;!0YOzXc@JbuwH z(4NI7d-ZMDnIV_ZRaBF%SD22Mi;78Fo+TcM;CL$)t~?2eZ^YzB2B=^5r`xtti2r7V z$tl6`I(L{duinsdgIKEVv>bmr^G5wuHnp%j!>*m+Oya*pVhLM~H|l$lJvp1p zK1QH=QVy)kF{CXYI#lgjos5> zaC#)8KlGM(4elaKcdbST!z9*#dnbslmImAWiU!sor6QN2(N&&W=D6Yd)o^v8<87UuG;b4D!b~7I2Ly{&h+vDd#Q`>*(2#WbMf@ zH{2L?EnhrWW)4GJ2|Hfb2{&34>5YAtVB&2Bo`%|GwCfcirU^Ry^e1^BvrYr9`G6_u#t;`!U!o6>j=;<4CXj;w>D)oFl`L2{p zHihf~lh^}zx8ndm{(>cTKg`6UhR^h!PbJgfmkUDqXW;$gHPkaLn#wx)!F@9yQufQ2 zRTH;F(SRj9?fR=w_FWz}giPYwY8diYOr8wSjP8PahAi0o!82jonva#bcSQNC?=3_< z*D=uJI*=v8p{UPgY@(HQ_8TIs6SXKNIQ85Apc>Y!vvS0Lrlf{F!EtVa>+3kQ6kRIW?q??%x*CwoScsMr#Xx zTeOIG_4gKV8as^7Pv=5f=Uytw@%yeRDS?Nt2Q2Ob(3aTAPwAWtt5&VUnJ+`Y=$9Zb zB#q<7=|$tvg%Fy4eg|o()kY6NH6RB9F!Yfe?98b}Z`TyaoI8cz;av^(Dau&1^Dr7# zID*B=cDPn7fC3d>@YpD{GHK-pa5E~PB_|cB)mbf2xvWW!-BE%kqWR=i>qR&ya)=jr zmU}PO7qEw>3gcqq(=c+v3;iC-p@m8)p0QTI(=wDqyS<|xMjPR~v(%9JL#_K zfv`khws_)8Nc+#DT(C$8yc9KvLd7WBzZ50wY7YTyDJvniWmp!7_VXQT~`IH6LMe<$OuhQB!RAGa0wKuLSi|#yndxiwVv2t#_e&?#l8uh;IVN0eSLm?tdWk*#|!NpQAmJo}f9ujMln+ zg<3~H5WM~apRPYnmoFCPeQUE}mbx}@XBn>JpEw^EZHZ-fMNJ{?IkO<(`&F`L$9r;8 z%o|sWe1-WAWAt>4J`LWu08OvVCJtfhaB1y&V(B13pUy2HKR+ddV7LUEBh-M2Vmc@t z)r+QWM8Zf#Hs`$Q=7Z&-(SVg8dpqOreL3`{fT0+^g98M$-K2mPzcshq|a26pRv!=aXOQ zGGO%TFIJs;MnB)4iXH>ksiMLp-h_cnt`Q4Z<$Rv}-O0lAm`}vGr3^$m z{MeTCC|Dg#K)E!OoRpA+RC`?1_B#e;?U~k$*DKR)WDTv=1lR->x)xw3x~2RORiPDL_bT5MGS50lR;hjM-5uI{Zul z7tLEjwGLF$RiCQKEI|j}?VYQ!l1&DoS~u){egXcbOoN-&)>M(7O&5-9Gmj25;Z!Y_ z{_{6v3Wu$5e}@ouDw}}PN)dASTM#)Qs6lqS#N$iH43U!fXLYT8o>x*1$qeDMMMd~ks76n4VS z>kM=@j5Eq7oOrrYFCf~ukCpws0v>9<2d`b<%u{A9<&|6s2TzYT@IX$N{yaRDr?>wM zR=sV6iGfLAXU!+kkM~1Ok{$80O&}H#2hA0NE13yeOIeZCEAZ5fZ06~yI1t;l9}I`f zNW|?ta(lNb+U_oeHh}?}B7KzD<@jS#f0 z{pQmsb9W19$#*jPx3b_C*9C1{9l**dd&AmncU(3$4}wH%(D?i+nCVl`?9D6$2Rl93 z+b4qRr!~p>2F~$$ViU+lv()}+ym`i}5^T~Gg#N8UIC!|O;_2pO$}t+)+8y>pbk#$u zHL8Tl=ju==ua>?{b|e-YxAB~71it;c61@v|Q)}M4ik6Q7h+m!9tmbVXqoo3CGq~O4 zuK-kj`wrLYsq(H*<=80-7cqJB5Bkp~ieuez+|>3K`giX|%8j+~*F{Ivm=M8#?*52< z6<`7;7aZaI7a_d z)wHOm=8X}R7@-5feci-{<7a91=9@)~KW|3&r?8Eu1%Nr3T$EEXG@BxgXUNesK&x36vjB#)ZcPN#)UL{09#G}6DNkk# zf7YHL{`=3v^iUd~wYx9Q&)8_le=+47tU^z?{lSq09civy&hZeU1)XT*q20vp9%jVTy|$KoW3K5Y!v70cHk4a zS3}SongGl1$kC$51dkYgC64w!LsSa+;h(Yr2=je$w+^QG`Y^q3RC7d2)=M; z#RB?xPC2{tZ3n*ZyMh*~ci=&*5w#T^r2T=`xFss|xGD2jATO`*D%YuOI7W!x@6jM?)? z2!nT>C->i5@k`azY1r;6rs#MRxis-Evnp~O=PpiR=Pu|+6Y&Z%<;fO!FE$H($vU#x z;Rv-i9EOCA0r)p%4%O+@r(zB!WQ#*I?HHJd>ziU=yi}2ji@jrx+@HVV?U{uGotVIz+nw5 z>|vtNLFgOl-mC@317uM3;0WtjZ2+II8sOfR5pwK66BSE!WM{7Vhi$h5Y4^e|^xhdu z^@XpM3k-RKeoX951vbw7NKgHn46f!k$=9o^8SP0f=0PDF;KdVV{5VK4*k}WtP(H+tUbcel z)@?9f@LT1UEdmfRwUrogtkVaVv}vn#8+&d*0BX+bp(WQbOl*y26wKnu!&N^JI@auATr{Xnmk;^Sk~eZV znzIr&UcFAEV*k?2UF$F)>j!liKR}M&$>e$`jjYGpO?-pnpK0pU5#plIgf|s0P#Lb{ z^C8_Df2$7C-jh1aLA!J;YvG~ytRuubS%o-M=fhhgj?2W`1f$0y3D3?I?KJ}6#Oou} zNba{;SVtlXmrlc5y*1FCeHciJ7o)xcV5yM|@oFg~&ayL6>;8|*oo>SzW_F%fYF99B zT3;FRwTqd#;V3b57KNf8w@F;IEU#iTotE!hhc1UVUbUgWY9>z>4ucQrA@@3J~L zbNxAq&w=Fp-08ebJI*Ik=|;8dr_zsiN^n)dU3yeY7OhN1X|k39jH+bQvhZ*8TZaY& z&wB;9FM_`&S_)4zw$qA>wV-_JDPct`fk)ie#!1 z;As9m^$n?Y*?{3~b!=v97>+B5;z*kxo4fZW*{&^wH$9Yimk&gN7j?9ZqVHHP59i~JVtj2B5D?-o2~VQlMLCdbR7I5=@|pc1 zX?W^_B*FMT8ddac+Ya65~i%c#J~x&uh>>VFh@L)PsF=DalkDAt!|& zF>{+O;60CHtw%Yc`dU-q{gL8#E~-GEf*5>RGKHNqZ#Ruk_lhVXW5}CN7Vn0~K zu>RZ<=(;{gk1KVNCK*I$DNQ`Lfls?z&%v8O59ppegR}=0;p435?7gR=PKM0vQDqS*&&!j%jw`q{$bL$-jH+QM#09wx8(i5O?0E| zHZt>62Hu*b%{Qp7Cevn)(mI7w;(l%oc1%$L|MF|V&kiR+q#6fWcVP8o&aJuXtmz$- zD3~Z_u{uMWz2+mt zprj-zIh@Lh$1Vm=oC!Dm6!{`rV*H&_f}oqUoG9&mK(4oVGmURL>Bu102>@02bwh%X zB@5{?BXQLI^aFh6-=!*wTvph@9DSt&u*8!GKN`YF_(&d|Bp(Bg@50IA<+m^(;2(W# zl8IV6MO4UN8-fK^!L&jhXsF-L-#EVn<=X<;_+>*(cy1DTa7>b9t>dBA_BRx(96_!| znlIqeM6bp!p$AFl9<_1tfC0q4Z`KmfU;hanw;cy?+Lo}cMn=+lB z{H_DP-07jWJk3Gqd^!HuvJviXoJxC6Y=$SCW2a)VDJ;D7oXTu7M%BA3iGlij61~}u1lKlTa(f56 zOnWyNSS`YNa^@iA?h9eX2hr<*D$ep1rJvTWXEVM?lBC`Mn5nzb*l1=KI08ZDH02$w{CaB*JhiXJzhMM(%XSls z&OlfdQ^cA~Gsl-&*`RD?0m;SVQ1)pF4%v$EjJ94!RToukbP2`zf)NB&pHt2pL67(A z(YKN;HC&}epOwm^qP`#)?0CeC%@YRWc~zX@s~9^kMsoP7dp291kcZlCe=5C{xpSw81Ez~Rv#wdqG|)*Hmn<=+zL_=T z?59ksS3dz?*R964HDAeiLK5}%7Fp{e%G**JffI&@iAu2? z%xK}mi%4^R*3)}je>($@>HFi)f=G7Y=__j3r-+kpE0L`Kb5^U%K=zO+zAk;uyr|Ly z(}w4`r8pFg9-85knMyb&FN6=(gK=-NFF!*gg8x%(KFqoy1QEq|X{`7z5K*~;k(M9u zLGd)MM^`~k?^Xway>sFIvQ;=}8c6p13dKz_{~C;Cz7-gC(=1bnzu{+3l`tkfRvFuVkMFR z*B(b>Z)7i2i}b;I-XeIy)s&{|9;Zb{3uuHJ*Gt>mNF?|>F+g&h7XOyVT`hI6b&EJy zrHb=11+SvKVm0OWD%0QP&8Bu7i|(tNK8e3A1AD?!VQQfgnB-@`>Pu5_|8N$KU3!@e z$2cQ6<)XtoH`3Q0Nfu83NF35n6R9YB&aZ6DEV<@KeSgp7P4K9v37?!WaqUU2S2{vV z*=?9tuZcmLFNuP%Fxt9TGZp9L;PA&fG|!#JpJq}5-@F1Kf43MFXt+wmWev%Xt=(jr zpE_2zGxS1v4c>7XBwPZ83Kks1gKnbSOnncw$u?ro!6i_!@-chi)O>8L7vS$YD30fj zc9VqyHc<6lAA4rI(cFt^#3D|Szt6x5&x&ti{^` zpK;l|uS}tR8r{O>Xih|$qQ7x5F5g*B=6g#q32s-J!vzHPzIaUr{6+APC!gz7{=q+w zWr@(ib-a^SSx{gxnJ+T?JTCb<3;ztfqo?}ff&H98du#+4?PFz7(0K$mB(5MomV5Em zUs1$;-nVd5x)L0z&*T`-m7qF#CRrMG0?jlPaYM>uSRpUQ_n72^2i7TI-mzAyGWlI) z;Dl-BYtIX@vm(Utut^3KYQ{0*quSW_R)IXTbf&*9e8iA@JK=3Y5lL##0`biaWUlHf z8f(h&u-o!+Xt@@YH_fDGzi-fe;-Ti@bOuk2*3ljR$-xpaMY`sdA=phQqKjsv!iCeP zAU92sp5I-Hzb+po!JY+RY2VBUb2FY26&<|yOI^w)BtiNI zj&(=6snj)R^0w_9Mry^wv>at(5vGSB>}rgi-iChSE3h&Z@Z=0*{+sQ3IJM&!b55%X z|BK@@%C$ku=?nbK7`v-urRVZ$Iqz{svop66wqL5h(apA66?2 zo685Pq0opkzkU5N=(25v*Uk5E;6N=kH?hb4VN+m1?-?A@@CUN@5uH;Wfzw{r(E~z# zWFE)VRk@=Jx_1zbHkT6bCo%MEML6eUk0O~8+d&?IpKH1sZu(Np*&2(ZEBl~0BpXzm zW%-7WCh=2= zBl;-#H&q?Jj<`vR-!txo=Arf|>*WJq^+fn*2Dw?%mOHG#^nT3b>A{)+IjrWoS*Nyd z=I`*%#{RG%T(VFaS`WO1z|t%{6K;yX?PU1(MW*m$y3XOz#vkmA7vYT3TOr;F>mA5T zv%tePo#ggo4FJxG{cFsC9GfRZesO=_ihGGfVzi&UxF1QHG)(w+>|5wQ;S*?nER1eR zD#jTWvapcr3Te&zLPI7_BW7VdOe~6q=)^GeSbmZQaO{F3f7@9juDh?%bb`963ez*T z8%S6BA=ovlfc!jRRJl<`KZs4`%PdJGQJ-Ad>^}pTe|Z3Y$d^(*ht2rswhUT-R=~oh zYitf(LOd33re~uxc^_ma5Q%O7aXEfplI1v;=x@6~C;IsT6?G)KJ(5_uH5i=*Vlnvs zPDp;^0)A%(nZ8}WS+gQJ@V@9u6$j7Ljw&rEUb+lZo=t_##nG^Na}&+nUjxn~41Dm@ zXKohW#=6~2?BR)*;nQQjd3(`h;`eC+o$qaj;klAvw0$>LR1AQj1CN%iJ;kgQmRvOi7wZyHj^sw=Q6r1DCaA#$E5OP}!U-(PlZYtL~IKGq4<~V=_vQz1>R6E_g zbt$#Vw?X}$Ow#=!mb~Jv#TxA(j%DJH4sRaPhz2`YwWyX@Pp~A#CmmRi{?$ZLbtXBk z{GM5ViR;ZCKSF(-%J9GV6+j{hwTcfsFWfIt*V^oUQP*am(M4c1`8qZfi8OOcE#WqSyVrFoLGw` z;DF&VpxMRLvRe}ScV^M5HEMXjWe|t5`bpS7iWj}_V(Haz)Zb=FZ3-UIJnV4UQUgMLfBol{-k%Q5~xp8Mvo`PmETWB5aeMqYM1!XFXJm{#~ca%8R2=jX3GIA(0+iS$x9(N zXeq3aPKIw~Dqv^PPn`Ee!NdMLl_|UQA?cGGv|pRTd$2@|cVzApn)&7{tajePe2JFf zX|~^^xwl1N{8=QC*!_%j++|Q<=QD05qXO+I?WD**8|4$OvNv88!9VHSP7TF3nBrr6Uo@A-d%lAe^}eXhx$Nvp3HdN?gR5`uhDR08iCUQ)Pov$1R-JnZzWSnk z#f_VxQuHNEmsce}g->(bLoa$I)RSqc66eiu|41{MCE*6=L3o?rK$`6`$mwtWbhot) z*FQv0}-Qe&eVP3qIB=6UC53(Xd0gbsF z%O~Mvb9W^%{-4Qw=2+SVbjc{8f@wQRnRzT8y(q=1{@+V`eJbBI>jjzODg>i4C&{EQ z^7O!_rO?dt#RbJxprm9??!rvC`rQRwr+g>6-D9TOs*`Dp-(8vj>A;u2iYHRUcorq| zad8?CPweD+Vi#}Fwb9A=j9kL*56WQPR|hD)5pr4+>3^M9;fcmhtbTDC_jUx6AL;od zb^#AiUmd+pc7W}V4%)atAK!+xL-_gxqUBftjh2r{xt%;u_hKPYvxtOiTg*WFYABuK zU(QH!-;HUXqo_~!L%8R2g1n9WO+T1%o+2ZC`gset+fJMfvnNwL@n0oOKXDVC{;h?O zwq?9G7fW$Y*JQjjHwqYgMYd0nW4S?9Up`ijC_&ZA zB#<4F;!T)57hhL;W57TKh|ehmi#0y5e7Ozm*mVhgr&NK}ugRFMo=HDD#SrOr5p?;l z{ZQNeg?t-mC+p0|$a6m~Z@uO;ylp>Dw5-;^&go8!%}^tVzVW0YNw#P#pvxEQEM{C& zxmibPA<=v}0gTU{g!s}gV1GoCw+X@$A<`qA#8?_e7rXK<4 z&ZXd&a)mgLi$TP^O|Zi-i!J5bfzq4`_+yknc5}IjO&3c^bnGOU5zTS!!fyeHtpT}} zQ^0<~eP*590!C+!3G>L+n3a`ZPyS{_vTxQ}5;==|mGL@4aHCZe15f{>cTX?mN4aYA zJ>({mJ?9par31ql={H1^_Jwd>jQKP-TZa|?ub(y#%TeaebT;=$IXDQIfZ(qo;(1UD z3_gUhR#1#zHucf6@o-XJ%BTC!Ns)y)1GIfOpBVoP!YA)0l2r+5WZ$!iB;;fdY3Mo) zq7tVtd+AE5F=)xFXffuU+syS=+wXuxkv>1s#0(Z%I}xXZWsJLa0kc&04mo{n8Gb7z zFef$xbvBPfPr*fSd{IRkAD5udan6VQ-wT@bV-R;$<xl}Rs(pQhRwQ*rWw0qSEk4|Qh5Ra|SF3#&ICsZ4YV zCaZqV2bs|a?4)pGBC^F4i@71qz;hn>8`sb&?z+@)`2|q3(!)K^R?zv}v)xcj6EcQ} z=(G6)z>}O#r(2$-7eWtl-#a6CpJ@Z$e`Cni=u^1u<`1gw{)cSKFT`#+hLrscq(AQ; zhgDnG5PTNKJltu^PL@7q7OIlXthhagcUz(W6ykPKJS|0|K6<0k;{!x$QXz9rOckzF zT)^e2KCD;RIPr{{z_V|i2CJ23(5$pH`f^t)gAOb#vWTRwQV&5^u^1+wX{BEl4zLD2 z$hZzGQLj_BysP@2aDH9@9$Xv<-~Fd-v)oM{cB*s-P*DrUVfS3RK2msJi1JG=~@{yC)g)t0I4Hynp4-io-H zd!M=;7NHfBPO%%3?n9ebEd6-#I$4(>&KtgC&Bp(JLu4~j7!Sjg{S-U;-7L?1EVr8o=!%ar5od4P1SyAFt%S(_p&i(b1L{kBlFo;k*vL;gqQr> ziQO_`-e(bGPB&zg)~&%7C!PL-0<1PYxv9!~(k!C_f~`FX=dk|Mze! zztY49>D*lFWjNMe;xqd`|Ks)|3})>lI5Hy0@4J?WEe7wHnq6l>ZJIYR+P4>%Zc~xYJbQHX-TF7Jx zoyE7f9_9R#n9t>E#KY?m91*g@iMwX<{{*hb(a|N4>^q;mt7*blJF}Wh&&lR;Yf`+( zsXAC?v=9!r3G$vuO7b0Y6nHQ1*3p9Q%h@thI|XQ!%zN3S8h z&ZR5o$vBeY`l)$*#ESa0AZHoJb0JJLul0u|!64l-dqYM2EMxXkCmNn5_1}*pzG% zsH=oi92eryy*^r#QODl-8;{#K$J*;NGZ}47ZS0XK!>VxsviV~>AbA^z6_| z{Qdg~8TI%~T0Sc?J~vo;BE%Y6wl`o&3RaGrorMNtJrL?jg`5sYu*}{wlV#2E^p9km zqme;&9+Ly(oGqk|uT9kz-@tMbO{`X(g}C|v64ko}1Fxvy&5dVaw-ZCYbSvRdgB2AJ zz6*}Gx%t_L81%Fpr}VidkjI1uCvn+CwQLxjjC4no8@P-+12MODJ| z{TLoNVhK6F5!ZAt!{$kEsGYj+uy)R_H*8 zy$Y@_p8&lxuF;`I+?gVHHfk2npeqhoaNPwz()IQ>%vl}>Q*TJ{&!>p+5{$>_QSpoT zui6J`b%WX5Ux{$q(HoP~7ZQh9Exz95CNSQ-1?57uz^&8?#E*7Y@=e6Zfx`c&LFRgR zkWmOFIXCG`D52kfi(+Ggk>?XI&Z? zztf4)BUc~?drqq-<7*C zO1y_vav)?z>MMA^OPv?hKLKiUE`!gPTr67>iRa37F=|^LzEA984qm@azOE0%OVi`P zr^5k96O+JO~vz1y|~$neXrJ;?{$k$?{ZT^5T^y zX!S;buzYK!V~q~6?rZ>6lQ-0TmonUu|3gHXP9iFn48INE(Jk~4&|l5mKCS|nUieE+ zr(L5u%};3XgCcX;|C&gh-XxBB@q&u?ox(p>M{%360to9(vYxp<^)qVLhmv0&=kQjD6V$#^!}aNBvh!-_yq`AeT-Nenc~6_JIp?>%s(Aq2Z`H#A)L!HDXkaB^M*Iz zdd?ki9vWn=t^W`q@kG4QC(mo0l?LB?ztaz=s;Ht_9Q~<~3hv&;FlpN#y8L7ep4|2g zeCxxJ|Km2OJaGj>$vGq>S|7ZZD#A^Lrx@B6N!!-y(=gXIhKc=3SBAfUQobd5`gtLL za0LbRpY?d#tbjPJSb+P2o50}bD*VtM!W`%3K2w4>lBcd4@dL+tczymKUACdJGV<&R z(lK^`ObP3P5N=jMst7cf=Ft_4%}7`Lb$pz96m>2e(1UKN)Sy2G?Zl8M@(vKi8{Lo= zxrg~9`IY$9Oh?C`F3eIfZ%~@1#Q*tN7v9QE!%L4jzJ8QHB&#nar@Q{JVQANjyx)8?{I>I)z1DqSmoo3WQ==D7pCrF>qG)g15pZ$%PKG@8 zfU2Y%TegGmuzoyCHgEBT_o8RuO*qfhbn~S{5~;XV(hVM7E5XQwcZ_LN3}F@5pwEFl zC8XEVoMW2|lU%NDr!x z0_{;{*wm(k6Gl7Z(u=2AsDeTALFY4lzayRZyGDT2(>+krpG*p-SJH8*Qe<7uM3}Nh1^ixV zu$n7RVMIy{b<~bwvf!vwt!do`NU{2nQlkMN9NMi9*%hC);%CTu{5=nBlFVMf=Xo$ow@W39Pw>M!P;;- zYdD@U46{JxiL)T_SOmzeSWilM9^7}gY3TegpV_ud4n1rnfV0)7t#zIB@^(INm0}6s zdd2DXH@Eq0!cxxPT?e`6`b6-$4WEQuA)!yB8Fk-@IP;Y^ifg>!<{R_;``-+fj&+3L z^ftJ*=^{0qFbZyZC1B3bDt?~xAKJ~1gCD=flXmZOpuBkoU4K#s-sR1u;xEon>r10i z-Qh9R_YC0M!!B4obs_X$TSMX&8(19f@&c>KBiOiiH0j8@P4Ojvr@1f{n9%d|zMnWo zwD69rw`XAf_%^2R#yXh$+PU*Keh1esZR24`Vif;LT|Jb z)14>A!kRq~$PC+LeztspUg=229R6HXxdsryb{k9)Uy7Zj$#8vdGN=|u;KWD+I!;-e zoSjz(`fF0GH@ak?l za)C-%ekYE(t}J9Wc{$>gZF^9W|Nd7_xX{sJlkxeEH6-G-Bps|@fyC7%Vx(?Psk)<;4(jli0`XtK3LsRb;TaqZU=yNwar&21uEPI(tR; z3*ERz0g8$P@KE0{wq8i5KQqVE&An=HeKzlPDR@9iW1qrV!8t1X(1*QXoj?!?+1G2x zb&XjCxr?_8MFIeS&-JmGkgkr#7rJoP*zvIX<9Fg#*}#a!d?c!&zp$C_Pt5fU#8I@B zif!q^l;5Sy(=lP-7*I!UKI1bFqKy{m+2Z6`dI0gU;aRV@c*pb4C{Vg-j+aeJu3QO^PV+mHd04t&-l(9%E|(v^%YW?V~h%KKN6YlRwnec6Q+5qf$q~Y7Ip(+ zq{BlA?SsWg+wCN7+_aB$Vn{kvf0e`d1D@n|WDBk8X~V0KfsTpgoP?+y@os+2KQES( z#JoIQJ0M`#NzUB1U(?xb{;NrsrUX1N-bcl~{v+G(`op-(r|D{GcbN8~mSlt(ky|B- zMDAoWIQ1XJEoN)TM3~BocibY1vmEiYT|F(cj6i9hMes2xmDov~;tFE@QJ(kC9eTG9 zZhSAsB!1sg+&u+fyGh|inGf8_7JQOs~pxn4F_X zCH{7j%-|XLW79Nz`Zf){SH%-E`4l*(Ac31sS_zwnm@P@Af*S5t~)|a0^X6hoOxh-MIW+oH`IzfA;B--kZXSO*9DVA zLG@%lsr{b9th z!uXOfdS-(yE}D3iym`2lWZij5ME+YxmZm=?#wp44Z$5`{<=4s2u^0K-^#k&Iz8v?k z&j&SO**!N}v`Y=PSaWP{RCM4M-9>^Ma|cja*T%qj+c7bTHpyLVnws!lI4u&~e`yYF@eqPug}smC+)6P;j5@+G&fmc|ziO zc8Ga#!JkuB6yuptf#i0+HCFcAfWP;%$lnELgll|7xbaI8@su4uE4)#`9jN_)@*vbLsaTS2?$wW}10k0WX;ap~EQwe9fH*GTeRKwC4!y zeW?h--1T^KdSy-Or!MnXUyKE_Pecm1?sPC*1k@z>FbM5}2-EtH=Cq$e`>Z9fQS=%; z+@5T4_C}dS->x{k#%ZDDVRPK_Sqm;dHKmTH*r{b2yDyWxoYrCrnnQ!|vDJaJjQdS0`TXVMk}xKG zKteckt~jci8qtIE#D%H5=khG`M5>kYOCa@jHfk65(&;?kfRA-hO#Jx`f z<*$<9`YIXB?avA8q{p+mllVOAu}D6>0>iObvT z=-YRVpsWwpSj@pQf95lX!V;P7l~aXZ-TP~FA76%E^&OZs#fAP`VM#Vf7=q5KBJ?ZM z#ngxnEDZC8$#3tGdE%EKegH{Ks61{TaK*jKjTTefM8NuE6#Q57ihA44#{r9T^kSX{ zZj#;%AM~}@Gt+B9*Hjz69FwPu78&FIPv2uDSJ(e z@h>Ta$qnnMQOS5P=>1IlQWaS7v{(!4o*X9Sof%5iSkV72*;8x1bb3lA2Xm~QSQS$# zh}t3)bgmVGm*)%mJY_TUY4Rbm`Hc}Au8t>>qo?7y(_5k2a0S|jW>Aj`N=ln0apw5~ z?x&zjXovIg$Dd-XWxMFNm;}y8K@7FNFC&E-O5|%zGOaBvhnLxP0*_N=XmhRt9XdBa z`s`NDdF34Zqk7sR*>oX$!bBFA=|$2xYm)FngOgCQZy$6oN~Xirqo~VIPrNm&%vGh3qbR=Ib2?}6tsHJaYh>E_baGha4WO;TsL(y$f1(5epDo6h^yJ4 zjZxNB#OZ7@{81TZ(&ssVQiv@mPnsaKW0cXWK8&6}@Q#X$m7?FMRI)&jjmpPU$>nkp zD3DQsnR1#S+V~dLwrNrSXiL~8xfnFZonRjGzK@WVMjAF@KASb<- zi!xP)v6U;KYJoL=iOivwB6AtbO*7!N!8r2umnYh>lmvShQ>&ikIPu^#5}MdS>pjQd z+h{wmF;*tCV)gm?*d&xF8zi@03~?(e{*e+TS*ltz39=hgz@=y>DXcn;pWQZL-r!Q$ zsyG4kcb5?Odw^`tnh2Lvsv$~FRT#5*4K#U}!O96^Na0cb-PUI!tk^mkm)!jhmVW1H z=}{wy_qS!G{a3ZWmtM-Hn})sGBe4378s>D z;$GRel7QWJ=>B3e;#Tm4`g$EFR~uhYK~g^JX50-@{NBda_XbnQ9DzSaCy-dHS6t+$ zDKLDmlqr3Af_kOgwfN@rlK=c01IPLf(YhnjP;Mv2+22za#2h`2A}7SqVs{qpc&Z6! zYX#hC=@;a_s~AeYnG5HJKQX6YSrGAkV)TVZIkUUEmIV2%=6vmM(HBlL{@>k(^#v~2 zkjawKb6%0%ue=GwYjEz$b=V3o*lix{_={|IUoWcs; zN1_4Ve`FxIqmz7SGJ^U|DKK~1Md~^dNtJ`5AlAu=L>5kEIxY5LuI3B6f1e&UoY_i_ z#~J|PKc{}oTt(Zlje{4DsIcQ5mHDVg6VtTm9hY5n+n7>*hS$hU$Xg4}vp$g8l)Z#0 z+5&>ti@5CZPxzjRobZ(k&l%8^hKr{j5#^M<5uClvl&xeSGj|Jm9H^Q-Bdi);hD*2c6#=_{gCTyEm zL0#!D(rY2j{s`ZWQf7}yYUBzkiuD5HL*l5vtXSYAyNyj6y^arjJ+N*#dQ*#@{cLJgIz-*Fz+y6CQ|%(?|{r%)&l+jpISM|cQsq-|jY zYbQhBm{!uHu1o)_?jTXhF+BVBBYDbhLpDC0J}p@TU%qKm9nss6IW7lpznljf#y?|L z?0*SovmX$@%ah39ks5M2(il>B{#jUcF%iT$3wme8@MoZk)ZM72mYfb5U*bbcbFJM@PKD<5Dc~F#lz- zbA1Id`x8dnuj}Fd+D$OIauQiRlqrZbFXFcA{K2_WOQEHl&sXfLVA3||pmpA4*dFfy z6?^W}(8@CMsdOSyoscBVA1)_ZQVd)_xtCdYT!U_0Z3GR+_S1)l*TCP==4ch;3`KLc zV&=GSs8aioxb`%2_rFhpr*qE$%R3U=E=)ys&WS)iOU!&c3akR^FFiK&VXlcMk4P{twR!p=V48g_N z1cT%w>4hr`;J?3*dB#Z`bZ*F^`<@EH>9v6CYOrFvMC4KX_XO0;DT5399)tayT$ph1 z2wZusPapl&gEpC^sKX90$Es}L?8^%C)n4u-AEv>z+u<~7s|Bvu*#%0EI4C*TOjTZu z!17;@$&0J6VEfB|%$i&0$tGQf4Yb@rlZ%Y;w%sM%Bvi$?J9F9kE_*o5GY&W89Dubt zG2}&P1*kP1g~wH2pkF(RQ|B(?9ha+g=E^(>>PTf&r%S+_3Cmgi_)L=Z>mdA_6p0oV zp;X4WnU-;z;M+=L98%Hex=X@fU9%d-jxk_AER$iawws$v9K3@~;*u!8?F(`1e~$M3 zq5S`J99-t-vlUGXsXXs$Un?+zs5eTarMVcLXGFuG_cXdOVEVs@Wkr=2v;JYxxVPC1#eBEhe{NncQYr z4w4X~w20v{$b;9iXPdraTcvm|`|D129& zOeCZh(AD!!;wCpr9VR@XRY(4#$0b#SGMYE({-&dt;cPsj z>*>Ck4p6k~9Z(Hpk~tuRJEG>i=fwwfg=@)|;3(L@cUO)a(nObYbIIxGt(;4zm~cY| z{~lb>N>Xi&&G-FyBRKV@64R_$Sadvy7HsAHOJ8>4tm!$VHKGdUG;9O+X?Zl)V+himS!^HSS z8u8a(%F%_2tmtu3dO5=k-dazxxF{k5J_XM7*ce@0d$Jb9J9$dw4HNX^a~*GF{P3mR z1YBolfK3Ir>Bs~pvmS9nP%Bu3PGPE;XTOJL)$#e-x~>J^=I+8PQ8S1k-|-Ojo{xI7LP41Fm_*LMEpRUBo%p)zRCT zg>>||PR?}iST5|aJSNJ`6#iCzOmm8C*oL*9OtoMhy65TQ`E$~2rMWbn0!Hz)hPL%vl~5rWoU$NQY$9*^B^pl60<)NLK0NHkCS5)Qh7( z=O&Q@XQIG(X+8}NzKHb?ed({&>bTlQ5)5-W)W2(s`q2r%y7B{Z;UIlc@Q|*zl>krw zbvW?PgYNuwng)-eIQ;J#G+$nUmXk$MFI)u==|{t(=0!CBw?10*<%5QO25Iz_qE3So zx%P{)5MNx3A1B9ySA{Audp8+=4rSw3`SW;tO%DAuvw|D6vt3YSwiMQ=q+yL0pHbK+ zj#?Y}d3*VEI2aj)4-EQwSMnA3&8lIwMJ|rMFvN%~o&#Evr|J43aeTi#0A+r+!HGDY zUpKgb{8v&)t5#-FyHT5kF0zW$VDTy_3>{)rTn3o6dyC2VX*Mup_ZW7PdL&q{Q6S;l zJRq!rBZnTD)A5aI@H3>6?_r#$r#rq^U-;!kpDs%y3NJ*V%-Way**YG7yXeDV*<@UE zk7naPi5Fy36k*XiY|LMGnLwC0l8sD@Z2b20SWL$IseEGZtGlp55ys?fx_e){J6_e(ku zc7X3rwlgy?ZzC0VR-%Xc6=-`t53~&KQ7xJM?5a+Cv`Sk+)!Q}U{){P*AXx>DZNYq( z&l#?St!56n1cF1PvhXu6QJpn64T6@H!|{cds1P*>%sdlNpSnU2#xf*!*>pBMqKZ^z zJ>#Ce&ZZLe891x92+~WQ;?$_sj9pzR{Us?RMXC4cB>QeWJT{42+LQ_fFO%_D{#aJ? zup(R=P{6})6X5;EL2lK|g=jLc2o0@{L77?!F*6BdRwqjcH#}(;YU zBFzC3p7A+y|8mGTtHgOfnuwmp1%hGI*qJ;hrAx$|JUjTB`CTao9nY`Ars=~NA2p7c z*nHq3&g8)UH5|XMYJ=i}G4#4o27RESDzwfNW4|xHM?2il(J!iD?4`aBf)FWZx;np@ zUedpfT8ZZvv65U+4){ThTWfJ(B!$`@p9vfK@6a9pI_Yt~_j;T5V}teucyRY0Iqvm} zE^0mpGKci(YDrNtOKvZHmUIc#ozGD7%k}VnW(!RWiN<$kQ6Mrefr8&Pt~Sycmhc^` z5|5MSUU3n4HPn;z!WBwQYB+y3pUb$@fJ9DC8ko;L0jEh4&?;9BWx3I~+$4>hz1lz=$2S1pTT6mpcLqFVwnLE+We35I)*;G^-myiYY$9m zP=zm6{2h102%dheD`Z;Jp(Hhz(|h-x$#X2^8Uhvx)#cWZIb*bNN{S9yR5hL+{yZLw zPAwogDf98@?iSJ;yaVU)cdR_e$<%gg7iW1So7-db09SQ9u(%jCAI~nH3IrvPq zki+HjZ1JfQ?#{fKsN1}p{AaF8HRQ4}#(ELF7C{V@PQ_u9Z`|pwV^lU{DG^Up;B#Vo zar1I*Fp%rVk+B41?|Wg`^eZ%|M1*Ue)j|(g$1)qEo9JAbB8XD2u8H*hLmq@zVyg2* z++8*v0=rM)nkGFM{2a6(RpBlvi;@FfX;q-+ySYm-r*Y0M1}1+s0^`nmbmKd|_#q)? z(H_I^`JC6|Cpf{+V}iZqFTtu4ix@bAqwjf7e`el^nLaTTqv7C01_s zBLR0J241v&V;)p!bFm&ibF*gEc4)g(Oi%8ZtO&L=l- zq=Ia4G1K(U6&$~5;<1rbP_=5OoqG9jao%X6!S{!a+dU!LaugG|q?S>rmSlZeBdg%CW0oeYx7s=E%Zpw~}IDYSX=zg!j`|Z_*bn;is>M{`? z9W@UOdoEISnOHsmIs=on}YL1rxD-%n}|uA2)nSLg3GEp zf~Ob8(uBBXJmk7a_(#i1SZn*+{LW-4SQuUf2K$omO3PJj+oijee5B67_^1ovf(A&yBCgjM2G+2doc;h7nK_?iA8Sf<3c*Smdb zXv9PKwjzn{cPxiwsJ%e72v2A$PNdoIc^}d8uXs2+3g?SB2?s}w;UeD`;LpoR&>(1sNy=6r z_HB-^-+7pNp9n?w>zSn9%}to|_cd<*R}bmAz96>rJa%n1WoHD4f^Wz{=2$}ocFgR; zf(@^DKjI1aywDoACY9o?khO3?dM7CLB!J<9N_s)E7(x$J@$4f3{OY=ifz2nt@Y@r? zO?OH5g=tJ&M0c@2+1dF1c?PX1-HMt)W0|(7i=Zdm4XmCIxtVi^x$ZAd z+uYwX2?v*g$G9LIf5#s_nMILu9`fD0)mV6QVgXT_B8v`f7R>T;F`5xl2_4R{D4TE@ zB!1@6kR#HV6#|u-7w+fHXFlCEOpORgZPtrLCJ78XgI_~uW;m=iCg7Ot(*&i(zAU$y-x9g=I zp8=i0?yX8C7$HSInk}W5ehq{9`)iDQ%0skmh=D>iYu*DK1$%TI;d9<9D!9k{f(;f5 zw?~Gf+M7&1&$&SuB3TO^8}#Ux-?FggiL%gM#24L43$edb2ouD|2s3YA0-ro*7Nf%0 zx#t?O{)H>}^)I5qf2V<2v?2RRkjk9K5Ab2qN3J+I7M4AYp*swC&)}mXF8p#P-Sj08 zX__vSD6NJ=+b`2Uzw)_@mG$Jg|8j0ckDTzy3QKl>7~gGFO~c|W6%5F!q<`dj_X$0T zrK2yxwLN-pdiOorHtH;wn5hmGj%s9J|3Oec6Aq&OJUgW+n0kBJTIe~ZG9|wslcAmS ziA>T`7a8Hn(IEk~EHAR?yKA9?b z{()5+&p_NCYvE+~RRaE43N;h`NrsgU{Jri3T1(>K`wk5F@~X)JluKk_#&jK4Tvn3$8Jn{`4r$ESlIM7x?+Dp`vSR$h`$3~5 z5q<8ihAh5UbgR6E=l-vS_QC1IL8P5$k&i$pXGA`0ZKwV1@5#Bc2H4hMh;QA4Fw`vx zCf6yj!5XhP{f&A;o1Lq00;5T;CQcF_IF=2nlk>Qb^3Fm!jLxUU?bmL=nb_EneX{~R}y zxUUI-**7fkB|pciP@0K-{`NGOPm0X<(Bk_dBSgWnPY^$~oAUW)qS#0#mf6U_ z_ibgoTkZffdfg(Lhdyy1yOv__wmAGyt4SA?YhZz>fzY(61ua;fKN;Z&Z&O343(sgO zuUmjl;t|5sdS_bp$O+GP>w%No7<3=<#tM%nMi7`lM{OM|R5H9n5FGu=P(Gv=n-{P|d0o1c*EV)oVf}Yjcr0ZQYF3VhxI|FAjX|h4kl4l1I0i#I* z&n&%Ol@09+O!4WQY_up@h$|1Dv6z(63AgU9#ekKIpmCKKEE8V~=R($@zREkaDT@Xj zl>|66=RfMpKY^Y89K$nh$KczbByd*}p1 zaYn+mS38+!&gr$^?wGMC_-#0nz!&L6eJi&1B|1*P00;4t6WQW-G= zBisyE-Uku6ePb|d*;QDsv5mA%HYQRAPwCqyHN>J(l^v5M23Nl;K~f45FHRQ}eJL?{ z<%q34Uy1hTN3iy}J{MOt3Z)-!BN^ETpgK#I-8Ga80j&yfcj8zqQd-SMsm@V7*rI$7G)vR)k$E-!pi0T!ak1pzhHP@_mn+lGgz*K%3Je4kh?ax%;y6^|TJEnBq$@@%h%0*}&Fvs7yjyPNeOJaDS3qJfNDk!p? zg)g@~2ZvW8aBXTaeg07o-ZtLCpW+kQreG;{(VMOCr(-_vmAi~Le%iw3zBADO%m`LW zy&%IcHHCIoUvPV`B<#tILFox`P-WhPl3yQEeK5vLb9RIIP#a1WU7#CAWQC2cv(YX} ziS=$VgnD8k{Bb}TraT|w9?$S*pBjiG+l0ikQyfY3Y}Vj#E9f~D(!!l`LX&rYaBS{O zj7*Wi3_dTK9y^gqYKVlhlqN3ZP@WO~UtSuMd zk27J+_~sjAPo*q$@ENmXCjYR%H?j`8Mow@}eQ+bAZSn|B^ zBg&{+<4>=xT80-F7tm7QT|#!Zq;PkyBI{n;imuCNpk>Nf81tYQ4*Ogok&C~;WGcdT z9(`;Pm0OKO?<1$OL=wgGzR~&sB{(|UM7Si=AF8)b#kV?A!fz&7xZ~9_c4VL!xw?8h zvh)qTaaacDe~$+nSq*$LA_Zq-`5Ef>zvw<@CccOWmN0mUXVNx%@w$^u=B1TF_E4E`Ojj>oQO~K zh-oEgjXc8n9qY)OokqB*Uj<;WbV{>97@|yH=1_g z$$2s4gsdNG4QE?uofctf& zgesRMS@(G&pltMuER^_26_Q3Q_AM`@{zm{(`Yu^Gk)zl!_8ds=mZA@y%w|%)M{v)- z8_~gU3wY09Bxm~v+tbL`<}n6kK;<05yE^);b_%d*k%!PF4z64i<7p*UvSr{(mp zs~wGJrh(YeOH{MQ4F!h^Ai(w}%4R3fPx4QQ&4Ctj_HH>@-#V(MOHLeJqD;tYvpcw8 z1<&QwETb{HX++*V2@-Fl!(WHd)b@Ej=aF1ZV!j4&-wvL~{}j$);yd0Z{m2Z)sNJC1 zOU8m~W-YfiZ!a8~v<*G)0wXiA2}^eH?8Yc9_Ida^s4LKhwDKq9_jwuNMY9x|p}{d` zo6S(AF$aQ5wvqcy4qRl1GDPrwmESY{xU2Ur;)ItK%o(>E*idp1|H>;O^dHAe4FfX& zULJOzUJLtLW#D(>XPnk~LNM>BvGDH(2Cr_7=OXyb!Ep^);nM~={C-@Oaf-^usHqNk z?^Gj6=nka!#u;Nqp%*jEGl~x`>w*8iCE(}n(bVVlL^QgpA@oYL;VMS!!S+E1Y;_S~ zkNd7ijgM;J8WKo~*QQaISH0YQ!9wB6o41L4XDJsZa3kNn%jqe&#JQ+fGHh!Pt^c_W zk{9p9;Gbt{tyB{3(wAkWX2jCDzeYpjayQtyuY(3(izbon?Wn|e4Aslxa8B<;`1ye2 zF1a0N{uq;6o ztsZX>>|0|64?aji{?CitaeX72o7;w)A68J^IT3VXj0Ej=DLc3^Ru z5$Ch<3dAO9^6x$I@ZI4PHFwbF6dgoidE8WdE)z|UkLEed-uhJItUVps6M>e3vGn(8 zOHg~e9%AJzxd@LWa=O@%ORJ1Fe}3j6R}}Dr$vUxwoBqL#zV!)(w^qI&{Fsa(EwyCT zgDvovy-NDkD~Ve7ISeYu#~;cCbTO{N7>f@$`+5TGOEaK-dCEewm%PIxI1-=V;?MZA zOeoXU!u3r-;JhOhs@AGQu>X3pAT5IIP$+yz@*MhGcU@s4^Mus)xInpe1cb-;VV(PY;Xrd5PO5y)#SR&;r=Pe%r~WYadz2>V zNd92vU*AJB8&1Od%{AC?dK>SPI>^t<^Wgcqvmn?xjsSN`?Knd7N@1#-dKY}Pgj z;oYhLX6vI$h}v@$4hlrrN()=&V7LYE07?Sywmur9+D||HD8p`hJt1^ogvN>I$%)*0 z`uZJ(qJl+2yT{W(rTRHNIbMP`#m+UqU-^@ye$ArY?;9~tw}z3)nkf9YvX9TGHS+uG z+jQy#HC8Lw9u}h{`)<1y+kV6wcZH3R&~H5VV99;v%|vawE>MIGPqgFOPR)g2$uc-( zs0q_YS5x^f2_P!i%NSdV;`_wEoS-v;SnO@0t4t^3gUGR96nzKAIjTXA+yz`8v5F|9 zZ-kT!BSd-eMlcZ^u!tU-Le8fiq@k@H^oGtNNZL|J-sLn^JHv?g zV3v6~{N=*4TTxh5G>E0hsvmwlXdpio?p7ZnCXHj+AZL%uMim&CIe%pzq8QsOfe)oY9?( z3;#>OL|IFiQMwy;UHC^u+xcEd*?F=jYBXK{yA1v|Oe1fkq%9W88sob1(J;!W7R@X- zz@gP;#Q*GNBJ$V_7tK$G;A;MC1&t#PA#o(J(F=;YZb5HD6LjrfN9s&DVyI)zCheXE z_HWmK(~A-s*rpFw)0c8ZyG98k;@2`)FY)=@L*MA+FHK;xR*bFbX+i%p6G@q90otf) zLGfT8j;reCuJ(+tF`jkyg)E9Fq2ehc)nMY(m!U+QXllMN#NP^q6`6x!||GkC~Ca z3PG+1FD0!Z?QK_xM+Qe~_qUJ?Uw8!1yrjSWeLGoW1c~w+GcKP;`ri=-!cF_f} zHvdBAm|dXPf~)Ya@lTTfpC5MGc9MDeVKks$4&T$CR8MOHN&dS7E42G)7@z$cckKly zwPt}u{@2e;OX?Rm+whQPAHK`YJouVxhC4tzB{6LNWwO6V3u+t(xv-3!eP*W)WGa5-l2c(IJ|`NCSJZ^Z=)!$+yyleu8V{G*z8i%IsZ@kC#( zf=J$2Ku$bVXFr~L%$d%agi{}{V06oLNT-zt7Cavh^>a4B#^F|Sz`cgNIy(&(AGko@ z?=7N<;+1690FdWJDyW)sPOvGqkW}dH1e>&Tu-3>xsI3t}c8WxkI$vA*cHnYN(4lWk z#42~#veps@G$|ankbnbsG=&#~qlmiJ0W|B6LMA+y1e^JRNV@@1 z^3OoytS~G-0l3-lF{$IZv5)ty#f2_kEh;|kfRT+-a8$2{4xP^@->;gGNX<={`!1Je zOFYJfGeRLNrXTGb))9NB26A5OC}XL41UE4P*yuf-1l6U{AX{}(%lxI^8}_j`_RS;? z&-~F6Y>2b$X%ssaO?69T@Zjg^#PMMUM&}#j%>)(5Yg7_eCXS*HKc$h=oIAb!b~~mm z=csw(Oz<;SgJ7Af*dI6^`}$SbF{^zs)^QV49qP}xh9(Iec<1?}a3xUSXXSoL$I1C$ zr_9^0FT)VA45ASK9wV-qqiXLM;j%wFgb&AfgO#o!)*o3(cO@1QkD#aYjYu%Dc9p~P zQ<8ZXP#nxiUxZxiBed}eLkElJ!j->ts17RyuH}<)nWrjAiQ_#KLpoS&jhJ(VchE&H z16%igV*GV3UG+{;xOo3J@*~BU{GPO!Ud!IgsAg+HujgbTb9+DL8%Be8$@pXd%YNg!fUaq7}q23@$RO$*pv^xtO$8}Sm<|TL}^a12u zs$t{<)Px0})XA51i`!mZB1_gj1Y~jJObr26@)h{m4%9%l!W@;0_-^4jWbt7 z3!|KeiGsTesGK=VIy!B!#IyqC3si)$+a2)o;c0L@DjiS#F%j17-+?Z6QtT zjIQb887>kxaOeF;%*Uu+GLg?<8;@4wtVWr^#qXNf?FJ=fQkeSw>5& zord%uB7-{1Ab5&86SJg%p822#og+Pz?UDpJ@B2*q+@EycL_e}vWGXB*lOv0cyd~kw z;_0kV4`JZN3s9jvhb>&9N0&6jflBj4*jjT!Fynk84DZ#(DSsWPfouYv8OT755Jf6z ze@)D!_u%$y0d1YL6p9S)6SW1qh-i;4G@rZ%KbL=?*Kc9gm_aM=Fej85h-9g7z-6Sd69Ne1rfvn_b7Mt}OnYzJE)N%X)(t0_OQ{*|E zJI?izS*kZE({4&S=fyJB;|`L0CtiaX|9p6NrI6&BtAh>C12_5{Pxowi!qj@m!7TB3 zS|6f+}Ft{qah8YX*VP-4b?X?vNNKHBxNKjq2k=vX^5;+ zXwZ}-rIJd!-}(LL&*wR>$GOM#`Mlrt*I>$wJ4DQHEV(1}q7=;t!seZH(VT3uxz?6^ z`tp?OotTK*CH}&vVH5Of{X^SE&!T@c^+@=uY9?W?D|CGFw+&nVhc?eSi*3zM$eo9) z_*Ev~i2IRhYVx2RcD7l=CLeJU{LX>jFirv|$*jS}b9T~wLZ;!(&10}{yBh8p_<)Hm z60E&*H~BMuHM62k6YYMhkX*?pWPEoAaY?YiG=m}nHyUks&iRiT2fd*irf;J={g;v0 zk3HmGf*Sq3ApmnF+PNo7EbvL+I(FXeZmQH&PFw|El~TiG5UHrb%vEJ{=j$L0e~|{U z!^<(q^d{PkKZGkoEval)8r7TM%!O6@f?@su28N!Z{jdGVMXTKy5+co496JOz+P1;8 z+BGCq)e7XJHZYGS+v2Ks!>nl8B3s9muc>ozG=E9BJBzfJ#M47m;Qrd;36mYLzTykj z?LSPMa~VGPvM3C^JpdwhIp`=ghV^>hhQEF`k@6j`0w=+aFl$HAGdVeQ_UtooFr=Hh zy8a}Lvv^`UaTB%NWpog4@<=ZjpH;FZ4%@pM`NzN(yz!@mOXTIgBY zw;_h2%Obd4Dao4$6@cV1fkR}wftKHoW_Be^fa?-bLch<^#_|+{WAc4rn_U*U5!_25HQo54ot_mq*na;S@NjVo38RZ3Iby)3JNF5FIDG~VR;z%)f3?`} z{au(bd85pO-RSx&4BzP&!U{hVkW5Qtk4_##rHaf!`X`SPqxD%=%W(R}vkYG!3&6b7 z4)`WS5m<>Jc+5`ZGgeY+EAX%#x-7tFTq$fG*M#$WbaCUz1jcL9EgU~G0pwIOnV?Z- zFwQqc$o}0Vo0j&FF+OkU_qpyMc}N7Pk39xhA4H|uJNr_87%wIVHk0lP%D^E@7 zo~h$tMZ^)dEnW=$&xydi^3#mg_i?PoeG|0o6sMD9d3rlRgPeV4jz#OQkd8x!WX0#p zc=zS8noDY(SX`V$))-XM!_!{FYl8GySshiMS%A?izma$3{iuhqd`!L6!a$5Vf;8%$HG0Xf!i%=VdIf#8jK%Uop3=-_)< zda`?fu9PjCp5Pmm0SyGr2S^_b6$*ehV$RsfZzPBoZ9@~?EFLM^7owOJUSv&{J zd-cg#Z8r?_+>h&42G9u}3Z!bmZ|-L1EjVlEh<9b{n1}Vx;YCju*|LzL+npRNm+zsU zmWQFbRxZ9ZD>e(ON>@CuSxD#kzZIge-lz9B8SwY0S*n$J1zj^Slz>CVZLBsVIKs87wM zyH*>a=@%ZhxU`WiyJo{#*HX?=bQ5X^s!+D4m72GI#RWC8f`8Ev9@M+QLS4ZX(lLn- z-ntE$l}5a2NesEFDr6Z?m@qCu@sQLqnHLd>A;vQV=IrAvqA4|spL_%GL#`EUOdpMx zoyVZ_jy6(PHjB=>J5;mn*){01i9_FuJNW8VTkvN(S2JNvvq+w4((|~nSK)z zabY{`Z9fcs6Ay82^)cLvnu~bx^b>(`cnJ3wRN#_5bIID1wKQBV0kZ9U=*_j7%*uHa z`Q=aA=-DhMYN7jRao2y|cWCZj!-aHYyf zerCQF|0-QV@X2POtE(`7OKBi>hq7p+VGJ=}7e^CL?Bf34gF0Lvh>j08Lc-l-Ou1$Q z%NHgQgR`FWwyHnQRv!tCarx+Dy$~v9#enJcOh$uPk^6#w**>|H%eU<&kG8qO<4+f4FA`3!Z?Q$?dg3iz_B3mS&p;ce6>B5&(V-cGzp z(oIC_t(TWk+Q^gpa~4WlwI(!qGYFt;}Ngx``zvStmU{J;DPV4X&=CIgjp zi`IAByIx9k$|ZTm@umo=I_^gFGYXi2$YQwq;wiKCZ~<``slarsxXX>dn}JJo&w}Ic z$8=rSb7b92;Bi_iNgaGm+}b^bnQSQRNS6YI&pn*_mvpfIvW?2TUQ9C650cd?$#BQ~ zDqISm&bj!BLgC`8BtP*;&AW+b@mshm{PM8FzTK@%J#C_A63vLl;aoZ{Pzu}%4MDDB zfc!3Bt^w*sjtl3yEJikH z8D2V~&L3avMme)l6W9xjemk z_#E!b&_@e_WwhIfP>Yx@Jg`RSGYee2k$vh+VTl{n>^TVTesNS{`)K^@Zp=R4oQV=M zhZqTm-L&ab0@g|#qq{QB;de)YmH74u+_oLVw`g0^5dS!6JeG(l;pf1^!x}x-FN538 zXPDIVJ5*oW3Vjc5K)Dg)=!jH9Xb~?0Zjc@&a@b-HWDTFcnZ%YFsc!i?x$MKnf4arB>V5z&YbAzLaA zqUB3MRg7<;>w*E?Jx+tUIV}w)`)$C(?Z+{9Y7+KMHN$9CRje}-=OpU>e>A>SxkI-BT))S&!4S3D6PWzL|ob9IUL_66uzu@O!f6ysbABVu-|gm`8Avh7Ibsq{@l z>OJ5>&vh45wR2k-t4GRYOAg`E(kI|duhHPY#ua1j_kzd_cf8`>hD|oHwiXFBL7fXcFwNIynk88+Y=Vq949Y^Q$SDq6Z89We$DX@R&Zln z9+ui4fD_iI=-$O+ssBwAPUhS+Y9O6K!+e}jBl{^CTK11#Jk(EoV+8l$uajif-H(j* zh%Z!M;vl`;6b?6@U7(@-ICRcB3Dxh$(Zc;kHnZA>;f|XaJEg6GCKWxRPBlJg=%q)) zkJ^G|rXiW3n}_*6o5_@G^7u}29mGr@z|=?gguTvwMto=;*!Q-OiVKync;6gS9V=vj z4#yLDfz6VbqC$04Ct;xAp>mLa#4IfqxH4DVNXDwaB<+bm?r>X*PH%pb$`U8+X%yoJ zM(WXX^(u7yydv%!^y1Zp7szNKkGcF?0C?|m0q-DF*y(x*3$1l=ewPAjk7(c&mu3*Z z3zn##-jCs;!EoR~5xlR-gzjNWR0x-1W_n8ERKY88Qcn>)6w2sb%ZpU~?>@YwJuI9r zhw1+7H>vUC7;^0BZ_==Rkan~Hn!P?qqF?7h-U2Cl{pcii)-e;VURx8IT#wPIldQ?w z+)9vW{Y5~AEXJ>u(8a6K%a|z-nz%W$x6_LOq)XG8|*j_{z$3)JD2_Bwd_ zd=gq*>!Z8Mq@Ys0lLWh;r`?T02c)134TD>1OxmNU!c1+x$Ke753Va*8u^;Iyk3YEY zscE%XLNV@JJVxkJ368H-bEsO*C*k*D1H*C0t2fQ>MWqr$e78CsH@@41=T~>a644kg zxgZsL1Ww+XPHk?=-zlWDd>gG%Rt3#{Pl@+|(e%!cz)7izr8aL<@sjKiX2xyA{cE41 zk&7I!wL+4AG&sz;4=|+A_W=?6oWPiT*-us$slmey@wBV5livSEsX>4WD|N7${JVb$ zf7eCfA<^s1qis9T+molS!&ftAb~mtXNS)mT3NCRt!0gdghQ&8; z<7g#M+;`d?hcmR8Uuu*2ABW3Hdhi(T)ZjT-5}%DpdV^@{okf299)(j^>tfK^Q|K}A zJFOoVPPO((YnX+LmtYZEH;cIgW#W!$n_h-DhHeKjKThfB; z2&}W4hF)fJu%)Y>B=qH>{apX0h7`%CSi_jy@FlXnfZiaCsNNd}>SK zZ0sJw7yB>d<@NbYv*Tj2xjOIl|oMsc%hA(9T&P$o&UVv z0>kEZU~Rw%UOaCacWPBS_5}a3efMY@THkt3y7m^L?1K@gEb5Iq8YPQbKN+8g65%57?k}U$lL3r&}c04aI6=Ew+G|IC*l}O?$da?3qjoqpUx@)nGb-gt2tcq~gql|xILTlXDD`T{`0{_KIOX$e9kok$zgzx!{ z^wiDAo)&4cXUYmJy~{VH@4g@pf}}*nJl4K`Bq< z>OHKchprUSQwPnldeDSnN5rMV>miWoBUjygbk-GuzS89e{NLy z)kTZ1P&$7Cnk-ermYNp&ro(_~E|uU7CHFz94^MXIsDqP~GMep8hj~#-tn|igTpPBD zlY13J55A>D?(0gd+LQ&^7n4ZiQ)3L?@E>{q^&qud>W5t>lTrIe2eA$O0e^KTaT~vQ z<7FX-pLZ;rdHnt^Rf=gO1KXmhq~TSFS|-V_+A@(@>mCZDChxv_HT9ptHh;{Ie8C}+p!bd?QNj`{sYGKq_A%G3tnKk ztDNR4Z(;5I2El@Rbi8{CXS%xx|pM--pJ8j=Lc$`jzrEg#Z5UxSSh!u|Q3hMsG@QFV7JzK9rsJJ$A6-}VH2@O=gS!{WRgY|~Z7Qij4l&W6#*_2 z!cp?K4>!l!26|ilQ0iqV{1EO?cg*Y|IbS5tUonI1kr3{j_`BDx+68Vkm_!y(df8{X9eNS4eY_ zH-u-m&;X-U&Xc+it1v~Rl33XJ5XF}a-5U9c6k9b_t7aF$?THC@|B#3 z@+EFUK%MQvW7FxO(&eznItwSH-X$}eRX}ucHZ6@g!Nu8Mp_0FD(>dGD)Wp~xg0>Bp z(A%U4Qlvw0cVrq-D$vB22I+9dZyy~Hd4=W?$2r9`Z}P-I3J-;vvi*mz}Fr9TG$hWZdc**MOuFOLTN5f^%8HL%Hv zG)hS!QIa_*dfNrxz0rnEz8C3#iR$>*Xd2Yb`o_#KTR_$wn}|nO|74VXyumoDh+Jzb z0w3oX>T7cecIR#(`HK5709C2Dqc-f$N`wOUKd9Z65&KUx3fgWY{>Y0$&y$1JPNQ^qFEe4gWWh zZZy_}Z#HURy&#(8AIKmho+L2O^h!uncP^fc72_Q)-^VXc9B^w@9C_we1{)tp@EUC$ z@b|V6`8rx2k2Gk&N&A(!rDXuO?)^*O$c2#!ZI@8n_cHpvG^9_>_uz$V0!O689~ZxP zKref3#O4p1(OL2j8DkvH_aD{gRQz1gP4W-wdASm~B|k{l=OwI5*J;c{BmRwi6rMTp zn7%HR9}Qa#BKLgRB0FIOIC`J<9(*ErrjRD2bF+TXA*od^dkSxoS0qQ26UpHkZz!DDjuXFD!?_4~I{UzA!j(OSb47~usfZ*^ zEBryfw=1DrPCHeLG2|t#oT85v@6+XthID`ZJ{+w$jW3?wgnE)^2+^#q;-v0a}Sn{6%6A%?hzqyLS<pyc-pn{njzgF6IyR;`+cCY|0G*Wm;8Ig3>ec=$5@REQ< z+r+?d_b;MzPLXaF7;qiYXQ|77slafWWNxM;eob!UZrdE>9BZPp`<_ zJvr=lhOT^LfCpBc0+YS_$nCWX+>-Fvni~HHSfG5DF|?I~<&Xulssi!+Rw>Aewj|0m zrWn1+7ewp)IQ_aYB=h$$Bd#dC2j&R*^fX&YZlcV>C=qyVHXEw4BJrKye{}SMow#Fl z98CKxLVUZD>7N2IqWvI-i_!N+mF^o<%r2ChLTAvZ7E3g9U4@&MbPGPpcq%hyBnIcq z6=u~5WVePtJew_tr`HdlUe_5YyB0<@&q;HRS7+N+5C4UbOl=(3`~s{!jx+Z?wqV0% z8T2dmB`P~D_=cQg@ZtMK&Z2TTqqtrK;O2 z@Xrgum(wavMnA}aA8(i9W{E)B6ZDP<{!+^A68KNPQ_*k5NKzyCG97|`RaGawTGYn$m5d}=um8h4-A8KD z7sb>#!esR6n~MV;UD(`jfkPyK9;x(%Ei==hrgJaou;Tdf{26A=ZW)aAk;cYR!si!B zMy(xNnYGL<+An_!G?w*HW{Ddrgi_8g<0g?RA3;rSTqfPaWBAJ-Q{dqo8IY4)MP_f8 z0P)d5^g;P3^2>Mx`${tiT*ZPQ@@P5mFJ(FAMJT8}mccr$^N@Vj8xke-VC)`cd^_D7 z3I6YYUtxCkUAq1HSbEAV7dLM7 zg7^yw@ZpBQ&)=_rgO4mpuhMxkK2ecvc27s|)zX~9q!%_nuWP`84a?XLgL>j!UqC$? zjzENT5j3~4V7fqw|2Z}Y9LIbkR!(umrL}`AGCWH@r!3@xT!&%)@?4mhVoaQkf?%tH z4pg`ZeOwzY*mrO(c-2?XC9x04uGM9rYH}adBgELzE2a73jVs}d(Cd`Uv99hmtU%?F zP3=dg&WL!?fvAB)L62*egxj^jKlV#DP;AEEhrtO2q#-l?VhQ3dFKMsmCKPdqr7f}2?3td!aCwFv zyM_ydPX?EmD>ufo_a{E4ZG|Ivsit)LZ=f9f+^)c2#7{bqByiIU$FQe8#uNVqeX!+) z;8B$OO*bDthSpzqkVQ#?24?Ii`nF>~C*7EZbN1OmsF*L?nN`gcyPkxz`_j-gu#F7O zIEMxa70j*O8Srtz3~X#@fajC;v-^abf{>KkjDxPglzLoE_WiOY(G@Rga$*&8f-xa) zw=V#J-$PrQjHu_zXtLJxGg*A)2n=qM#v8{j@>ewK$r7KpASJqno>~

"); + cx.update_editor(|editor, _, cx| { + set_linked_edit_ranges( + (Point::new(0, 1), Point::new(0, 4)), + (Point::new(0, 11), Point::new(0, 14)), + editor, + cx, + ); + }); + cx.update_editor(|editor, window, cx| { + editor.handle_input(">", window, cx); + }); + cx.assert_editor_state("
ˇ
"); + + // Test typing . do extend linked pair + cx.set_state(""); + cx.update_editor(|editor, _, cx| { + set_linked_edit_ranges( + (Point::new(0, 1), Point::new(0, 9)), + (Point::new(0, 12), Point::new(0, 20)), + editor, + cx, + ); + }); + cx.update_editor(|editor, window, cx| { + editor.handle_input(".", window, cx); + }); + cx.assert_editor_state(""); + cx.update_editor(|editor, _, cx| { + set_linked_edit_ranges( + (Point::new(0, 1), Point::new(0, 10)), + (Point::new(0, 13), Point::new(0, 21)), + editor, + cx, + ); + }); + cx.update_editor(|editor, window, cx| { + editor.handle_input("V", window, cx); + }); + cx.assert_editor_state(""); +} + #[gpui::test] async fn test_invisible_worktree_servers(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index d5a3f17822ff7f0f2324414aeaa9819b8605f53b..2b91f8cb1ca4c515d2f09997f07b42d611b4baaf 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -627,7 +627,7 @@ pub fn show_link_definition( TriggerPoint::Text(trigger_anchor) => { // If no symbol range returned from language server, use the surrounding word. let (offset_range, _) = - snapshot.surrounding_word(*trigger_anchor, false); + snapshot.surrounding_word(*trigger_anchor, None); RangeInEditor::Text( snapshot.anchor_before(offset_range.start) ..snapshot.anchor_after(offset_range.end), diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index bf21d6b461e6fdc082fdd1431f13b8daae730824..a1b311a3ac3b8ed330fee0f015c41d327efe342d 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -17,8 +17,8 @@ use gpui::{ ParentElement, Pixels, SharedString, Styled, Task, WeakEntity, Window, point, }; use language::{ - Bias, Buffer, BufferRow, CharKind, DiskState, LocalFile, Point, SelectionGoal, - proto::serialize_anchor as serialize_text_anchor, + Bias, Buffer, BufferRow, CharKind, CharScopeContext, DiskState, LocalFile, Point, + SelectionGoal, proto::serialize_anchor as serialize_text_anchor, }; use lsp::DiagnosticSeverity; use project::{ @@ -1573,7 +1573,8 @@ impl SearchableItem for Editor { } SeedQuerySetting::Selection => String::new(), SeedQuerySetting::Always => { - let (range, kind) = snapshot.surrounding_word(selection.start, true); + let (range, kind) = + snapshot.surrounding_word(selection.start, Some(CharScopeContext::Completion)); if kind == Some(CharKind::Word) { let text: String = snapshot.text_for_range(range).collect(); if !text.trim().is_empty() { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 1a7fca79f64c2c253117a3acde8c4d7519a9c282..d5d83da47bc18a4fd15f59df2ddb2238ceb768d4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -546,6 +546,23 @@ pub enum CharKind { Word, } +/// Context for character classification within a specific scope. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum CharScopeContext { + /// Character classification for completion queries. + /// + /// This context treats certain characters as word constituents that would + /// normally be considered punctuation, such as '-' in Tailwind classes + /// ("bg-yellow-100") or '.' in import paths ("foo.ts"). + Completion, + /// Character classification for linked edits. + /// + /// This context handles characters that should be treated as part of + /// identifiers during linked editing operations, such as '.' in JSX + /// component names like ``. + LinkedEdit, +} + /// A runnable is a set of data about a region that could be resolved into a task pub struct Runnable { pub tags: SmallVec<[RunnableTag; 1]>, @@ -3449,16 +3466,14 @@ impl BufferSnapshot { pub fn surrounding_word( &self, start: T, - for_completion: bool, + scope_context: Option, ) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; let mut next_chars = self.chars_at(start).take(128).peekable(); let mut prev_chars = self.reversed_chars_at(start).take(128).peekable(); - let classifier = self - .char_classifier_at(start) - .for_completion(for_completion); + let classifier = self.char_classifier_at(start).scope_context(scope_context); let word_kind = cmp::max( prev_chars.peek().copied().map(|c| classifier.kind(c)), next_chars.peek().copied().map(|c| classifier.kind(c)), @@ -5212,7 +5227,7 @@ pub(crate) fn contiguous_ranges( #[derive(Default, Debug)] pub struct CharClassifier { scope: Option, - for_completion: bool, + scope_context: Option, ignore_punctuation: bool, } @@ -5220,14 +5235,14 @@ impl CharClassifier { pub fn new(scope: Option) -> Self { Self { scope, - for_completion: false, + scope_context: None, ignore_punctuation: false, } } - pub fn for_completion(self, for_completion: bool) -> Self { + pub fn scope_context(self, scope_context: Option) -> Self { Self { - for_completion, + scope_context, ..self } } @@ -5257,10 +5272,10 @@ impl CharClassifier { } if let Some(scope) = &self.scope { - let characters = if self.for_completion { - scope.completion_query_characters() - } else { - scope.word_characters() + let characters = match self.scope_context { + Some(CharScopeContext::Completion) => scope.completion_query_characters(), + Some(CharScopeContext::LinkedEdit) => scope.linked_edit_characters(), + None => scope.word_characters(), }; if let Some(characters) = characters && characters.contains(&c) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2af5657ea776ddd85bf9495d3c1f32c2d0c69ac2..3e9f3bf1bd0cb4719f5442e1b1bd9e357ac9efca 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -780,6 +780,9 @@ pub struct LanguageConfig { /// A list of characters that Zed should treat as word characters for completion queries. #[serde(default)] pub completion_query_characters: HashSet, + /// A list of characters that Zed should treat as word characters for linked edit operations. + #[serde(default)] + pub linked_edit_characters: HashSet, /// A list of preferred debuggers for this language. #[serde(default)] pub debuggers: IndexSet, @@ -916,6 +919,8 @@ pub struct LanguageConfigOverride { #[serde(default)] pub completion_query_characters: Override>, #[serde(default)] + pub linked_edit_characters: Override>, + #[serde(default)] pub opt_into_language_servers: Vec, #[serde(default)] pub prefer_label_for_snippet: Option, @@ -974,6 +979,7 @@ impl Default for LanguageConfig { hidden: false, jsx_tag_auto_close: None, completion_query_characters: Default::default(), + linked_edit_characters: Default::default(), debuggers: Default::default(), } } @@ -2011,6 +2017,15 @@ impl LanguageScope { ) } + /// Returns a list of language-specific characters that are considered part of + /// identifiers during linked editing operations. + pub fn linked_edit_characters(&self) -> Option<&HashSet> { + Override::as_option( + self.config_override().map(|o| &o.linked_edit_characters), + Some(&self.language.config.linked_edit_characters), + ) + } + /// Returns whether to prefer snippet `label` over `new_text` to replace text when /// completion is accepted. /// diff --git a/crates/language/src/text_diff.rs b/crates/language/src/text_diff.rs index 11d8a070d213852f0a98078f2ed8c76c9cced47b..5a74362d7d3cb2404cc67ed32595a06efd291ca4 100644 --- a/crates/language/src/text_diff.rs +++ b/crates/language/src/text_diff.rs @@ -1,4 +1,4 @@ -use crate::{CharClassifier, CharKind, LanguageScope}; +use crate::{CharClassifier, CharKind, CharScopeContext, LanguageScope}; use anyhow::{Context, anyhow}; use imara_diff::{ Algorithm, UnifiedDiffBuilder, diff, @@ -181,7 +181,8 @@ fn diff_internal( } fn tokenize(text: &str, language_scope: Option) -> impl Iterator { - let classifier = CharClassifier::new(language_scope).for_completion(true); + let classifier = + CharClassifier::new(language_scope).scope_context(Some(CharScopeContext::Completion)); let mut chars = text.char_indices(); let mut prev = None; let mut start_ix = 0; diff --git a/crates/languages/src/javascript/config.toml b/crates/languages/src/javascript/config.toml index 128eac0e4dda2b5b437c494e862970c23a8df3a1..3bac37aa13ed34c18d1fb8e4f70e0905938e5213 100644 --- a/crates/languages/src/javascript/config.toml +++ b/crates/languages/src/javascript/config.toml @@ -30,6 +30,9 @@ close_tag_node_name = "jsx_closing_element" jsx_element_node_name = "jsx_element" tag_name_node_name = "identifier" +[overrides.default] +linked_edit_characters = ["."] + [overrides.element] line_comments = { remove = true } block_comment = { start = "{/* ", prefix = "", end = "*/}", tab_size = 0 } diff --git a/crates/languages/src/tsx/config.toml b/crates/languages/src/tsx/config.toml index b5ef5bd56df2097bc920f02b87d07e4118d7b0d1..d0a4eb6532db621d741df2fbc99125e1c037ccdf 100644 --- a/crates/languages/src/tsx/config.toml +++ b/crates/languages/src/tsx/config.toml @@ -29,6 +29,9 @@ jsx_element_node_name = "jsx_element" tag_name_node_name = "identifier" tag_name_node_name_alternates = ["member_expression"] +[overrides.default] +linked_edit_characters = ["."] + [overrides.element] line_comments = { remove = true } block_comment = { start = "{/*", prefix = "", end = "*/}", tab_size = 0 } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 2ceeffc89061aa429727142a1659a392d6374b09..c79bc03489be89ad00d10392c520fe13e7748a60 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -17,10 +17,10 @@ use gpui::{App, AppContext as _, Context, Entity, EntityId, EventEmitter, Task}; use itertools::Itertools; use language::{ AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier, - CharKind, Chunk, CursorShape, DiagnosticEntry, DiskState, File, IndentGuideSettings, - IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, - PointUtf16, Selection, TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, - TreeSitterOptions, Unclipped, + CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntry, DiskState, File, + IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, + OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _, + ToPoint as _, TransactionId, TreeSitterOptions, Unclipped, language_settings::{LanguageSettings, language_settings}, }; @@ -4204,11 +4204,15 @@ impl MultiBufferSnapshot { self.diffs.values().any(|diff| !diff.is_empty()) } - pub fn is_inside_word(&self, position: T, for_completion: bool) -> bool { + pub fn is_inside_word( + &self, + position: T, + scope_context: Option, + ) -> bool { let position = position.to_offset(self); let classifier = self .char_classifier_at(position) - .for_completion(for_completion); + .scope_context(scope_context); let next_char_kind = self.chars_at(position).next().map(|c| classifier.kind(c)); let prev_char_kind = self .reversed_chars_at(position) @@ -4220,16 +4224,14 @@ impl MultiBufferSnapshot { pub fn surrounding_word( &self, start: T, - for_completion: bool, + scope_context: Option, ) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; let mut next_chars = self.chars_at(start).peekable(); let mut prev_chars = self.reversed_chars_at(start).peekable(); - let classifier = self - .char_classifier_at(start) - .for_completion(for_completion); + let classifier = self.char_classifier_at(start).scope_context(scope_context); let word_kind = cmp::max( prev_chars.peek().copied().map(|c| classifier.kind(c)), @@ -4258,12 +4260,10 @@ impl MultiBufferSnapshot { pub fn char_kind_before( &self, start: T, - for_completion: bool, + scope_context: Option, ) -> Option { let start = start.to_offset(self); - let classifier = self - .char_classifier_at(start) - .for_completion(for_completion); + let classifier = self.char_classifier_at(start).scope_context(scope_context); self.reversed_chars_at(start) .next() .map(|ch| classifier.kind(ch)) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index a960e1183dd46537ef3aee829cd9753b28001480..5ec6e502bd85a25b6755c6994feff7a3062c919c 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -16,8 +16,8 @@ use collections::{HashMap, HashSet}; use futures::future; use gpui::{App, AsyncApp, Entity, Task}; use language::{ - Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, OffsetRangeExt, PointUtf16, - ToOffset, ToPointUtf16, Transaction, Unclipped, + Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CharScopeContext, + OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, language_settings::{InlayHintKind, LanguageSettings, language_settings}, point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, @@ -350,7 +350,7 @@ impl LspCommand for PrepareRename { } Some(lsp::PrepareRenameResponse::DefaultBehavior { .. }) => { let snapshot = buffer.snapshot(); - let (range, _) = snapshot.surrounding_word(self.position, false); + let (range, _) = snapshot.surrounding_word(self.position, None); let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end); Ok(PrepareRenameResponse::Success(range)) } @@ -2293,7 +2293,10 @@ impl LspCommand for GetCompletions { range_for_token .get_or_insert_with(|| { let offset = self.position.to_offset(&snapshot); - let (range, kind) = snapshot.surrounding_word(offset, true); + let (range, kind) = snapshot.surrounding_word( + offset, + Some(CharScopeContext::Completion), + ); let range = if kind == Some(CharKind::Word) { range } else { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 9e7fb4a564751335db7ba6fe2afe61563ea0f161..c7fb8ffa35ea090296f137b11f08379db968ce3d 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -30,7 +30,9 @@ use gpui::{ Render, Subscription, Task, WeakEntity, Window, actions, }; use insert::{NormalBefore, TemporaryNormal}; -use language::{CharKind, CursorShape, Point, Selection, SelectionGoal, TransactionId}; +use language::{ + CharKind, CharScopeContext, CursorShape, Point, Selection, SelectionGoal, TransactionId, +}; pub use mode_indicator::ModeIndicator; use motion::Motion; use normal::search::SearchSubmit; @@ -1347,7 +1349,8 @@ impl Vim { let selection = editor.selections.newest::(cx); let snapshot = &editor.snapshot(window, cx).buffer_snapshot; - let (range, kind) = snapshot.surrounding_word(selection.start, true); + let (range, kind) = + snapshot.surrounding_word(selection.start, Some(CharScopeContext::Completion)); if kind == Some(CharKind::Word) { let text: String = snapshot.text_for_range(range).collect(); if !text.trim().is_empty() { From 782058647db4f1b25d6c04c85728faac557e59cf Mon Sep 17 00:00:00 2001 From: Nia Date: Sat, 20 Sep 2025 09:04:32 +0200 Subject: [PATCH 161/721] tests: Add an automatic perf profiler (#38543) Add an auto-profiler for our tests, to hopefully allow better triage of performance impacts resulting from code changes. Comprehensive usage docs are in the code. Currently, it uses hyperfine under the hood and prints markdown to the command line for all crates with relevant tests enabled. We may want to expand this to allow outputting json in the future to allow e.g. automatically comparing the difference between two runs on different commits, and in general a lot of functionality could be added (maybe measuring memory usage?). It's enabled (mostly as an example) on two tests inside `gpui` and a bunch of those inside `vim`. I'd have happily used `cargo bench`, but that's nightly-only. Release Notes: - N/A --- .cargo/config.toml | 1 + Cargo.lock | 9 ++ Cargo.toml | 2 +- crates/gpui/Cargo.toml | 1 + crates/gpui/src/style.rs | 6 +- crates/util_macros/Cargo.toml | 3 + crates/util_macros/src/util_macros.rs | 147 +++++++++++++++++++- crates/vim/Cargo.toml | 1 + crates/vim/src/test.rs | 25 ++++ tooling/perf/Cargo.toml | 11 ++ tooling/perf/LICENSE-APACHE | 1 + tooling/perf/src/main.rs | 191 ++++++++++++++++++++++++++ 12 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 tooling/perf/Cargo.toml create mode 120000 tooling/perf/LICENSE-APACHE create mode 100644 tooling/perf/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 717c5e18c8d294bacf65207bc6b8ecb7dba1b152..74d34226af09c11b56faa6722e00afa218c924f5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,6 +4,7 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"] [alias] xtask = "run --package xtask --" +perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--config", "target.'cfg(true)'.runner='target/release/perf'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"] [target.x86_64-unknown-linux-gnu] linker = "clang" diff --git a/Cargo.lock b/Cargo.lock index 548ff152066745344b65c75b0be80db71c6f7f5e..84ae8e613365ef3976970e61dfc7b03aaf969062 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7924,6 +7924,7 @@ dependencies = [ "unicode-segmentation", "usvg", "util", + "util_macros", "uuid", "waker-fn", "wayland-backend", @@ -12163,6 +12164,13 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "perf" +version = "0.1.0" +dependencies = [ + "workspace-hack", +] + [[package]] name = "pest" version = "2.8.0" @@ -18727,6 +18735,7 @@ dependencies = [ "tokio", "ui", "util", + "util_macros", "vim_mode_setting", "workspace", "workspace-hack", diff --git a/Cargo.toml b/Cargo.toml index aa95b1f4a78fe2599bcccd3036c2ebb65761ada3..ad07429243817b27b4c09fc651b50de820183a9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,7 +220,7 @@ members = [ # "tooling/workspace-hack", - "tooling/xtask", + "tooling/xtask", "tooling/perf", ] default-members = ["crates/zed"] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index ac1bdf85cb478064db42b3dccde8e44adee72fdd..2919fecabf050a011109b2abfe69394a0ead2e67 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -110,6 +110,7 @@ resvg = { version = "0.45.0", default-features = false, features = [ "memmap-fonts", ] } usvg = { version = "0.45.0", default-features = false } +util_macros.workspace = true schemars.workspace = true seahash = "4.1" semantic_version.workspace = true diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 78bca5a4993271883c555fe05366a7c9a0c472ac..8afb4e4eb8af70a78c1cd4fc0176a7fe3baf3c3e 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -1300,7 +1300,9 @@ mod tests { use super::*; - #[test] + use util_macros::perf; + + #[perf] fn test_basic_highlight_style_combination() { let style_a = HighlightStyle::default(); let style_b = HighlightStyle::default(); @@ -1385,7 +1387,7 @@ mod tests { ); } - #[test] + #[perf] fn test_combine_highlights() { assert_eq!( combine_highlights( diff --git a/crates/util_macros/Cargo.toml b/crates/util_macros/Cargo.toml index 996eefcb303ee5959f0e7fa920f1a91a509407eb..45145a68f6a7d54d759d932c3dc851d14f4939d9 100644 --- a/crates/util_macros/Cargo.toml +++ b/crates/util_macros/Cargo.toml @@ -17,3 +17,6 @@ doctest = false quote.workspace = true syn.workspace = true workspace-hack.workspace = true + +[features] +perf-enabled = [] diff --git a/crates/util_macros/src/util_macros.rs b/crates/util_macros/src/util_macros.rs index 9d0b06ab10a7454d6c0d19fd54722fd98db4ac25..d3f05afdecbca8cb3b4c8685054d3828e6c702fd 100644 --- a/crates/util_macros/src/util_macros.rs +++ b/crates/util_macros/src/util_macros.rs @@ -1,8 +1,9 @@ #![cfg_attr(not(target_os = "windows"), allow(unused))] +#![allow(clippy::test_attr_in_doctest)] use proc_macro::TokenStream; -use quote::quote; -use syn::{LitStr, parse_macro_input}; +use quote::{ToTokens, quote}; +use syn::{ItemFn, LitStr, parse_macro_input, parse_quote}; /// A macro used in tests for cross-platform path string literals in tests. On Windows it replaces /// `/` with `\\` and adds `C:` to the beginning of absolute paths. On other platforms, the path is @@ -87,3 +88,145 @@ pub fn line_endings(input: TokenStream) -> TokenStream { #text }) } + +/// Inner data for the perf macro. +struct PerfArgs { + /// How many times to loop a test before rerunning the test binary. + /// If left empty, the test harness will auto-determine this value. + iterations: Option, +} + +impl syn::parse::Parse for PerfArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(PerfArgs { iterations: None }); + } + + let mut iterations = None; + // In principle we only have one possible argument, but leave this as + // a loop in case we expand this in the future. + for meta in + syn::punctuated::Punctuated::::parse_terminated(input)? + { + match &meta { + syn::Meta::NameValue(meta_name_value) => { + if meta_name_value.path.is_ident("iterations") { + iterations = Some(meta_name_value.value.clone()); + } else { + return Err(syn::Error::new_spanned( + &meta_name_value.path, + "unexpected argument, expected 'iterations'", + )); + } + } + _ => { + return Err(syn::Error::new_spanned( + meta, + "expected name-value argument like 'iterations = 1'", + )); + } + } + } + + Ok(PerfArgs { iterations }) + } +} + +/// Marks a test as perf-sensitive, to be triaged when checking the performance +/// of a build. This also automatically applies `#[test]`. +/// +/// By default, the number of iterations when profiling this test is auto-determined. +/// If this needs to be overwritten, pass the desired iteration count to the macro +/// as a parameter (`#[perf(iterations = n)]`). Note that the actual profiler may still +/// run the test an arbitrary number times; this flag just sets the number of executions +/// before the process is restarted and global state is reset. +/// +/// # Usage notes +/// This should probably not be applied to tests that do any significant fs IO, as +/// locks on files may not be released in time when repeating a test many times. This +/// might lead to spurious failures. +/// +/// # Examples +/// ```rust +/// use util_macros::perf; +/// +/// #[perf] +/// fn expensive_computation_test() { +/// // Test goes here. +/// } +/// ``` +/// +/// This also works with `#[gpui::test]`s, though in most cases it shouldn't +/// be used with automatic iterations. +/// ```rust,ignore +/// use util_macros::perf; +/// +/// #[perf(iterations = 1)] +/// #[gpui::test] +/// fn oneshot_test(_cx: &mut gpui::TestAppContext) { +/// // Test goes here. +/// } +/// ``` +#[proc_macro_attribute] +pub fn perf(our_attr: TokenStream, input: TokenStream) -> TokenStream { + // If any of the below constants are changed, make sure to also update the perf + // profiler to match! + + /// The suffix on tests marked with `#[perf]`. + const SUF_NORMAL: &str = "__ZED_PERF"; + /// The suffix on tests marked with `#[perf(iterations = n)]`. + const SUF_FIXED: &str = "__ZED_PERF_FIXEDITER"; + /// The env var in which we pass the iteration count to our tests. + const ITER_ENV_VAR: &str = "ZED_PERF_ITER"; + + let iter_count = parse_macro_input!(our_attr as PerfArgs).iterations; + + let ItemFn { + mut attrs, + vis, + mut sig, + block, + } = parse_macro_input!(input as ItemFn); + attrs.push(parse_quote!(#[test])); + attrs.push(parse_quote!(#[allow(non_snake_case)])); + + let block: Box = if cfg!(perf_enabled) { + // Make the ident obvious when calling, for the test parser. + let mut new_ident = sig.ident.to_string(); + if iter_count.is_some() { + new_ident.push_str(SUF_FIXED); + } else { + new_ident.push_str(SUF_NORMAL); + } + + let new_ident = syn::Ident::new(&new_ident, sig.ident.span()); + sig.ident = new_ident; + // If we have a preset iteration count, just use that. + if let Some(iter_count) = iter_count { + parse_quote!({ + for _ in 0..#iter_count { + #block + } + }) + } else { + // Otherwise, the perf harness will pass us the value in an env var. + parse_quote!({ + let iter_count = std::env::var(#ITER_ENV_VAR).unwrap().parse::().unwrap(); + for _ in 0..iter_count { + #block + } + }) + } + } else { + block + }; + + ItemFn { + attrs, + vis, + sig, + block, + } + .into_token_stream() + .into() +} diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index abe92dd58594f05d8cf71dbde4fb129aafa26a03..a76d1f7ddc7b619ac231cd163a0721439255889a 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -46,6 +46,7 @@ theme.workspace = true tokio = { version = "1.15", features = ["full"], optional = true } ui.workspace = true util.workspace = true +util_macros.workspace = true vim_mode_setting.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 2256c2577ecd282f690ee7b3afe9e2b21b6e8788..03adfc8af15cf92f7ee6c4c857c0f154e2c969f3 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -25,6 +25,9 @@ use search::BufferSearchBar; use crate::{PushSneak, PushSneakBackward, insert::NormalBefore, motion, state::Mode}; +use util_macros::perf; + +#[perf] #[gpui::test] async fn test_initially_disabled(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, false).await; @@ -44,6 +47,7 @@ async fn test_neovim(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("ˇtest"); } +#[perf] #[gpui::test] async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -80,6 +84,7 @@ async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) { assert_eq!(cx.mode(), Mode::Normal); } +#[perf] #[gpui::test] async fn test_cancel_selection(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -104,6 +109,7 @@ async fn test_cancel_selection(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog"); } +#[perf] #[gpui::test] async fn test_buffer_search(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -132,6 +138,7 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) { }) } +#[perf] #[gpui::test] async fn test_count_down(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -143,6 +150,7 @@ async fn test_count_down(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe"); } +#[perf] #[gpui::test] async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -157,6 +165,7 @@ async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("aˇa\nbb\ncc"); } +#[perf] #[gpui::test] async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -175,6 +184,7 @@ async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("aa\nbb\ncˇc"); } +#[perf] #[gpui::test] async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -241,6 +251,7 @@ async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) { cx.assert_state("aˇbc\n", Mode::Insert); } +#[perf] #[gpui::test] async fn test_escape_cancels(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -251,6 +262,7 @@ async fn test_escape_cancels(cx: &mut gpui::TestAppContext) { cx.assert_state("aˇbc", Mode::Normal); } +#[perf] #[gpui::test] async fn test_selection_on_search(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -289,6 +301,7 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) { cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal); } +#[perf] #[gpui::test] async fn test_word_characters(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new_typescript(cx).await; @@ -315,6 +328,7 @@ async fn test_word_characters(cx: &mut gpui::TestAppContext) { ) } +#[perf] #[gpui::test] async fn test_kebab_case(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new_html(cx).await; @@ -821,6 +835,7 @@ async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) { two"}); } +#[perf] #[gpui::test] async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -881,6 +896,7 @@ fn assert_pending_input(cx: &mut VimTestContext, expected: &str) { }); } +#[perf] #[gpui::test] async fn test_jk_multi(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -972,6 +988,7 @@ async fn test_comma_w(cx: &mut gpui::TestAppContext) { .assert_eq("hellˇo hello\nhello hello"); } +#[perf] #[gpui::test] async fn test_completion_menu_scroll_aside(cx: &mut TestAppContext) { let mut cx = VimTestContext::new_typescript(cx).await; @@ -1053,6 +1070,7 @@ async fn test_completion_menu_scroll_aside(cx: &mut TestAppContext) { }); } +#[perf] #[gpui::test] async fn test_rename(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new_typescript(cx).await; @@ -1088,6 +1106,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal) } +#[perf(iterations = 1)] #[gpui::test] async fn test_remap(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1210,6 +1229,7 @@ async fn test_undo(cx: &mut gpui::TestAppContext) { 3"}); } +#[perf] #[gpui::test] async fn test_mouse_selection(cx: &mut TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1385,6 +1405,7 @@ async fn test_dw_eol(cx: &mut gpui::TestAppContext) { .assert_eq("twelve ˇtwelve char\ntwelve char"); } +#[perf] #[gpui::test] async fn test_toggle_comments(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1476,6 +1497,7 @@ async fn test_find_multibyte(cx: &mut gpui::TestAppContext) { .assert_eq(r#""#); } +#[perf] #[gpui::test] async fn test_sneak(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1695,6 +1717,7 @@ async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("ˇ"); } +#[perf] #[gpui::test] async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1850,6 +1873,7 @@ async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("hellˇllo world."); } +#[perf(iterations = 1)] #[gpui::test] async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) { VimTestContext::init(cx); @@ -2150,6 +2174,7 @@ async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq(indoc! {"ˇ"}); } +#[perf] #[gpui::test] async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; diff --git a/tooling/perf/Cargo.toml b/tooling/perf/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..f5013a82836b9888d94fb39fa18f0efa00e1b0ce --- /dev/null +++ b/tooling/perf/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "perf" +version = "0.1.0" +publish.workspace = true +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +workspace-hack.workspace = true diff --git a/tooling/perf/LICENSE-APACHE b/tooling/perf/LICENSE-APACHE new file mode 120000 index 0000000000000000000000000000000000000000..1cd601d0a3affae83854be02a0afdec3b7a9ec4d --- /dev/null +++ b/tooling/perf/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/tooling/perf/src/main.rs b/tooling/perf/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..a119811aba76afccc16dbef48e4dbee576b46fdc --- /dev/null +++ b/tooling/perf/src/main.rs @@ -0,0 +1,191 @@ +#![warn(clippy::all, clippy::pedantic, clippy::undocumented_unsafe_blocks)] +#![cfg_attr(release, deny(warnings))] + +//! Perf profiler for Zed tests. Outputs timings of tests marked with the `#[perf]` +//! attribute to stdout in Markdown. See the documentation of `util_macros::perf` +//! for usage details on the actual attribute. +//! +//! # Setup +//! Make sure `hyperfine` is installed and in the shell path, then run +//! `cargo build --bin perf --workspace --release` to build the profiler. +//! +//! # Usage +//! Calling this tool rebuilds everything with some cfg flags set for the perf +//! proc macro *and* enables optimisations (`release-fast` profile), so expect it +//! to take a little while. +//! +//! To test an individual crate, run: +//! ```sh +//! cargo perf-test -p $CRATE +//! ``` +//! +//! To test everything (which will be **VERY SLOW**), run: +//! ```sh +//! cargo perf-test --workspace +//! ``` +//! +//! # Notes +//! This should probably not be called manually unless you're working on the profiler +//! itself; use the `cargo perf-test` alias (after building this crate) instead. + +use std::{ + process::{Command, Stdio}, + time::{Duration, Instant}, +}; + +/// How many iterations to attempt the first time a test is run. +const DEFAULT_ITER_COUNT: usize = 12; +/// Multiplier for the iteration count when a test doesn't pass the noise cutoff. +const ITER_COUNT_MUL: usize = 4; +/// How long a test must have run to be assumed to be reliable-ish. +const NOISE_CUTOFF: Duration = Duration::from_millis(250); + +// If any of the below constants are changed, make sure to also update the perf +// proc macro to match! + +/// The suffix on tests marked with `#[perf]`. +const SUF_NORMAL: &str = "__ZED_PERF"; +/// The suffix on tests marked with `#[perf(iterations = n)]`. +const SUF_FIXED: &str = "__ZED_PERF_FIXEDITER"; +/// The env var in which we pass the iteration count to our tests. +const ITER_ENV_VAR: &str = "ZED_PERF_ITER"; + +#[allow(clippy::too_many_lines)] +fn main() { + // We get passed the test we need to run as the 1st argument after our own name. + let test_bin = std::env::args().nth(1).unwrap(); + let mut cmd = Command::new(&test_bin); + // --format=json is nightly-only :( + cmd.args(["--list", "--format=terse"]); + let out = cmd + .output() + .expect("FATAL: Could not run test binary {test_bin}"); + assert!( + out.status.success(), + "FATAL: Cannot do perf check - test binary {test_bin} returned an error" + ); + // Parse the test harness output to look for tests we care about. + let stdout = String::from_utf8_lossy(&out.stdout); + let mut test_list: Vec<_> = stdout + .lines() + .filter_map(|line| { + // This should split only in two; e.g., + // "app::test::test_arena: test" => "app::test::test_arena:", "test" + let line: Vec<_> = line.split_whitespace().collect(); + match line[..] { + // Final byte of t_name is ":", which we need to ignore. + [t_name, kind] => (kind == "test").then(|| &t_name[..t_name.len() - 1]), + _ => None, + } + }) + // Exclude tests that aren't marked for perf triage based on suffix. + .filter(|t_name| t_name.ends_with(SUF_NORMAL) || t_name.ends_with(SUF_FIXED)) + .collect(); + + // Pulling itertools just for .dedup() would be quite a big dependency that's + // not used elsewhere, so do this on the vec instead. + test_list.sort_unstable(); + test_list.dedup(); + + if !test_list.is_empty() { + // Print the markdown header which matches hyperfine's result. + // TODO: Support exporting JSON also. + println!( + "| Command | Mean [ms] | Min [ms] | Max [ms] | Iterations | Iter/sec |\n|:---|---:|---:|---:|---:|---:|" + ); + } + + // Spawn and profile an instance of each perf-sensitive test, via hyperfine. + for t_name in test_list { + // Pretty-print the stripped name for the test. + let t_name_normal = t_name.replace(SUF_FIXED, "").replace(SUF_NORMAL, ""); + // Time test execution to see how many iterations we need to do in order + // to account for random noise. This is skipped for tests with fixed + // iteration counts. + let final_iter_count = if t_name.ends_with(SUF_FIXED) { + None + } else { + let mut iter_count = DEFAULT_ITER_COUNT; + loop { + let mut cmd = Command::new(&test_bin); + cmd.args([t_name, "--exact"]); + cmd.env(ITER_ENV_VAR, format!("{iter_count}")); + // Don't let the child muck up our stdin/out/err. + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::null()); + cmd.stderr(Stdio::null()); + let pre = Instant::now(); + // Discard the output beyond ensuring success. + let out = cmd.spawn().unwrap().wait(); + let post = Instant::now(); + if !out.unwrap().success() { + println!( + "| {t_name_normal} (ERRORED IN TRIAGE) | N/A | N/A | N/A | {iter_count} | N/A |" + ); + return; + } + if post - pre > NOISE_CUTOFF { + break Some(iter_count); + } else if let Some(c) = iter_count.checked_mul(ITER_COUNT_MUL) { + iter_count = c; + } else { + // This should almost never happen, but maybe..? + eprintln!( + "WARNING: Running nearly usize::MAX iterations of test {t_name_normal}" + ); + break Some(iter_count); + } + } + }; + + // Now profile! + let mut perf_cmd = Command::new("hyperfine"); + // Warm up the cache and print markdown output to stdout. + perf_cmd.args([ + "--style", + "none", + "--warmup", + "1", + "--export-markdown", + "-", + &format!("{test_bin} {t_name}"), + ]); + if let Some(final_iter_count) = final_iter_count { + perf_cmd.env(ITER_ENV_VAR, format!("{final_iter_count}")); + } + let p_out = perf_cmd.output().unwrap(); + let fin_iter = match final_iter_count { + Some(i) => &format!("{i}"), + None => "(preset)", + }; + if p_out.status.success() { + let output = String::from_utf8_lossy(&p_out.stdout); + // Strip the name of the test binary from the table (and the space after it!) + // + our extraneous test bits + the "Relative" column (which is always at the end and "1.00"). + let output = output + .replace(&format!("{test_bin} "), "") + .replace(SUF_FIXED, "") + .replace(SUF_NORMAL, "") + .replace(" 1.00 |", ""); + // Can't use .last() since we have a trailing newline. Sigh. + let fin = output.lines().nth(3).unwrap(); + + // Calculate how many iterations this does per second, for easy comparison. + let ms = fin + .split_whitespace() + .nth(3) + .unwrap() + .parse::() + .unwrap(); + let mul_fac = 1000.0 / ms; + let iter_sec = match final_iter_count { + #[allow(clippy::cast_precision_loss)] + Some(c) => &format!("{:.1}", mul_fac * c as f64), + None => "(unknown)", + }; + println!("{fin} {fin_iter} | {iter_sec} |"); + } else { + println!("{t_name_normal} (ERRORED) | N/A | N/A | N/A | {fin_iter} | N/A |"); + } + } +} From ffa23d25e315eda8079664fb8fe2d8618003ef4b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 20 Sep 2025 11:23:02 -0400 Subject: [PATCH 162/721] Fix formatting in workspace `Cargo.toml` (#38563) This PR fixes some formatting issues in the workspace `Cargo.toml`. Release Notes: - N/A --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ad07429243817b27b4c09fc651b50de820183a9d..fd08fbb3f971e84d27e469bd79888531366109c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -219,8 +219,9 @@ members = [ # Tooling # + "tooling/perf", "tooling/workspace-hack", - "tooling/xtask", "tooling/perf", + "tooling/xtask", ] default-members = ["crates/zed"] From 1d1bbf01a931720febd3fb6a1a10b27e27c06179 Mon Sep 17 00:00:00 2001 From: Vitaly Slobodin Date: Sat, 20 Sep 2025 19:29:12 +0200 Subject: [PATCH 163/721] docs: Mention `herb` LSP for Ruby language (#38351) Hi! This pull request mentions [the `herb` LSP](https://herb-tools.dev) for `HTML/ERB` language that the Ruby extension supports. Thanks! Release Notes: - N/A --------- Co-authored-by: Finn Evers --- docs/src/languages/ruby.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index ef4b026db1db85ccf9104fdd3522ea27d2e1b50f..bcab5333d7fc3216eb6475acfe47b51013010afe 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -9,6 +9,7 @@ Ruby support is available through the [Ruby extension](https://github.com/zed-ex - [ruby-lsp](https://github.com/Shopify/ruby-lsp) - [solargraph](https://github.com/castwide/solargraph) - [rubocop](https://github.com/rubocop/rubocop) + - [Herb](https://herb-tools.dev) - Debug Adapter: [`rdbg`](https://github.com/ruby/debug) The Ruby extension also provides support for ERB files. @@ -27,6 +28,7 @@ In addition to these two language servers, Zed also supports: - [rubocop](https://github.com/rubocop/rubocop) which is a static code analyzer and linter for Ruby. Under the hood, it's also used by Zed as a language server, but its functionality is complimentary to that of solargraph and ruby-lsp. - [sorbet](https://sorbet.org/) which is a static type checker for Ruby with a custom gradual type system. - [steep](https://github.com/soutaro/steep) which is a static type checker for Ruby that leverages Ruby Signature (RBS). +- [Herb](https://herb-tools.dev) which is a language server for ERB files. When configuring a language server, it helps to open the LSP Logs window using the 'dev: Open Language Server Logs' command. You can then choose the corresponding language instance to see any logged information. @@ -238,6 +240,10 @@ To enable Steep, add `\"steep\"` to the `language_servers` list for Ruby in your } ``` +## Setting up Herb + +`Herb` is enabled by default for the `HTML/ERB` language. + ## Using the Tailwind CSS Language Server with Ruby It's possible to use the [Tailwind CSS Language Server](https://github.com/tailwindlabs/tailwindcss-intellisense/tree/HEAD/packages/tailwindcss-language-server#readme) in Ruby and ERB files. From f5c2e4b49e5126d29cbee05733072f7ed769304e Mon Sep 17 00:00:00 2001 From: CharlesChen0823 Date: Sun, 21 Sep 2025 02:01:55 +0800 Subject: [PATCH 164/721] vim: Remove duplicate bracket pair (#38560) remove depulicate code, this same with line: 556-562 Release Notes: - N/A --- crates/vim/src/surrounds.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 5e25b08dd8656887b2013df52a5e7d62fce5dbe0..3ce4e6a2e94b1090714a81195c5562374efb95eb 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -602,13 +602,6 @@ fn all_support_surround_pair() -> Vec { surround: true, newline: false, }, - BracketPair { - start: "{".into(), - end: "}".into(), - close: true, - surround: true, - newline: false, - }, BracketPair { start: "<".into(), end: ">".into(), From 18df6a81b420914507515e7ba3c19665e4e11197 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Sat, 20 Sep 2025 14:14:55 -0400 Subject: [PATCH 165/721] acp: Fix spawning login task (#38567) Reverts #38175, which is not correct, since in fact we do need to pre-quote the command and arguments for the shell when using `SpawnInTerminal` (although we should probably change the API so that this isn't necessary). Then, applies the same fix as #38565 to fix the root cause of being unable to spawn the login task on macOS, or in any case where the command/args contain spaces. Release Notes: - Fixed being unable to login with Claude Code or Gemini using the terminal. --- Cargo.lock | 1 + crates/agent_ui/Cargo.toml | 1 + crates/agent_ui/src/acp/thread_view.rs | 34 ++++++++++++-------------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84ae8e613365ef3976970e61dfc7b03aaf969062..6293b0cc2da475ef5f2282a039c123c76d31e1c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -419,6 +419,7 @@ dependencies = [ "serde_json", "serde_json_lenient", "settings", + "shlex", "smol", "streaming_diff", "task", diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml index 47d9f6d6a27a2ad5102e831094912208e66a9b43..028db95c10a8c7a319bb05927dcabd0564a14683 100644 --- a/crates/agent_ui/Cargo.toml +++ b/crates/agent_ui/Cargo.toml @@ -80,6 +80,7 @@ serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true settings.workspace = true +shlex.workspace = true smol.workspace = true streaming_diff.workspace = true task.workspace = true diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index cd72be9b184ded0d53125bfd569da89acff59a48..8658e2c285997c18ece2b9783c25fbcaa614dc83 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -9,7 +9,7 @@ use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, AgentServerDelegate}; use agent_settings::{AgentProfileId, AgentSettings, CompletionMode}; use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer}; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Context as _, Result, anyhow, bail}; use arrayvec::ArrayVec; use audio::{Audio, Sound}; use buffer_diff::BufferDiff; @@ -1582,6 +1582,19 @@ impl AcpThreadView { window.spawn(cx, async move |cx| { let mut task = login.clone(); + task.command = task + .command + .map(|command| anyhow::Ok(shlex::try_quote(&command)?.to_string())) + .transpose()?; + task.args = task + .args + .iter() + .map(|arg| { + Ok(shlex::try_quote(arg) + .context("Failed to quote argument")? + .to_string()) + }) + .collect::>>()?; task.full_label = task.label.clone(); task.id = task::TaskId(format!("external-agent-{}-login", task.label)); task.command_label = task.label.clone(); @@ -1591,7 +1604,7 @@ impl AcpThreadView { task.shell = shell; let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| { - terminal_panel.spawn_task(&login, window, cx) + terminal_panel.spawn_task(&task, window, cx) })?; let terminal = terminal.await?; @@ -5669,23 +5682,6 @@ pub(crate) mod tests { }); } - #[gpui::test] - async fn test_spawn_external_agent_login_handles_spaces(cx: &mut TestAppContext) { - init_test(cx); - - // Verify paths with spaces aren't pre-quoted - let path_with_spaces = "/Users/test/Library/Application Support/Zed/cli.js"; - let login_task = task::SpawnInTerminal { - command: Some("node".to_string()), - args: vec![path_with_spaces.to_string(), "/login".to_string()], - ..Default::default() - }; - - // Args should be passed as-is, not pre-quoted - assert!(!login_task.args[0].starts_with('"')); - assert!(!login_task.args[0].starts_with('\'')); - } - #[gpui::test] async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) { init_test(cx); From 839c216620af116459e2ba15e82f3df8c3597349 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 20 Sep 2025 22:10:47 +0200 Subject: [PATCH 166/721] terminal: Re-add sanitizing trailing periods in URL detection (#38569) I accidentally regressed this when bumping alacritty in https://github.com/zed-industries/zed/pull/38505 cc @davewa Release Notes: - N/A --- crates/terminal/src/terminal_hyperlinks.rs | 145 ++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal_hyperlinks.rs b/crates/terminal/src/terminal_hyperlinks.rs index 25db02c5e84f692622a1c97ed891c886b02b26a9..3c20261988a7b30e124000bcdae7596c162d0853 100644 --- a/crates/terminal/src/terminal_hyperlinks.rs +++ b/crates/terminal/src/terminal_hyperlinks.rs @@ -79,7 +79,8 @@ pub(super) fn find_from_grid_point( Some((url, true, url_match)) } else if let Some(url_match) = regex_match_at(term, point, &mut regex_searches.url_regex) { let url = term.bounds_to_string(*url_match.start(), *url_match.end()); - Some((url, true, url_match)) + let (sanitized_url, sanitized_match) = sanitize_url_punctuation(url, url_match, term); + Some((sanitized_url, true, sanitized_match)) } else if let Some(python_match) = regex_match_at(term, point, &mut regex_searches.python_file_line_regex) { @@ -164,6 +165,63 @@ pub(super) fn find_from_grid_point( }) } +fn sanitize_url_punctuation( + url: String, + url_match: Match, + term: &Term, +) -> (String, Match) { + let mut sanitized_url = url; + let mut chars_trimmed = 0; + + // First, handle parentheses balancing using single traversal + let (open_parens, close_parens) = + sanitized_url + .chars() + .fold((0, 0), |(opens, closes), c| match c { + '(' => (opens + 1, closes), + ')' => (opens, closes + 1), + _ => (opens, closes), + }); + + // Trim unbalanced closing parentheses + if close_parens > open_parens { + let mut remaining_close = close_parens; + while sanitized_url.ends_with(')') && remaining_close > open_parens { + sanitized_url.pop(); + chars_trimmed += 1; + remaining_close -= 1; + } + } + + // Handle trailing periods + if sanitized_url.ends_with('.') { + let trailing_periods = sanitized_url + .chars() + .rev() + .take_while(|&c| c == '.') + .count(); + + if trailing_periods > 1 { + sanitized_url.truncate(sanitized_url.len() - trailing_periods); + chars_trimmed += trailing_periods; + } else if trailing_periods == 1 + && let Some(second_last_char) = sanitized_url.chars().rev().nth(1) + && (second_last_char.is_alphanumeric() || second_last_char == '/') + { + sanitized_url.pop(); + chars_trimmed += 1; + } + } + + if chars_trimmed > 0 { + let new_end = url_match.end().sub(term, Boundary::Grid, chars_trimmed); + let sanitized_match = Match::new(*url_match.start(), new_end); + (sanitized_url, sanitized_match) + } else { + (sanitized_url, url_match) + } +} + fn is_path_surrounded_by_common_symbols(path: &str) -> bool { // Avoid detecting `[]` or `()` strings as paths, surrounded by common symbols path.len() > 2 @@ -233,6 +291,91 @@ mod tests { ); } + #[test] + fn test_url_parentheses_sanitization() { + // Test our sanitize_url_parentheses function directly + let test_cases = vec![ + // Cases that should be sanitized (unbalanced parentheses) + ("https://www.google.com/)", "https://www.google.com/"), + ("https://example.com/path)", "https://example.com/path"), + ("https://test.com/))", "https://test.com/"), + // Cases that should NOT be sanitized (balanced parentheses) + ( + "https://en.wikipedia.org/wiki/Example_(disambiguation)", + "https://en.wikipedia.org/wiki/Example_(disambiguation)", + ), + ("https://test.com/(hello)", "https://test.com/(hello)"), + ( + "https://example.com/path(1)(2)", + "https://example.com/path(1)(2)", + ), + // Edge cases + ("https://test.com/", "https://test.com/"), + ("https://example.com", "https://example.com"), + ]; + + for (input, expected) in test_cases { + // Create a minimal terminal for testing + let term = Term::new(Config::default(), &TermSize::new(80, 24), VoidListener); + + // Create a dummy match that spans the entire input + let start_point = AlacPoint::new(Line(0), Column(0)); + let end_point = AlacPoint::new(Line(0), Column(input.len())); + let dummy_match = Match::new(start_point, end_point); + + let (result, _) = sanitize_url_punctuation(input.to_string(), dummy_match, &term); + assert_eq!(result, expected, "Failed for input: {}", input); + } + } + + #[test] + fn test_url_periods_sanitization() { + // Test URLs with trailing periods (sentence punctuation) + let test_cases = vec![ + // Cases that should be sanitized (trailing periods likely punctuation) + ("https://example.com.", "https://example.com"), + ( + "https://github.com/zed-industries/zed.", + "https://github.com/zed-industries/zed", + ), + ( + "https://example.com/path/file.html.", + "https://example.com/path/file.html", + ), + ( + "https://example.com/file.pdf.", + "https://example.com/file.pdf", + ), + ("https://example.com:8080.", "https://example.com:8080"), + ("https://example.com..", "https://example.com"), + ( + "https://en.wikipedia.org/wiki/C.E.O.", + "https://en.wikipedia.org/wiki/C.E.O", + ), + // Cases that should NOT be sanitized (periods are part of URL structure) + ( + "https://example.com/v1.0/api", + "https://example.com/v1.0/api", + ), + ("https://192.168.1.1", "https://192.168.1.1"), + ("https://sub.domain.com", "https://sub.domain.com"), + ]; + + for (input, expected) in test_cases { + // Create a minimal terminal for testing + let term = Term::new(Config::default(), &TermSize::new(80, 24), VoidListener); + + // Create a dummy match that spans the entire input + let start_point = AlacPoint::new(Line(0), Column(0)); + let end_point = AlacPoint::new(Line(0), Column(input.len())); + let dummy_match = Match::new(start_point, end_point); + + // This test should initially fail since we haven't implemented period sanitization yet + let (result, _) = sanitize_url_punctuation(input.to_string(), dummy_match, &term); + assert_eq!(result, expected, "Failed for input: {}", input); + } + } + #[test] fn test_word_regex() { re_test( From 11041ef3b0b9751b70890190601dc3e247ebde7b Mon Sep 17 00:00:00 2001 From: Nia Date: Sun, 21 Sep 2025 13:54:59 +0200 Subject: [PATCH 167/721] perf: Greatly expand profiler (#38584) Expands on #38543 (notably allows setting importance categories and weights on tests, and a lot of internal refactoring) because I couldn't help myself. Also allows exporting runs to json and comparing across them. See code for docs. Release Notes: - N/A --- .cargo/config.toml | 2 + .gitignore | 1 + Cargo.lock | 4 + Cargo.toml | 1 + crates/util_macros/Cargo.toml | 1 + crates/util_macros/src/util_macros.rs | 224 ++++++++------ tooling/perf/Cargo.toml | 23 +- tooling/perf/src/lib.rs | 413 +++++++++++++++++++++++++ tooling/perf/src/main.rs | 414 +++++++++++++++++++++----- 9 files changed, 917 insertions(+), 166 deletions(-) create mode 100644 tooling/perf/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 74d34226af09c11b56faa6722e00afa218c924f5..9da793fc48b62f7f03cd1d36a505fa1e1ef2a45a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,6 +5,7 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"] [alias] xtask = "run --package xtask --" perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--config", "target.'cfg(true)'.runner='target/release/perf'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"] +perf-compare = ["run", "--release", "-p", "perf", "--", "compare"] [target.x86_64-unknown-linux-gnu] linker = "clang" @@ -24,3 +25,4 @@ rustflags = [ [env] MACOSX_DEPLOYMENT_TARGET = "10.15.7" +CARGO_WORKSPACE_DIR = { value = "", relative = true } diff --git a/.gitignore b/.gitignore index 7b40c45adf614eb91f1676144e7b70a7b2a373f2..d248b1f7e5adf30cb286a1737c1cd4f72f0f5d20 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ .venv .vscode .wrangler +.perf-runs /assets/*licenses.* /crates/collab/seed.json /crates/theme/schemas/theme.json diff --git a/Cargo.lock b/Cargo.lock index 6293b0cc2da475ef5f2282a039c123c76d31e1c7..dbe2467499ad1c5d6f67c4de82546e2b560451bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12169,6 +12169,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" name = "perf" version = "0.1.0" dependencies = [ + "collections", + "serde", + "serde_json", "workspace-hack", ] @@ -18586,6 +18589,7 @@ dependencies = [ name = "util_macros" version = "0.1.0" dependencies = [ + "perf", "quote", "syn 2.0.101", "workspace-hack", diff --git a/Cargo.toml b/Cargo.toml index fd08fbb3f971e84d27e469bd79888531366109c4..d4812908ac8292caf8371ce1d6dd9c9ee4042ca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -356,6 +356,7 @@ outline = { path = "crates/outline" } outline_panel = { path = "crates/outline_panel" } panel = { path = "crates/panel" } paths = { path = "crates/paths" } +perf = { path = "tooling/perf" } picker = { path = "crates/picker" } plugin = { path = "crates/plugin" } plugin_macros = { path = "crates/plugin_macros" } diff --git a/crates/util_macros/Cargo.toml b/crates/util_macros/Cargo.toml index 45145a68f6a7d54d759d932c3dc851d14f4939d9..344331f1395ebb90251c4b08f92c57f28213fa4f 100644 --- a/crates/util_macros/Cargo.toml +++ b/crates/util_macros/Cargo.toml @@ -16,6 +16,7 @@ doctest = false [dependencies] quote.workspace = true syn.workspace = true +perf.workspace = true workspace-hack.workspace = true [features] diff --git a/crates/util_macros/src/util_macros.rs b/crates/util_macros/src/util_macros.rs index d3f05afdecbca8cb3b4c8685054d3828e6c702fd..69f6306133f490087b2cefeb71aeafab08b98a9a 100644 --- a/crates/util_macros/src/util_macros.rs +++ b/crates/util_macros/src/util_macros.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(target_os = "windows"), allow(unused))] #![allow(clippy::test_attr_in_doctest)] +use perf::*; use proc_macro::TokenStream; use quote::{ToTokens, quote}; use syn::{ItemFn, LitStr, parse_macro_input, parse_quote}; @@ -90,68 +91,81 @@ pub fn line_endings(input: TokenStream) -> TokenStream { } /// Inner data for the perf macro. +#[derive(Default)] struct PerfArgs { - /// How many times to loop a test before rerunning the test binary. - /// If left empty, the test harness will auto-determine this value. + /// How many times to loop a test before rerunning the test binary. If left + /// empty, the test harness will auto-determine this value. iterations: Option, + /// How much this test's results should be weighed when comparing across runs. + /// If unspecified, defaults to `WEIGHT_DEFAULT` (50). + weight: Option, + /// How relevant a benchmark is to overall performance. See docs on the enum + /// for details. If unspecified, `Average` is selected. + importance: Importance, } -impl syn::parse::Parse for PerfArgs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - if input.is_empty() { - return Ok(PerfArgs { iterations: None }); - } - - let mut iterations = None; - // In principle we only have one possible argument, but leave this as - // a loop in case we expand this in the future. - for meta in - syn::punctuated::Punctuated::::parse_terminated(input)? - { - match &meta { - syn::Meta::NameValue(meta_name_value) => { - if meta_name_value.path.is_ident("iterations") { - iterations = Some(meta_name_value.value.clone()); - } else { - return Err(syn::Error::new_spanned( - &meta_name_value.path, - "unexpected argument, expected 'iterations'", - )); - } - } - _ => { - return Err(syn::Error::new_spanned( - meta, - "expected name-value argument like 'iterations = 1'", - )); - } - } +#[warn(clippy::all, clippy::pedantic)] +impl PerfArgs { + /// Parses attribute arguments into a `PerfArgs`. + fn parse_into(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { + if meta.path.is_ident("iterations") { + self.iterations = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("weight") { + self.weight = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("critical") { + self.importance = Importance::Critical; + } else if meta.path.is_ident("important") { + self.importance = Importance::Important; + } else if meta.path.is_ident("average") { + // This shouldn't be specified manually, but oh well. + self.importance = Importance::Average; + } else if meta.path.is_ident("iffy") { + self.importance = Importance::Iffy; + } else if meta.path.is_ident("fluff") { + self.importance = Importance::Fluff; + } else { + return Err(syn::Error::new_spanned(meta.path, "unexpected identifier")); } - - Ok(PerfArgs { iterations }) + Ok(()) } } /// Marks a test as perf-sensitive, to be triaged when checking the performance /// of a build. This also automatically applies `#[test]`. /// +/// +/// # Usage +/// Applying this attribute to a test marks it as average importance by default. +/// There are 4 levels of importance (`Critical`, `Important`, `Average`, `Fluff`); +/// see the documentation on `Importance` for details. Add the importance as a +/// parameter to override the default (e.g. `#[perf(important)]`). +/// +/// Each test also has a weight factor. This is irrelevant on its own, but is considered +/// when comparing results across different runs. By default, this is set to 50; +/// pass `weight = n` as a parameter to override this. Note that this value is only +/// relevant within its importance category. +/// /// By default, the number of iterations when profiling this test is auto-determined. -/// If this needs to be overwritten, pass the desired iteration count to the macro -/// as a parameter (`#[perf(iterations = n)]`). Note that the actual profiler may still -/// run the test an arbitrary number times; this flag just sets the number of executions -/// before the process is restarted and global state is reset. +/// If this needs to be overwritten, pass the desired iteration count as a parameter +/// (`#[perf(iterations = n)]`). Note that the actual profiler may still run the test +/// an arbitrary number times; this flag just sets the number of executions before the +/// process is restarted and global state is reset. /// -/// # Usage notes -/// This should probably not be applied to tests that do any significant fs IO, as -/// locks on files may not be released in time when repeating a test many times. This -/// might lead to spurious failures. +/// This attribute should probably not be applied to tests that do any significant +/// disk IO, as locks on files may not be released in time when repeating a test many +/// times. This might lead to spurious failures. /// /// # Examples /// ```rust /// use util_macros::perf; /// /// #[perf] -/// fn expensive_computation_test() { +/// fn generic_test() { +/// // Test goes here. +/// } +/// +/// #[perf(fluff, weight = 30)] +/// fn cold_path_test() { /// // Test goes here. /// } /// ``` @@ -161,72 +175,108 @@ impl syn::parse::Parse for PerfArgs { /// ```rust,ignore /// use util_macros::perf; /// -/// #[perf(iterations = 1)] +/// #[perf(iterations = 1, critical)] /// #[gpui::test] /// fn oneshot_test(_cx: &mut gpui::TestAppContext) { /// // Test goes here. /// } /// ``` #[proc_macro_attribute] +#[warn(clippy::all, clippy::pedantic)] pub fn perf(our_attr: TokenStream, input: TokenStream) -> TokenStream { - // If any of the below constants are changed, make sure to also update the perf - // profiler to match! - - /// The suffix on tests marked with `#[perf]`. - const SUF_NORMAL: &str = "__ZED_PERF"; - /// The suffix on tests marked with `#[perf(iterations = n)]`. - const SUF_FIXED: &str = "__ZED_PERF_FIXEDITER"; - /// The env var in which we pass the iteration count to our tests. - const ITER_ENV_VAR: &str = "ZED_PERF_ITER"; - - let iter_count = parse_macro_input!(our_attr as PerfArgs).iterations; + let mut args = PerfArgs::default(); + let parser = syn::meta::parser(|meta| PerfArgs::parse_into(&mut args, meta)); + parse_macro_input!(our_attr with parser); let ItemFn { - mut attrs, + attrs: mut attrs_main, vis, - mut sig, + sig: mut sig_main, block, } = parse_macro_input!(input as ItemFn); - attrs.push(parse_quote!(#[test])); - attrs.push(parse_quote!(#[allow(non_snake_case)])); + attrs_main.push(parse_quote!(#[test])); + attrs_main.push(parse_quote!(#[allow(non_snake_case)])); + + let fns = if cfg!(perf_enabled) { + #[allow(clippy::wildcard_imports, reason = "We control the other side")] + use consts::*; - let block: Box = if cfg!(perf_enabled) { // Make the ident obvious when calling, for the test parser. - let mut new_ident = sig.ident.to_string(); - if iter_count.is_some() { - new_ident.push_str(SUF_FIXED); - } else { - new_ident.push_str(SUF_NORMAL); - } + // Also set up values for the second metadata-returning "test". + let mut new_ident_main = sig_main.ident.to_string(); + let mut new_ident_meta = new_ident_main.clone(); + new_ident_main.push_str(SUF_NORMAL); + new_ident_meta.push_str(SUF_MDATA); - let new_ident = syn::Ident::new(&new_ident, sig.ident.span()); - sig.ident = new_ident; - // If we have a preset iteration count, just use that. - if let Some(iter_count) = iter_count { - parse_quote!({ - for _ in 0..#iter_count { - #block - } - }) - } else { - // Otherwise, the perf harness will pass us the value in an env var. + let new_ident_main = syn::Ident::new(&new_ident_main, sig_main.ident.span()); + sig_main.ident = new_ident_main; + + // We don't want any nonsense if the original test had a weird signature. + let new_ident_meta = syn::Ident::new(&new_ident_meta, sig_main.ident.span()); + let sig_meta = parse_quote!(fn #new_ident_meta()); + let attrs_meta = parse_quote!(#[test] #[allow(non_snake_case)]); + + // Make the test loop as the harness instructs it to. + let block_main = { + // The perf harness will pass us the value in an env var. Even if we + // have a preset value, just do this to keep the code paths unified. parse_quote!({ let iter_count = std::env::var(#ITER_ENV_VAR).unwrap().parse::().unwrap(); for _ in 0..iter_count { #block } }) - } + }; + let importance = format!("{}", args.importance); + let block_meta = { + // This function's job is to just print some relevant info to stdout, + // based on the params this attr is passed. It's not an actual test. + // Since we use a custom attr set on our metadata fn, it shouldn't + // cause problems with xfail tests. + let q_iter = if let Some(iter) = args.iterations { + quote! { + println!("{} {} {}", #MDATA_LINE_PREF, #ITER_COUNT_LINE_NAME, #iter); + } + } else { + quote! {} + }; + let weight = args + .weight + .unwrap_or_else(|| parse_quote! { #WEIGHT_DEFAULT }); + parse_quote!({ + #q_iter + println!("{} {} {}", #MDATA_LINE_PREF, #WEIGHT_LINE_NAME, #weight); + println!("{} {} {}", #MDATA_LINE_PREF, #IMPORTANCE_LINE_NAME, #importance); + println!("{} {} {}", #MDATA_LINE_PREF, #VERSION_LINE_NAME, #MDATA_VER); + }) + }; + + vec![ + // The real test. + ItemFn { + attrs: attrs_main, + vis: vis.clone(), + sig: sig_main, + block: block_main, + }, + // The fake test. + ItemFn { + attrs: attrs_meta, + vis, + sig: sig_meta, + block: block_meta, + }, + ] } else { - block + vec![ItemFn { + attrs: attrs_main, + vis, + sig: sig_main, + block, + }] }; - ItemFn { - attrs, - vis, - sig, - block, - } - .into_token_stream() - .into() + fns.into_iter() + .flat_map(|f| TokenStream::from(f.into_token_stream())) + .collect() } diff --git a/tooling/perf/Cargo.toml b/tooling/perf/Cargo.toml index f5013a82836b9888d94fb39fa18f0efa00e1b0ce..4766b58d8a760aa995dba7092d33c436559019c2 100644 --- a/tooling/perf/Cargo.toml +++ b/tooling/perf/Cargo.toml @@ -1,11 +1,30 @@ [package] name = "perf" version = "0.1.0" +description = "A tool for measuring Zed test performance, with too many Clippy lints" publish.workspace = true edition.workspace = true -[lints] -workspace = true +[lib] + +# Some personal lint preferences :3 +[lints.rust] +missing_docs = "warn" + +[lints.clippy] +needless_continue = "allow" # For a convenience macro +all = "warn" +pedantic = "warn" +style = "warn" +missing_docs_in_private_items = "warn" +as_underscore = "deny" +allow_attributes_without_reason = "deny" +let_underscore_must_use = "forbid" +undocumented_unsafe_blocks = "forbid" +missing_safety_doc = "forbid" [dependencies] +collections.workspace = true +serde.workspace = true +serde_json.workspace = true workspace-hack.workspace = true diff --git a/tooling/perf/src/lib.rs b/tooling/perf/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..30909f646b061895e10f5c860149e2370892ccd2 --- /dev/null +++ b/tooling/perf/src/lib.rs @@ -0,0 +1,413 @@ +//! Some constants and datatypes used in the Zed perf profiler. Should only be +//! consumed by the crate providing the matching macros. + +use collections::HashMap; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +pub mod consts { + //! Preset idenitifiers and constants so that the profiler and proc macro agree + //! on their communication protocol. + + /// The suffix on the actual test function. + pub const SUF_NORMAL: &str = "__ZED_PERF_FN"; + /// The suffix on an extra function which prints metadata about a test to stdout. + pub const SUF_MDATA: &str = "__ZED_PERF_MDATA"; + /// The env var in which we pass the iteration count to our tests. + pub const ITER_ENV_VAR: &str = "ZED_PERF_ITER"; + /// The prefix printed on all benchmark test metadata lines, to distinguish it from + /// possible output by the test harness itself. + pub const MDATA_LINE_PREF: &str = "ZED_MDATA_"; + /// The version number for the data returned from the test metadata function. + /// Increment on non-backwards-compatible changes. + pub const MDATA_VER: u32 = 0; + /// The default weight, if none is specified. + pub const WEIGHT_DEFAULT: u8 = 50; + /// How long a test must have run to be assumed to be reliable-ish. + pub const NOISE_CUTOFF: std::time::Duration = std::time::Duration::from_millis(250); + + /// Identifier for the iteration count of a test metadata. + pub const ITER_COUNT_LINE_NAME: &str = "iter_count"; + /// Identifier for the weight of a test metadata. + pub const WEIGHT_LINE_NAME: &str = "weight"; + /// Identifier for importance in test metadata. + pub const IMPORTANCE_LINE_NAME: &str = "importance"; + /// Identifier for the test metadata version. + pub const VERSION_LINE_NAME: &str = "version"; + + /// Where to save json run information. + pub const RUNS_DIR: &str = ".perf-runs"; +} + +/// How relevant a benchmark is. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub enum Importance { + /// Regressions shouldn't be accepted without good reason. + Critical = 4, + /// Regressions should be paid extra attention. + Important = 3, + /// No extra attention should be paid to regressions, but they might still + /// be indicative of something happening. + #[default] + Average = 2, + /// Unclear if regressions are likely to be meaningful, but still worth keeping + /// an eye on. Lowest level that's checked by default by the profiler. + Iffy = 1, + /// Regressions are likely to be spurious or don't affect core functionality. + /// Only relevant if a lot of them happen, or as supplemental evidence for a + /// higher-importance benchmark regressing. Not checked by default. + Fluff = 0, +} + +impl std::fmt::Display for Importance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Importance::Critical => f.write_str("critical"), + Importance::Important => f.write_str("important"), + Importance::Average => f.write_str("average"), + Importance::Iffy => f.write_str("iffy"), + Importance::Fluff => f.write_str("fluff"), + } + } +} + +/// Why or when did this test fail? +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum FailKind { + /// Failed while triaging it to determine the iteration count. + Triage, + /// Failed while profiling it. + Profile, + /// Failed due to an incompatible version for the test. + VersionMismatch, + /// Skipped due to filters applied on the perf run. + Skipped, +} + +impl std::fmt::Display for FailKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FailKind::Triage => f.write_str("failed in triage"), + FailKind::Profile => f.write_str("failed while profiling"), + FailKind::VersionMismatch => f.write_str("test version mismatch"), + FailKind::Skipped => f.write_str("skipped"), + } + } +} + +/// Information about a given perf test. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestMdata { + /// A version number for when the test was generated. If this is greater + /// than the version this test handler expects, one of the following will + /// happen in an unspecified manner: + /// - The test is skipped silently. + /// - The handler exits with an error message indicating the version mismatch + /// or inability to parse the metadata. + /// + /// INVARIANT: If `version` <= `MDATA_VER`, this tool *must* be able to + /// correctly parse the output of this test. + pub version: u32, + /// How many iterations to pass this test, if this is preset. + pub iterations: Option, + /// The importance of this particular test. See the docs on `Importance` for + /// details. + pub importance: Importance, + /// The weight of this particular test within its importance category. Used + /// when comparing across runs. + pub weight: u8, +} + +/// The actual timings of a test, as measured by Hyperfine. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Timings { + /// Mean runtime for `self.iter_total` runs of this test. + pub mean: Duration, + /// Standard deviation for the above. + pub stddev: Duration, +} + +impl Timings { + /// How many iterations does this test seem to do per second? + #[expect( + clippy::cast_precision_loss, + reason = "We only care about a couple sig figs anyways" + )] + #[must_use] + pub fn iters_per_sec(&self, total_iters: usize) -> f64 { + (1000. / self.mean.as_millis() as f64) * total_iters as f64 + } +} + +/// Aggregate output of all tests run by this handler. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Output { + /// A list of test outputs. Format is `(test_name, iter_count, timings)`. + /// The latter being set indicates the test succeeded. + /// + /// INVARIANT: If the test succeeded, the second field is `Some(mdata)` and + /// `mdata.iterations` is `Some(_)`. + tests: Vec<(String, Option, Result)>, +} + +impl Output { + /// Instantiates an empty "output". Useful for merging. + #[must_use] + pub fn blank() -> Self { + Output { tests: Vec::new() } + } + + /// Reports a success and adds it to this run's `Output`. + pub fn success( + &mut self, + name: impl AsRef, + mut mdata: TestMdata, + iters: usize, + timings: Timings, + ) { + mdata.iterations = Some(iters); + self.tests + .push((name.as_ref().to_string(), Some(mdata), Ok(timings))); + } + + /// Reports a failure and adds it to this run's `Output`. If this test was tried + /// with some number of iterations (i.e. this was not a version mismatch or skipped + /// test), it should be reported also. + /// + /// Using the `fail!()` macro is usually more convenient. + pub fn failure( + &mut self, + name: impl AsRef, + mut mdata: Option, + attempted_iters: Option, + kind: FailKind, + ) { + if let Some(ref mut mdata) = mdata { + mdata.iterations = attempted_iters; + } + self.tests + .push((name.as_ref().to_string(), mdata, Err(kind))); + } + + /// True if no tests executed this run. + #[must_use] + pub fn is_empty(&self) -> bool { + self.tests.is_empty() + } + + /// Sorts the runs in the output in the order that we want it printed. + pub fn sort(&mut self) { + self.tests.sort_unstable_by(|a, b| match (a, b) { + // Tests where we got no metadata go at the end. + ((_, Some(_), _), (_, None, _)) => std::cmp::Ordering::Greater, + ((_, None, _), (_, Some(_), _)) => std::cmp::Ordering::Less, + // Then sort by importance, then weight. + ((_, Some(a_mdata), _), (_, Some(b_mdata), _)) => { + let c = a_mdata.importance.cmp(&b_mdata.importance); + if matches!(c, std::cmp::Ordering::Equal) { + a_mdata.weight.cmp(&b_mdata.weight) + } else { + c + } + } + // Lastly by name. + ((a_name, ..), (b_name, ..)) => a_name.cmp(b_name), + }); + } + + /// Merges the output of two runs, appending a prefix to the results of the new run. + /// To be used in conjunction with `Output::blank()`, or else only some tests will have + /// a prefix set. + pub fn merge(&mut self, other: Self, pref_other: impl AsRef) { + self.tests = std::mem::take(&mut self.tests) + .into_iter() + .chain(other.tests.into_iter().map(|(name, md, tm)| { + let mut new_name = "crates/".to_string(); + new_name.push_str(pref_other.as_ref()); + new_name.push_str("::"); + new_name.push_str(&name); + (new_name, md, tm) + })) + .collect(); + } + + /// Evaluates the performance of `self` against `baseline`. The latter is taken + /// as the comparison point, i.e. a positive resulting `PerfReport` means that + /// `self` performed better. + /// + /// # Panics + /// `self` and `baseline` are assumed to have the iterations field on all + /// `TestMdata`s set to `Some(_)` if the `TestMdata` is present itself. + #[must_use] + pub fn compare_perf(self, baseline: Self) -> PerfReport { + let self_categories = self.collapse(); + let mut other_categories = baseline.collapse(); + + let deltas = self_categories + .into_iter() + .filter_map(|(cat, self_data)| { + // Only compare categories where both meow + // runs have data. / + let mut other_data = other_categories.remove(&cat)?; + let mut max = 0.; + let mut min = 0.; + + // Running totals for averaging out tests. + let mut r_total_numerator = 0.; + let mut r_total_denominator = 0; + // Yeah this is O(n^2), but realistically it'll hardly be a bottleneck. + for (name, (s_timings, s_iters, weight)) in self_data { + // Only use the new weights if they conflict. + let Some((o_timings, o_iters, _)) = other_data.remove(&name) else { + continue; + }; + let shift = + (s_timings.iters_per_sec(s_iters) / o_timings.iters_per_sec(o_iters)) - 1.; + if shift > max { + max = shift; + } + if shift < min { + min = shift; + } + r_total_numerator += shift * f64::from(weight); + r_total_denominator += u32::from(weight); + } + let mean = r_total_numerator / f64::from(r_total_denominator); + // TODO: also aggregate standard deviation? that's harder to keep + // meaningful, though, since we dk which tests are correlated + Some((cat, PerfDelta { max, mean, min })) + }) + .collect(); + + PerfReport { deltas } + } + + /// Collapses the `PerfReport` into a `HashMap` of `Importance` <-> tests + /// each represented as a map of `name, (Timings, iterations, weight)`. + fn collapse(self) -> HashMap> { + let mut categories = HashMap::>::default(); + for entry in self.tests { + if let Some(mdata) = entry.1 + && let Ok(timings) = entry.2 + { + if let Some(handle) = categories.get_mut(&mdata.importance) { + handle.insert(entry.0, (timings, mdata.iterations.unwrap(), mdata.weight)); + } else { + let mut new = HashMap::default(); + new.insert(entry.0, (timings, mdata.iterations.unwrap(), mdata.weight)); + categories.insert(mdata.importance, new); + } + } + } + + categories + } +} + +impl std::fmt::Display for Output { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Don't print the header for an empty run. + if self.tests.is_empty() { + return Ok(()); + } + + // We want to print important tests at the top, then alphabetical. + let mut sorted = self.clone(); + sorted.sort(); + // Markdown header for making a nice little table :> + writeln!( + f, + "| Command | Iter/sec | Mean [ms] | SD [ms] | Iterations | Importance (weight) |", + )?; + writeln!(f, "|:---|---:|---:|---:|---:|---:|")?; + for (name, metadata, timings) in &sorted.tests { + match metadata { + Some(metadata) => match timings { + // Happy path. + Ok(timings) => { + // If the test succeeded, then metadata.iterations is Some(_). + writeln!( + f, + "| {} | {:.2} | {} | {:.2} | {} | {} ({}) |", + name, + timings.iters_per_sec(metadata.iterations.unwrap()), + { + // Very small mean runtimes will give inaccurate + // results. Should probably also penalise weight. + let mean = timings.mean.as_secs_f64() * 1000.; + if mean < consts::NOISE_CUTOFF.as_secs_f64() * 1000. / 8. { + format!("{mean:.2} (unreliable)") + } else { + format!("{mean:.2}") + } + }, + timings.stddev.as_secs_f64() * 1000., + metadata.iterations.unwrap(), + metadata.importance, + metadata.weight, + )?; + } + // We have (some) metadata, but the test errored. + Err(err) => writeln!( + f, + "| ({}) {} | N/A | N/A | N/A | {} | {} ({}) |", + err, + name, + metadata + .iterations + .map_or_else(|| "N/A".to_owned(), |i| format!("{i}")), + metadata.importance, + metadata.weight + )?, + }, + // No metadata, couldn't even parse the test output. + None => writeln!( + f, + "| ({}) {} | N/A | N/A | N/A | N/A | N/A |", + timings.as_ref().unwrap_err(), + name + )?, + } + } + writeln!(f)?; + Ok(()) + } +} + +/// The difference in performance between two runs within a given importance +/// category. +struct PerfDelta { + /// The biggest improvement / least bad regression. + max: f64, + /// The weighted average change in test times. + mean: f64, + /// The worst regression / smallest improvement. + min: f64, +} + +/// Shim type for reporting all performance deltas across importance categories. +pub struct PerfReport { + /// Inner (group, diff) pairing. + deltas: HashMap, +} + +impl std::fmt::Display for PerfReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.deltas.is_empty() { + return write!(f, "(no matching tests)"); + } + let sorted = self.deltas.iter().collect::>(); + writeln!(f, "| Category | Max | Mean | Min |")?; + // We don't want to print too many newlines at the end, so handle newlines + // a little jankily like this. + write!(f, "|:---|---:|---:|---:|")?; + for (cat, delta) in sorted.into_iter().rev() { + write!( + f, + "\n| {cat} | {:.3} | {:.3} | {:.3} |", + delta.max, delta.mean, delta.min + )?; + } + Ok(()) + } +} diff --git a/tooling/perf/src/main.rs b/tooling/perf/src/main.rs index a119811aba76afccc16dbef48e4dbee576b46fdc..2610adc66f88dfa675df975219f5b2937011e81b 100644 --- a/tooling/perf/src/main.rs +++ b/tooling/perf/src/main.rs @@ -1,18 +1,15 @@ -#![warn(clippy::all, clippy::pedantic, clippy::undocumented_unsafe_blocks)] -#![cfg_attr(release, deny(warnings))] - //! Perf profiler for Zed tests. Outputs timings of tests marked with the `#[perf]` //! attribute to stdout in Markdown. See the documentation of `util_macros::perf` //! for usage details on the actual attribute. //! //! # Setup //! Make sure `hyperfine` is installed and in the shell path, then run -//! `cargo build --bin perf --workspace --release` to build the profiler. +//! `cargo build -p perf --release` to build the profiler. //! //! # Usage -//! Calling this tool rebuilds everything with some cfg flags set for the perf -//! proc macro *and* enables optimisations (`release-fast` profile), so expect it -//! to take a little while. +//! Calling this tool rebuilds the targeted crate(s) with some cfg flags set for the +//! perf proc macro *and* enables optimisations (`release-fast` profile), so expect +//! it to take a little while. //! //! To test an individual crate, run: //! ```sh @@ -24,37 +21,221 @@ //! cargo perf-test --workspace //! ``` //! +//! Some command-line parameters are also recognised by this profiler. To filter +//! out all tests below a certain importance (e.g. `important`), run: +//! ```sh +//! cargo perf-test $WHATEVER -- --important +//! ``` +//! +//! Similarly, to skip outputting progress to the command line, pass `-- --quiet`. +//! These flags can be combined. +//! +//! ## Comparing runs +//! Passing `--json=ident` will save per-crate run files in `.perf-runs`, e.g. +//! `cargo perf-test -p gpui -- --json=blah` will result in `.perf-runs/blah.gpui.json` +//! being created (unless no tests were run). These results can be automatically +//! compared. To do so, run `cargo perf-compare new-ident old-ident`. +//! +//! NB: All files matching `.perf-runs/ident.*.json` will be considered when +//! doing this comparison, so ensure there aren't leftover files in your `.perf-runs` +//! directory that might match that! +//! //! # Notes //! This should probably not be called manually unless you're working on the profiler //! itself; use the `cargo perf-test` alias (after building this crate) instead. +#[allow(clippy::wildcard_imports, reason = "Our crate")] +use perf::*; + use std::{ + fs::OpenOptions, + io::Write, + path::{Path, PathBuf}, process::{Command, Stdio}, time::{Duration, Instant}, }; /// How many iterations to attempt the first time a test is run. -const DEFAULT_ITER_COUNT: usize = 12; +const DEFAULT_ITER_COUNT: usize = 3; /// Multiplier for the iteration count when a test doesn't pass the noise cutoff. const ITER_COUNT_MUL: usize = 4; -/// How long a test must have run to be assumed to be reliable-ish. -const NOISE_CUTOFF: Duration = Duration::from_millis(250); -// If any of the below constants are changed, make sure to also update the perf -// proc macro to match! +/// Report a failure into the output and skip an iteration. +macro_rules! fail { + ($output:ident, $name:expr, $kind:expr) => {{ + $output.failure($name, None, None, $kind); + continue; + }}; + ($output:ident, $name:expr, $mdata:expr, $kind:expr) => {{ + $output.failure($name, Some($mdata), None, $kind); + continue; + }}; + ($output:ident, $name:expr, $mdata:expr, $count:expr, $kind:expr) => {{ + $output.failure($name, Some($mdata), Some($count), $kind); + continue; + }}; +} -/// The suffix on tests marked with `#[perf]`. -const SUF_NORMAL: &str = "__ZED_PERF"; -/// The suffix on tests marked with `#[perf(iterations = n)]`. -const SUF_FIXED: &str = "__ZED_PERF_FIXEDITER"; -/// The env var in which we pass the iteration count to our tests. -const ITER_ENV_VAR: &str = "ZED_PERF_ITER"; +/// How does this perf run return its output? +enum OutputKind<'a> { + /// Print markdown to the terminal. + Markdown, + /// Save JSON to a file. + Json(&'a Path), +} -#[allow(clippy::too_many_lines)] +/// Runs a given metadata-returning function from a test handler, parsing its +/// output into a `TestMdata`. +fn parse_mdata(test_bin: &str, mdata_fn: &str) -> Result { + let mut cmd = Command::new(test_bin); + cmd.args([mdata_fn, "--exact", "--nocapture"]); + let out = cmd + .output() + .expect("FATAL: Could not run test binary {test_bin}"); + assert!(out.status.success()); + let stdout = String::from_utf8_lossy(&out.stdout); + let mut version = None; + let mut iterations = None; + let mut importance = Importance::default(); + let mut weight = consts::WEIGHT_DEFAULT; + for line in stdout + .lines() + .filter_map(|l| l.strip_prefix(consts::MDATA_LINE_PREF)) + { + let mut items = line.split_whitespace(); + // For v0, we know the ident always comes first, then one field. + match items.next().unwrap() { + consts::VERSION_LINE_NAME => { + let v = items.next().unwrap().parse::().unwrap(); + if v > consts::MDATA_VER { + return Err(FailKind::VersionMismatch); + } + version = Some(v); + } + consts::ITER_COUNT_LINE_NAME => { + iterations = Some(items.next().unwrap().parse::().unwrap()); + } + consts::IMPORTANCE_LINE_NAME => { + importance = match items.next().unwrap() { + "critical" => Importance::Critical, + "important" => Importance::Important, + "average" => Importance::Average, + "iffy" => Importance::Iffy, + "fluff" => Importance::Fluff, + _ => unreachable!(), + }; + } + consts::WEIGHT_LINE_NAME => { + weight = items.next().unwrap().parse::().unwrap(); + } + _ => unreachable!(), + } + } + + Ok(TestMdata { + version: version.unwrap(), + // Iterations may be determined by us and thus left unspecified. + iterations, + // In principle this should always be set, but just for the sake of + // stability allow the potentially-breaking change of not reporting the + // importance without erroring. Maybe we want to change this. + importance, + // Same with weight. + weight, + }) +} + +/// Compares the perf results of two profiles as per the arguments passed in. +fn compare_profiles(args: &[String]) { + let ident_new = args.first().expect("FATAL: missing identifier for new run"); + let ident_old = args.get(1).expect("FATAL: missing identifier for old run"); + // TODO: move this to a constant also tbh + let wspace_dir = std::env::var("CARGO_WORKSPACE_DIR").unwrap(); + let runs_dir = PathBuf::from(&wspace_dir).join(consts::RUNS_DIR); + + // Use the blank outputs initially, so we can merge into these with prefixes. + let mut outputs_new = Output::blank(); + let mut outputs_old = Output::blank(); + + for e in runs_dir.read_dir().unwrap() { + let Ok(entry) = e else { + continue; + }; + let Ok(metadata) = entry.metadata() else { + continue; + }; + if metadata.is_file() { + let Ok(name) = entry.file_name().into_string() else { + continue; + }; + + // A little helper to avoid code duplication. Reads the `output` from + // a json file, then merges it into what we have so far. + let read_into = |output: &mut Output| { + let mut elems = name.split('.').skip(1); + let prefix = elems.next().unwrap(); + assert_eq!("json", elems.next().unwrap()); + assert!(elems.next().is_none()); + let handle = OpenOptions::new().read(true).open(entry.path()).unwrap(); + let o_other: Output = serde_json::from_reader(handle).unwrap(); + output.merge(o_other, prefix); + }; + + if name.starts_with(ident_old) { + read_into(&mut outputs_old); + } else if name.starts_with(ident_new) { + read_into(&mut outputs_new); + } + } + } + + let res = outputs_new.compare_perf(outputs_old); + println!("{res}"); +} + +#[expect(clippy::too_many_lines, reason = "This will be split up soon!")] fn main() { + let args = std::env::args().collect::>(); // We get passed the test we need to run as the 1st argument after our own name. - let test_bin = std::env::args().nth(1).unwrap(); - let mut cmd = Command::new(&test_bin); + let test_bin = args + .get(1) + .expect("FATAL: No test binary or command; this shouldn't be manually invoked!"); + + // We're being asked to compare two results, not run the profiler. + if test_bin == "compare" { + compare_profiles(&args[2..]); + return; + } + + // Whether to skip printing some information to stderr. + let mut quiet = false; + // Minimum test importance we care about this run. + let mut thresh = Importance::Iffy; + // Where to print the output of this run. + let mut out_kind = OutputKind::Markdown; + + for arg in args.iter().skip(2) { + match arg.as_str() { + "--critical" => thresh = Importance::Critical, + "--important" => thresh = Importance::Important, + "--average" => thresh = Importance::Average, + "--iffy" => thresh = Importance::Iffy, + "--fluff" => thresh = Importance::Fluff, + "--quiet" => quiet = true, + s if s.starts_with("--json") => { + out_kind = OutputKind::Json(Path::new( + s.strip_prefix("--json=") + .expect("FATAL: Invalid json parameter; pass --json=filename"), + )); + } + _ => (), + } + } + if !quiet { + eprintln!("Starting perf check"); + } + + let mut cmd = Command::new(test_bin); // --format=json is nightly-only :( cmd.args(["--list", "--format=terse"]); let out = cmd @@ -64,6 +245,9 @@ fn main() { out.status.success(), "FATAL: Cannot do perf check - test binary {test_bin} returned an error" ); + if !quiet { + eprintln!("Test binary ran successfully; starting profile..."); + } // Parse the test harness output to look for tests we care about. let stdout = String::from_utf8_lossy(&out.stdout); let mut test_list: Vec<_> = stdout @@ -79,37 +263,62 @@ fn main() { } }) // Exclude tests that aren't marked for perf triage based on suffix. - .filter(|t_name| t_name.ends_with(SUF_NORMAL) || t_name.ends_with(SUF_FIXED)) + .filter(|t_name| { + t_name.ends_with(consts::SUF_NORMAL) || t_name.ends_with(consts::SUF_MDATA) + }) .collect(); // Pulling itertools just for .dedup() would be quite a big dependency that's - // not used elsewhere, so do this on the vec instead. + // not used elsewhere, so do this on a vec instead. test_list.sort_unstable(); test_list.dedup(); - if !test_list.is_empty() { - // Print the markdown header which matches hyperfine's result. - // TODO: Support exporting JSON also. - println!( - "| Command | Mean [ms] | Min [ms] | Max [ms] | Iterations | Iter/sec |\n|:---|---:|---:|---:|---:|---:|" - ); - } + let len = test_list.len(); + + // Tests should come in pairs with their mdata fn! + assert!( + len.is_multiple_of(2), + "Malformed tests in test binary {test_bin}" + ); + + let mut output = Output::default(); // Spawn and profile an instance of each perf-sensitive test, via hyperfine. - for t_name in test_list { - // Pretty-print the stripped name for the test. - let t_name_normal = t_name.replace(SUF_FIXED, "").replace(SUF_NORMAL, ""); + // Each test is a pair of (test, metadata-returning-fn), so grab both. We also + // know the list is sorted. + for (idx, t_pair) in test_list.chunks_exact(2).enumerate() { + if !quiet { + eprint!("\rProfiling test {}/{}", idx + 1, len / 2); + } + // Be resilient against changes to these constants. + let (t_name, t_mdata) = if consts::SUF_NORMAL < consts::SUF_MDATA { + (t_pair[0], t_pair[1]) + } else { + (t_pair[1], t_pair[0]) + }; + // Pretty-printable stripped name for the test. + let t_name_pretty = t_name.replace(consts::SUF_NORMAL, ""); + + // Get the metadata this test reports for us. + let t_mdata = match parse_mdata(test_bin, t_mdata) { + Ok(mdata) => mdata, + Err(err) => fail!(output, t_name_pretty, err), + }; + + if t_mdata.importance < thresh { + fail!(output, t_name_pretty, t_mdata, FailKind::Skipped); + } + // Time test execution to see how many iterations we need to do in order // to account for random noise. This is skipped for tests with fixed // iteration counts. - let final_iter_count = if t_name.ends_with(SUF_FIXED) { - None - } else { + let mut errored = false; + let final_iter_count = t_mdata.iterations.unwrap_or_else(|| { let mut iter_count = DEFAULT_ITER_COUNT; loop { - let mut cmd = Command::new(&test_bin); + let mut cmd = Command::new(test_bin); cmd.args([t_name, "--exact"]); - cmd.env(ITER_ENV_VAR, format!("{iter_count}")); + cmd.env(consts::ITER_ENV_VAR, format!("{iter_count}")); // Don't let the child muck up our stdin/out/err. cmd.stdin(Stdio::null()); cmd.stdout(Stdio::null()); @@ -119,28 +328,32 @@ fn main() { let out = cmd.spawn().unwrap().wait(); let post = Instant::now(); if !out.unwrap().success() { - println!( - "| {t_name_normal} (ERRORED IN TRIAGE) | N/A | N/A | N/A | {iter_count} | N/A |" - ); - return; + errored = true; + break iter_count; } - if post - pre > NOISE_CUTOFF { - break Some(iter_count); + if post - pre > consts::NOISE_CUTOFF { + break iter_count; } else if let Some(c) = iter_count.checked_mul(ITER_COUNT_MUL) { iter_count = c; } else { // This should almost never happen, but maybe..? eprintln!( - "WARNING: Running nearly usize::MAX iterations of test {t_name_normal}" + "WARNING: Running nearly usize::MAX iterations of test {t_name_pretty}" ); - break Some(iter_count); + break iter_count; } } - }; + }); + + // Don't profile failing tests. + if errored { + fail!(output, t_name_pretty, t_mdata, FailKind::Triage); + } // Now profile! let mut perf_cmd = Command::new("hyperfine"); // Warm up the cache and print markdown output to stdout. + // TODO: json perf_cmd.args([ "--style", "none", @@ -150,42 +363,89 @@ fn main() { "-", &format!("{test_bin} {t_name}"), ]); - if let Some(final_iter_count) = final_iter_count { - perf_cmd.env(ITER_ENV_VAR, format!("{final_iter_count}")); - } + perf_cmd.env(consts::ITER_ENV_VAR, format!("{final_iter_count}")); let p_out = perf_cmd.output().unwrap(); - let fin_iter = match final_iter_count { - Some(i) => &format!("{i}"), - None => "(preset)", - }; if p_out.status.success() { - let output = String::from_utf8_lossy(&p_out.stdout); - // Strip the name of the test binary from the table (and the space after it!) - // + our extraneous test bits + the "Relative" column (which is always at the end and "1.00"). - let output = output - .replace(&format!("{test_bin} "), "") - .replace(SUF_FIXED, "") - .replace(SUF_NORMAL, "") - .replace(" 1.00 |", ""); + let cmd_output = String::from_utf8_lossy(&p_out.stdout); // Can't use .last() since we have a trailing newline. Sigh. - let fin = output.lines().nth(3).unwrap(); + let results_line = cmd_output.lines().nth(3).unwrap(); + // Grab the values out of the pretty-print. + // TODO: Parse json instead. + let mut res_iter = results_line.split_whitespace(); + // Durations are given in milliseconds, so account for that. + let mean = + Duration::from_secs_f64(res_iter.nth(4).unwrap().parse::().unwrap() / 1000.); + let stddev = + Duration::from_secs_f64(res_iter.nth(1).unwrap().parse::().unwrap() / 1000.); - // Calculate how many iterations this does per second, for easy comparison. - let ms = fin - .split_whitespace() - .nth(3) + output.success( + t_name_pretty, + t_mdata, + final_iter_count, + Timings { mean, stddev }, + ); + } else { + fail!( + output, + t_name_pretty, + t_mdata, + final_iter_count, + FailKind::Profile + ); + } + } + if !quiet { + if output.is_empty() { + eprintln!("Nothing to do."); + } else { + // If stdout and stderr are on the same terminal, move us after the + // output from above. + eprintln!(); + } + } + + // No need making an empty json file on every empty test bin. + if output.is_empty() { + return; + } + + match out_kind { + OutputKind::Markdown => print!("{output}"), + OutputKind::Json(user_path) => { + let wspace_dir = std::env::var("CARGO_WORKSPACE_DIR").unwrap(); + let runs_dir = PathBuf::from(&wspace_dir).join(consts::RUNS_DIR); + std::fs::create_dir_all(&runs_dir).unwrap(); + assert!( + !user_path.to_string_lossy().is_empty(), + "FATAL: Empty filename specified!" + ); + // Get the test binary's crate's name; a path like + // target/release-fast/deps/gpui-061ff76c9b7af5d7 + // would be reduced to just "gpui". + let test_bin_stripped = Path::new(test_bin) + .file_name() + .unwrap() + .to_str() .unwrap() - .parse::() + .rsplit_once('-') + .unwrap() + .0; + let mut file_path = runs_dir.join(user_path); + file_path + .as_mut_os_string() + .push(format!(".{test_bin_stripped}.json")); + let mut out_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&file_path) .unwrap(); - let mul_fac = 1000.0 / ms; - let iter_sec = match final_iter_count { - #[allow(clippy::cast_precision_loss)] - Some(c) => &format!("{:.1}", mul_fac * c as f64), - None => "(unknown)", - }; - println!("{fin} {fin_iter} | {iter_sec} |"); - } else { - println!("{t_name_normal} (ERRORED) | N/A | N/A | N/A | {fin_iter} | N/A |"); + out_file + .write_all(&serde_json::to_vec(&output).unwrap()) + .unwrap(); + if !quiet { + eprintln!("JSON output written to {}", file_path.display()); + } } } } From 891a06c2940b7aa441aac047a98d0dce86fb39a0 Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Sun, 21 Sep 2025 20:18:17 -0400 Subject: [PATCH 168/721] docs: Small grammar fix to use a possessive pronoun (#38610) > Your extension can define it's own debug locators > Your extension can define it is own debug locators The sentence above does not make sense after expanding "it's". We should instead be using the possessive "its" in this scenario. Release Notes: - N/A Signed-off-by: Remy Suen --- docs/src/extensions/debugger-extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/extensions/debugger-extensions.md b/docs/src/extensions/debugger-extensions.md index 4412bf8b9aa576e736b5b6dc25c5f4bc48100b18..fa33c25732631ca98910f0b19275ab32475955ff 100644 --- a/docs/src/extensions/debugger-extensions.md +++ b/docs/src/extensions/debugger-extensions.md @@ -65,7 +65,7 @@ Put another way, it is supposed to answer the question: "Given a program, a list Zed offers an automatic way to create debug scenarios with _debug locators_. A locator locates the debug target and figures out how to spawn a debug session for it. Thanks to locators, we can automatically convert existing user tasks (e.g. `cargo run`) and convert them into debug scenarios (e.g. `cargo build` followed by spawning a debugger with `target/debug/my_program` as the program to debug). -> Your extension can define it's own debug locators even if it does not expose a debug adapter. We strongly recommend doing so when your extension already exposes language tasks, as it allows users to spawn a debug session without having to manually configure the debug adapter. +> Your extension can define its own debug locators even if it does not expose a debug adapter. We strongly recommend doing so when your extension already exposes language tasks, as it allows users to spawn a debug session without having to manually configure the debug adapter. Locators can (but don't have to) be agnostic to the debug adapter they are used with. They are simply responsible for locating the debug target and figuring out how to spawn a debug session for it. This allows for a more flexible and extensible debugging experience. From 271771c742001157c0568c2cf40f4b184120fbe4 Mon Sep 17 00:00:00 2001 From: Miao Date: Mon, 22 Sep 2025 17:06:54 +0800 Subject: [PATCH 169/721] =?UTF-8?q?editor:=20Prevent=20non=E2=80=91boundar?= =?UTF-8?q?y=20highlight=20indices=20in=20UTF=E2=80=918=20(#38510)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #38359 Release Notes: - Use byte offsets for highlights; fix UTF‑8 crash --- crates/debugger_ui/src/new_process_modal.rs | 1 - .../src/highlighted_match_with_paths.rs | 49 ++++++++++++++++--- crates/recent_projects/src/recent_projects.rs | 25 +++++----- crates/tasks_ui/src/modal.rs | 1 - 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index f1fa4738e30e5ed24e7815b61571b03e5a16252e..a25c02c1b5f72f1e85f532fcee244f0165a8a48e 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -1514,7 +1514,6 @@ impl PickerDelegate for DebugDelegate { let highlighted_location = HighlightedMatch { text: hit.string.clone(), highlight_positions: hit.positions.clone(), - char_count: hit.string.chars().count(), color: Color::Default, }; diff --git a/crates/picker/src/highlighted_match_with_paths.rs b/crates/picker/src/highlighted_match_with_paths.rs index 255e0150e8d6d9684b4f5b1315d4975f037ace48..6e91b997da2dab2ac61befd2f596e6f3a4207c85 100644 --- a/crates/picker/src/highlighted_match_with_paths.rs +++ b/crates/picker/src/highlighted_match_with_paths.rs @@ -10,36 +10,36 @@ pub struct HighlightedMatchWithPaths { pub struct HighlightedMatch { pub text: String, pub highlight_positions: Vec, - pub char_count: usize, pub color: Color, } impl HighlightedMatch { pub fn join(components: impl Iterator, separator: &str) -> Self { - let mut char_count = 0; - let separator_char_count = separator.chars().count(); + // Track a running byte offset and insert separators between parts. + let mut first = true; + let mut byte_offset = 0; let mut text = String::new(); let mut highlight_positions = Vec::new(); for component in components { - if char_count != 0 { + if !first { text.push_str(separator); - char_count += separator_char_count; + byte_offset += separator.len(); } + first = false; highlight_positions.extend( component .highlight_positions .iter() - .map(|position| position + char_count), + .map(|position| position + byte_offset), ); text.push_str(&component.text); - char_count += component.text.chars().count(); + byte_offset += component.text.len(); } Self { text, highlight_positions, - char_count, color: Color::Default, } } @@ -73,3 +73,36 @@ impl RenderOnce for HighlightedMatchWithPaths { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn join_offsets_positions_by_bytes_not_chars() { + // "αβγ" is 3 Unicode scalar values, 6 bytes in UTF-8. + let left_text = "αβγ".to_string(); + let right_text = "label".to_string(); + let left = HighlightedMatch { + text: left_text, + highlight_positions: vec![], + color: Color::Default, + }; + let right = HighlightedMatch { + text: right_text, + highlight_positions: vec![0, 1], + color: Color::Default, + }; + let joined = HighlightedMatch::join([left, right].into_iter(), ""); + + assert!( + joined + .highlight_positions + .iter() + .all(|&p| joined.text.is_char_boundary(p)), + "join produced non-boundary positions {:?} for text {:?}", + joined.highlight_positions, + joined.text + ); + } +} diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 2b011638218dd58b758f3af2e46836614e1c6780..ad7270d98c2597d77a71945c4aed97374cc6d8da 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -463,8 +463,7 @@ impl PickerDelegate for RecentProjectsDelegate { .map(|path| { let highlighted_text = highlights_for_path(path.as_ref(), &hit.positions, path_start_offset); - - path_start_offset += highlighted_text.1.char_count; + path_start_offset += highlighted_text.1.text.len(); highlighted_text }) .unzip(); @@ -590,34 +589,33 @@ fn highlights_for_path( path_start_offset: usize, ) -> (Option, HighlightedMatch) { let path_string = path.to_string_lossy(); - let path_char_count = path_string.chars().count(); + let path_text = path_string.to_string(); + let path_byte_len = path_text.len(); // Get the subset of match highlight positions that line up with the given path. // Also adjusts them to start at the path start let path_positions = match_positions .iter() .copied() .skip_while(|position| *position < path_start_offset) - .take_while(|position| *position < path_start_offset + path_char_count) + .take_while(|position| *position < path_start_offset + path_byte_len) .map(|position| position - path_start_offset) .collect::>(); // Again subset the highlight positions to just those that line up with the file_name // again adjusted to the start of the file_name let file_name_text_and_positions = path.file_name().map(|file_name| { - let text = file_name.to_string_lossy(); - let char_count = text.chars().count(); - let file_name_start = path_char_count - char_count; + let file_name_text = file_name.to_string_lossy().to_string(); + let file_name_start_byte = path_byte_len - file_name_text.len(); let highlight_positions = path_positions .iter() .copied() - .skip_while(|position| *position < file_name_start) - .take_while(|position| *position < file_name_start + char_count) - .map(|position| position - file_name_start) + .skip_while(|position| *position < file_name_start_byte) + .take_while(|position| *position < file_name_start_byte + file_name_text.len()) + .map(|position| position - file_name_start_byte) .collect::>(); HighlightedMatch { - text: text.to_string(), + text: file_name_text, highlight_positions, - char_count, color: Color::Default, } }); @@ -625,9 +623,8 @@ fn highlights_for_path( ( file_name_text_and_positions, HighlightedMatch { - text: path_string.to_string(), + text: path_text, highlight_positions: path_positions, - char_count: path_char_count, color: Color::Default, }, ) diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 3522e9522a6d32d729e7f0dca6731b2052f63f94..3b669e5a4d88405d32c77d88abf336c4c65f30c0 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -482,7 +482,6 @@ impl PickerDelegate for TasksModalDelegate { let highlighted_location = HighlightedMatch { text: hit.string.clone(), highlight_positions: hit.positions.clone(), - char_count: hit.string.chars().count(), color: Color::Default, }; let icon = match source_kind { From 79620454d0ba0fb81306e80dccfe77a7652d94fa Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 22 Sep 2025 07:14:04 -0300 Subject: [PATCH 170/721] Docs: change format_on_save value from false to "off" (#38615) Found this outdated piece of information in the docs while trying to disable it myself, this PR simply changes `false` to `"off"`. Release Notes: - N/A --- docs/src/languages/python.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/languages/python.md b/docs/src/languages/python.md index faca1185768d09b5dcb6e485c146b0e1973cf870..98eca1fcc9d43747aaf45085db5ed831f8d0b25f 100644 --- a/docs/src/languages/python.md +++ b/docs/src/languages/python.md @@ -198,7 +198,7 @@ You can disable format-on-save for Python files in your `settings.json`: { "languages": { "Python": { - "format_on_save": false + "format_on_save": "off" } } } From a2c71d3d207c963f54a622f77a745d943075c02a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 22 Sep 2025 13:20:46 +0200 Subject: [PATCH 171/721] text: Assert text anchor offset validity on construction (#38441) Attempt to aid debugging some utf8 indexing issues Release Notes: - N/A Co-authored-by: Mikayla Maki --- .../editor/src/highlight_matching_bracket.rs | 4 +- crates/language/src/buffer.rs | 6 +- crates/multi_buffer/src/multi_buffer.rs | 3 +- crates/rope/src/rope.rs | 167 ++++++++++++++++++ crates/text/src/text.rs | 21 +++ 5 files changed, 195 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index aa4e616924ad6bd47627bfd95e9a5c58587afc25..aa1647cac0ba3e9d7644871faeac783e44501c57 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -29,7 +29,9 @@ pub fn refresh_matching_bracket_highlights( if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow) && head < snapshot.buffer_snapshot.len() { - tail += 1; + if let Some(tail_ch) = snapshot.buffer_snapshot.chars_at(tail).next() { + tail += tail_ch.len_utf8(); + } } if let Some((opening_range, closing_range)) = snapshot diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d5d83da47bc18a4fd15f59df2ddb2238ceb768d4..1f5c35f8df9288182b294b8e52a9cd7bdb531124 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -4121,8 +4121,7 @@ impl BufferSnapshot { range: Range, ) -> impl Iterator + '_ { // Find bracket pairs that *inclusively* contain the given range. - let range = range.start.to_offset(self).saturating_sub(1) - ..self.len().min(range.end.to_offset(self) + 1); + let range = range.start.to_previous_offset(self)..range.end.to_next_offset(self); self.all_bracket_ranges(range) .filter(|pair| !pair.newline_only) } @@ -4131,8 +4130,7 @@ impl BufferSnapshot { &self, range: Range, ) -> impl Iterator, DebuggerTextObject)> + '_ { - let range = range.start.to_offset(self).saturating_sub(1) - ..self.len().min(range.end.to_offset(self) + 1); + let range = range.start.to_previous_offset(self)..range.end.to_next_offset(self); let mut matches = self.syntax.matches_with_options( range.clone(), diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index c79bc03489be89ad00d10392c520fe13e7748a60..8d72e919a4802c1875c956fc3e6d72fc0e9a2ade 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -5259,7 +5259,8 @@ impl MultiBufferSnapshot { } else { Anchor::max() }; - // TODO this is a hack, remove it + + // TODO this is a hack, because all APIs should be able to handle ExcerptId::min and max. if let Some((excerpt_id, _, _)) = self.as_singleton() { anchor.excerpt_id = *excerpt_id; } diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 9c98985989c2ac0fdcd5a39342dd9911d64dd01a..d802f972168348b15f003e1fd2fa567c1f9d75bb 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -30,6 +30,76 @@ impl Rope { Self::default() } + pub fn is_char_boundary(&self, offset: usize) -> bool { + if self.chunks.is_empty() { + return offset == 0; + } + let mut cursor = self.chunks.cursor::(&()); + cursor.seek(&offset, Bias::Left); + let chunk_offset = offset - cursor.start(); + cursor + .item() + .map(|chunk| chunk.text.is_char_boundary(chunk_offset)) + .unwrap_or(false) + } + + pub fn floor_char_boundary(&self, index: usize) -> usize { + if index >= self.len() { + self.len() + } else { + #[inline] + pub(crate) const fn is_utf8_char_boundary(u8: u8) -> bool { + // This is bit magic equivalent to: b < 128 || b >= 192 + (u8 as i8) >= -0x40 + } + + let mut cursor = self.chunks.cursor::(&()); + cursor.seek(&index, Bias::Left); + let chunk_offset = index - cursor.start(); + let lower_idx = cursor.item().map(|chunk| { + let lower_bound = chunk_offset.saturating_sub(3); + chunk + .text + .as_bytes() + .get(lower_bound..=chunk_offset) + .map(|it| { + let new_idx = it + .iter() + .rposition(|&b| is_utf8_char_boundary(b)) + .unwrap_or(0); + lower_bound + new_idx + }) + .unwrap_or(chunk.text.len()) + }); + lower_idx.map_or_else(|| self.len(), |idx| cursor.start() + idx) + } + } + + pub fn ceil_char_boundary(&self, index: usize) -> usize { + if index > self.len() { + self.len() + } else { + #[inline] + pub(crate) const fn is_utf8_char_boundary(u8: u8) -> bool { + // This is bit magic equivalent to: b < 128 || b >= 192 + (u8 as i8) >= -0x40 + } + + let mut cursor = self.chunks.cursor::(&()); + cursor.seek(&index, Bias::Left); + let chunk_offset = index - cursor.start(); + let upper_idx = cursor.item().map(|chunk| { + let upper_bound = Ord::min(chunk_offset + 4, chunk.text.len()); + chunk.text.as_bytes()[chunk_offset..upper_bound] + .iter() + .position(|&b| is_utf8_char_boundary(b)) + .map_or(upper_bound, |pos| pos + chunk_offset) + }); + + upper_idx.map_or_else(|| self.len(), |idx| cursor.start() + idx) + } + } + pub fn append(&mut self, rope: Rope) { if let Some(chunk) = rope.chunks.first() && (self @@ -2069,6 +2139,103 @@ mod tests { assert!(!rope.reversed_chunks_in_range(0..0).equals_str("foo")); } + #[test] + fn test_is_char_boundary() { + let fixture = "地"; + let rope = Rope::from("地"); + for b in 0..=fixture.len() { + assert_eq!(rope.is_char_boundary(b), fixture.is_char_boundary(b)); + } + let fixture = ""; + let rope = Rope::from(""); + for b in 0..=fixture.len() { + assert_eq!(rope.is_char_boundary(b), fixture.is_char_boundary(b)); + } + let fixture = "🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️‍⚧️🏁🏳️‍🌈🏴‍☠️⛳️📬📭🏴🏳️🚩"; + let rope = Rope::from("🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️‍⚧️🏁🏳️‍🌈🏴‍☠️⛳️📬📭🏴🏳️🚩"); + for b in 0..=fixture.len() { + assert_eq!(rope.is_char_boundary(b), fixture.is_char_boundary(b)); + } + } + + #[test] + fn test_floor_char_boundary() { + // polyfill of str::floor_char_boundary + fn floor_char_boundary(str: &str, index: usize) -> usize { + if index >= str.len() { + str.len() + } else { + let lower_bound = index.saturating_sub(3); + let new_index = str.as_bytes()[lower_bound..=index] + .iter() + .rposition(|b| (*b as i8) >= -0x40); + + lower_bound + new_index.unwrap() + } + } + + let fixture = "地"; + let rope = Rope::from("地"); + for b in 0..=fixture.len() { + assert_eq!( + rope.floor_char_boundary(b), + floor_char_boundary(&fixture, b) + ); + } + + let fixture = ""; + let rope = Rope::from(""); + for b in 0..=fixture.len() { + assert_eq!( + rope.floor_char_boundary(b), + floor_char_boundary(&fixture, b) + ); + } + + let fixture = "🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️‍⚧️🏁🏳️‍🌈🏴‍☠️⛳️📬📭🏴🏳️🚩"; + let rope = Rope::from("🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️‍⚧️🏁🏳️‍🌈🏴‍☠️⛳️📬📭🏴🏳️🚩"); + for b in 0..=fixture.len() { + assert_eq!( + rope.floor_char_boundary(b), + floor_char_boundary(&fixture, b) + ); + } + } + + #[test] + fn test_ceil_char_boundary() { + // polyfill of str::ceil_char_boundary + fn ceil_char_boundary(str: &str, index: usize) -> usize { + if index > str.len() { + str.len() + } else { + let upper_bound = Ord::min(index + 4, str.len()); + str.as_bytes()[index..upper_bound] + .iter() + .position(|b| (*b as i8) >= -0x40) + .map_or(upper_bound, |pos| pos + index) + } + } + + let fixture = "地"; + let rope = Rope::from("地"); + for b in 0..=fixture.len() { + assert_eq!(rope.ceil_char_boundary(b), ceil_char_boundary(&fixture, b)); + } + + let fixture = ""; + let rope = Rope::from(""); + for b in 0..=fixture.len() { + assert_eq!(rope.ceil_char_boundary(b), ceil_char_boundary(&fixture, b)); + } + + let fixture = "🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️‍⚧️🏁🏳️‍🌈🏴‍☠️⛳️📬📭🏴🏳️🚩"; + let rope = Rope::from("🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️‍⚧️🏁🏳️‍🌈🏴‍☠️⛳️📬📭🏴🏳️🚩"); + for b in 0..=fixture.len() { + assert_eq!(rope.ceil_char_boundary(b), ceil_char_boundary(&fixture, b)); + } + } + fn clip_offset(text: &str, mut offset: usize, bias: Bias) -> usize { while !text.is_char_boundary(offset) { match bias { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 590c30c8a73c13180e4d09dda1b3a071ef46ad7f..bbf9e2e3812238eebbdf92c63bd3b23819a9dac0 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2400,6 +2400,17 @@ impl BufferSnapshot { } else if bias == Bias::Right && offset == self.len() { Anchor::MAX } else { + if !self.visible_text.is_char_boundary(offset) { + // find the character + let char_start = self.visible_text.floor_char_boundary(offset); + // `char_start` must be less than len and a char boundary + let ch = self.visible_text.chars_at(char_start).next().unwrap(); + let char_range = char_start..char_start + ch.len_utf8(); + panic!( + "byte index {} is not a char boundary; it is inside {:?} (bytes {:?})", + offset, ch, char_range, + ); + } let mut fragment_cursor = self.fragments.cursor::(&None); fragment_cursor.seek(&offset, bias); let fragment = fragment_cursor.item().unwrap(); @@ -3065,6 +3076,16 @@ impl operation_queue::Operation for Operation { pub trait ToOffset { fn to_offset(&self, snapshot: &BufferSnapshot) -> usize; + fn to_next_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot + .visible_text + .ceil_char_boundary(self.to_offset(snapshot) + 1) + } + fn to_previous_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot + .visible_text + .floor_char_boundary(self.to_offset(snapshot).saturating_sub(1)) + } } impl ToOffset for Point { From 50bd8bc2551a287cca389c1177eb1db570e83a0d Mon Sep 17 00:00:00 2001 From: Justin Su Date: Mon, 22 Sep 2025 07:29:46 -0400 Subject: [PATCH 172/721] docs: Add instructions for setting up `fish_indent` for fish (#38414) Release Notes: - N/A --- docs/src/languages/fish.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/src/languages/fish.md b/docs/src/languages/fish.md index 969beee2209b7f066b01fa703a7912260cf8b05e..ad2148d807baeb73241206ab5538ddaffdc789ce 100644 --- a/docs/src/languages/fish.md +++ b/docs/src/languages/fish.md @@ -4,3 +4,28 @@ Fish language support in Zed is provided by the community-maintained [Fish exten Report issues to: [https://github.com/hasit/zed-fish/issues](https://github.com/hasit/zed-fish/issues) - Tree-sitter: [ram02z/tree-sitter-fish](https://github.com/ram02z/tree-sitter-fish) + +### Formatting + +Zed supports auto-formatting fish code using external tools like [`fish_indent`](https://fishshell.com/docs/current/cmds/fish_indent.html), which is included with fish. + +1. Ensure `fish_indent` is available in your path and check the version: + +```sh +which fish_indent +fish_indent --version +``` + +2. Configure Zed to automatically format fish code with `fish_indent`: + +```json + "languages": { + "Fish": { + "formatter": { + "external": { + "command": "fish_indent" + } + } + } + }, +``` From 55dc9ff7ca77009d0c342ae6377b0d1d7413e32c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 22 Sep 2025 13:45:23 +0200 Subject: [PATCH 173/721] text: Implement `Rope::clip_offset` in terms of the new utf8 boundary methods (#38630) Release Notes: - N/A --- .../src/assistant_context.rs | 2 +- crates/language/src/buffer.rs | 4 ++-- crates/project/src/git_store/conflict_set.rs | 4 ++-- crates/rope/src/rope.rs | 24 ++++--------------- crates/text/src/text.rs | 4 +++- 5 files changed, 12 insertions(+), 26 deletions(-) diff --git a/crates/assistant_context/src/assistant_context.rs b/crates/assistant_context/src/assistant_context.rs index 12eda0954a2e1cca9ddc7df9816b8f5a37d0ce10..23aeabbc8929e6a3874c2fbbf74b8f9729860481 100644 --- a/crates/assistant_context/src/assistant_context.rs +++ b/crates/assistant_context/src/assistant_context.rs @@ -2445,7 +2445,7 @@ impl AssistantContext { .message_anchors .get(next_message_ix) .map_or(buffer.len(), |message| { - buffer.clip_offset(message.start.to_offset(buffer) - 1, Bias::Left) + buffer.clip_offset(message.start.to_previous_offset(buffer), Bias::Left) }); Some(self.insert_message_at_offset(offset, role, status, cx)) } else { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 1f5c35f8df9288182b294b8e52a9cd7bdb531124..311ef4d55b947888cd7fbc6706a9bd581f2dd27d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -4198,8 +4198,8 @@ impl BufferSnapshot { range: Range, options: TreeSitterOptions, ) -> impl Iterator, TextObject)> + '_ { - let range = range.start.to_offset(self).saturating_sub(1) - ..self.len().min(range.end.to_offset(self) + 1); + let range = + range.start.to_previous_offset(self)..self.len().min(range.end.to_next_offset(self)); let mut matches = self.syntax diff --git a/crates/project/src/git_store/conflict_set.rs b/crates/project/src/git_store/conflict_set.rs index 2bcfc75b32da3c5a4860cc72f3266bff38f022e3..067af17820e58264006d0227cfb0f3c13069fcf9 100644 --- a/crates/project/src/git_store/conflict_set.rs +++ b/crates/project/src/git_store/conflict_set.rs @@ -344,8 +344,8 @@ mod tests { assert_eq!(conflicts_in_range.len(), 1); // Test with a range that doesn't include any conflicts - let range = buffer.anchor_after(first_conflict_end.to_offset(&buffer) + 1) - ..buffer.anchor_before(second_conflict_start.to_offset(&buffer) - 1); + let range = buffer.anchor_after(first_conflict_end.to_next_offset(&buffer)) + ..buffer.anchor_before(second_conflict_start.to_previous_offset(&buffer)); let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot); assert_eq!(conflicts_in_range.len(), 0); } diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index d802f972168348b15f003e1fd2fa567c1f9d75bb..cb2e103f76fa636571526c71afcbb3358542b083 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -459,26 +459,10 @@ impl Rope { }) } - pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { - let mut cursor = self.chunks.cursor::(&()); - cursor.seek(&offset, Bias::Left); - if let Some(chunk) = cursor.item() { - let mut ix = offset - cursor.start(); - while !chunk.text.is_char_boundary(ix) { - match bias { - Bias::Left => { - ix -= 1; - offset -= 1; - } - Bias::Right => { - ix += 1; - offset += 1; - } - } - } - offset - } else { - self.summary().len + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + match bias { + Bias::Left => self.floor_char_boundary(offset), + Bias::Right => self.ceil_char_boundary(offset), } } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index bbf9e2e3812238eebbdf92c63bd3b23819a9dac0..a38693ed934855acd0e0c6ff726c7835a1aa057e 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2128,7 +2128,7 @@ impl BufferSnapshot { let row_end_offset = if row >= self.max_point().row { self.len() } else { - Point::new(row + 1, 0).to_offset(self) - 1 + Point::new(row + 1, 0).to_previous_offset(self) }; (row_end_offset - row_start_offset) as u32 } @@ -3076,11 +3076,13 @@ impl operation_queue::Operation for Operation { pub trait ToOffset { fn to_offset(&self, snapshot: &BufferSnapshot) -> usize; + /// Turns this point into the next offset in the buffer that comes after this, respecting utf8 boundaries. fn to_next_offset(&self, snapshot: &BufferSnapshot) -> usize { snapshot .visible_text .ceil_char_boundary(self.to_offset(snapshot) + 1) } + /// Turns this point into the previous offset in the buffer that comes before this, respecting utf8 boundaries. fn to_previous_offset(&self, snapshot: &BufferSnapshot) -> usize { snapshot .visible_text From 8bac1bee7a6e6b3c08873e9233b2515c3a6fd386 Mon Sep 17 00:00:00 2001 From: localcc Date: Mon, 22 Sep 2025 13:46:29 +0200 Subject: [PATCH 174/721] Disable subpixel shifting for y axis on Windows (#38440) Release Notes: - N/A --------- Co-authored-by: Jakub Konka --- crates/gpui/src/platform/linux/text_system.rs | 6 +++--- crates/gpui/src/platform/mac/text_system.rs | 4 ++-- crates/gpui/src/platform/windows/direct_write.rs | 11 +++++------ crates/gpui/src/text_system.rs | 8 +++++++- crates/gpui/src/window.rs | 9 +++++---- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/gpui/src/platform/linux/text_system.rs b/crates/gpui/src/platform/linux/text_system.rs index f66a2e71d49f39c0e82770e23aa8eca752970daf..3f045f9406f2ce1f06b55367600547843cdb69e3 100644 --- a/crates/gpui/src/platform/linux/text_system.rs +++ b/crates/gpui/src/platform/linux/text_system.rs @@ -1,6 +1,6 @@ use crate::{ Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, - GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS, + GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size, point, size, }; use anyhow::{Context as _, Ok, Result}; @@ -276,7 +276,7 @@ impl CosmicTextSystemState { let font = &self.loaded_fonts[params.font_id.0].font; let subpixel_shift = params .subpixel_variant - .map(|v| v as f32 / (SUBPIXEL_VARIANTS as f32 * params.scale_factor)); + .map(|v| v as f32 / (SUBPIXEL_VARIANTS_X as f32 * params.scale_factor)); let image = self .swash_cache .get_image( @@ -311,7 +311,7 @@ impl CosmicTextSystemState { let font = &self.loaded_fonts[params.font_id.0].font; let subpixel_shift = params .subpixel_variant - .map(|v| v as f32 / (SUBPIXEL_VARIANTS as f32 * params.scale_factor)); + .map(|v| v as f32 / (SUBPIXEL_VARIANTS_X as f32 * params.scale_factor)); let mut image = self .swash_cache .get_image( diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 7f765fbaac80e27f8db4b9c4f2d00de90e991a9a..67ece1f153fb4ea73a12259d2d55409059aadb40 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -1,7 +1,7 @@ use crate::{ Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, - RenderGlyphParams, Result, SUBPIXEL_VARIANTS, ShapedGlyph, ShapedRun, SharedString, Size, + RenderGlyphParams, Result, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size, point, px, size, swap_rgba_pa_to_bgra, }; use anyhow::anyhow; @@ -395,7 +395,7 @@ impl MacTextSystemState { let subpixel_shift = params .subpixel_variant - .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); + .map(|v| v as f32 / SUBPIXEL_VARIANTS_X as f32); cx.set_allows_font_smoothing(true); cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); cx.set_gray_fill_color(0.0, 1.0); diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index df3161bf079a8eb0cb04908e586f5d344519821e..e187fc4b09176906102a1bf8fe50b410aae3cb2b 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -723,11 +723,10 @@ impl DirectWriteState { dx: 0.0, dy: 0.0, }; - let subpixel_shift = params - .subpixel_variant - .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); - let baseline_origin_x = subpixel_shift.x / params.scale_factor; - let baseline_origin_y = subpixel_shift.y / params.scale_factor; + let baseline_origin_x = + params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor; + let baseline_origin_y = + params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor; let mut rendering_mode = DWRITE_RENDERING_MODE1::default(); let mut grid_fit_mode = DWRITE_GRID_FIT_MODE::default(); @@ -859,7 +858,7 @@ impl DirectWriteState { let bitmap_size = glyph_bounds.size; let subpixel_shift = params .subpixel_variant - .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); + .map(|v| v as f32 / SUBPIXEL_VARIANTS_X as f32); let baseline_origin_x = subpixel_shift.x / params.scale_factor; let baseline_origin_y = subpixel_shift.y / params.scale_factor; diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 4d4087f45d4093c239218f96f015d153fa77dc10..efa4ad032a66ce92a71cbd82be6ed4a63d527858 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -41,7 +41,13 @@ pub struct FontId(pub usize); #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] pub struct FontFamilyId(pub usize); -pub(crate) const SUBPIXEL_VARIANTS: u8 = 4; +pub(crate) const SUBPIXEL_VARIANTS_X: u8 = 4; + +pub(crate) const SUBPIXEL_VARIANTS_Y: u8 = if cfg!(target_os = "windows") { + 1 +} else { + SUBPIXEL_VARIANTS_X +}; /// The GPUI text rendering sub system. pub struct TextSystem { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 307197acca7cd3eba7f69e7731288449a96ad35a..d9bf27dca1253fa0a5286148ea64a03c3a5bac37 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -11,8 +11,8 @@ use crate::{ MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptButton, PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge, - SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size, - StrikethroughStyle, Style, SubscriberSet, Subscription, SystemWindowTab, + SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ScaledPixels, Scene, Shadow, + SharedString, Size, StrikethroughStyle, Style, SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabHandles, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, @@ -2944,9 +2944,10 @@ impl Window { let element_opacity = self.element_opacity(); let scale_factor = self.scale_factor(); let glyph_origin = origin.scale(scale_factor); + let subpixel_variant = Point { - x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8, - y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8, + x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS_X as f32).floor() as u8, + y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS_Y as f32).floor() as u8, }; let params = RenderGlyphParams { font_id, From 1bbf98aea6f335e791f19d8f76ba8a5f0510937f Mon Sep 17 00:00:00 2001 From: strygwyr <13832826+dstrygwyr@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:35:43 +0700 Subject: [PATCH 175/721] Fix arrow function detection in TypeScript/JavaScript outline (#38411) Closes #35102 https://github.com/user-attachments/assets/3c946d6c-0acd-4cfe-8cb3-61eb6d20f808 Release Notes: - TypeScript/JavaScript: symbol outline now includes closures nested within functions. --- crates/languages/src/javascript/outline.scm | 22 +++++++++++++++++++++ crates/languages/src/tsx/outline.scm | 22 +++++++++++++++++++++ crates/languages/src/typescript/outline.scm | 22 +++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/crates/languages/src/javascript/outline.scm b/crates/languages/src/javascript/outline.scm index ca16c27a27be3e1e09ced16cd2eef7aa28345f9e..8a58b6dc1b04b0b36f6155055bd8439bc2ddeca2 100644 --- a/crates/languages/src/javascript/outline.scm +++ b/crates/languages/src/javascript/outline.scm @@ -116,4 +116,26 @@ ) ) @item +; Arrow functions in variable declarations (anywhere in the tree, including nested in functions) +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (arrow_function)) @item) + +; Async arrow functions in variable declarations +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (arrow_function + "async" @context)) @item) + +; Named function expressions in variable declarations +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (function_expression)) @item) + (comment) @annotation diff --git a/crates/languages/src/tsx/outline.scm b/crates/languages/src/tsx/outline.scm index f4261b9697d376f517b717bc942387190e0b6dde..72efff9ab74155c626bd7b91ad1cf8b580431492 100644 --- a/crates/languages/src/tsx/outline.scm +++ b/crates/languages/src/tsx/outline.scm @@ -124,4 +124,26 @@ ) ) @item +; Arrow functions in variable declarations (anywhere in the tree, including nested in functions) +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (arrow_function)) @item) + +; Async arrow functions in variable declarations +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (arrow_function + "async" @context)) @item) + +; Named function expressions in variable declarations +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (function_expression)) @item) + (comment) @annotation diff --git a/crates/languages/src/typescript/outline.scm b/crates/languages/src/typescript/outline.scm index f4261b9697d376f517b717bc942387190e0b6dde..72efff9ab74155c626bd7b91ad1cf8b580431492 100644 --- a/crates/languages/src/typescript/outline.scm +++ b/crates/languages/src/typescript/outline.scm @@ -124,4 +124,26 @@ ) ) @item +; Arrow functions in variable declarations (anywhere in the tree, including nested in functions) +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (arrow_function)) @item) + +; Async arrow functions in variable declarations +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (arrow_function + "async" @context)) @item) + +; Named function expressions in variable declarations +(lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name + value: (function_expression)) @item) + (comment) @annotation From e0028fbef26947298a8b44a27f560fd36b04a131 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Mon, 22 Sep 2025 14:56:37 +0200 Subject: [PATCH 176/721] git_ui: Remove duplicated/unused tooltips (#38439) Release Notes: - N/A --- crates/git_ui/src/commit_modal.rs | 4 ---- crates/git_ui/src/git_panel.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs index a2f84726543af50312dc24d0fcd9e0486b51d9c5..b7088f27e918e92ac669de87158f8b810488e956 100644 --- a/crates/git_ui/src/commit_modal.rs +++ b/crates/git_ui/src/commit_modal.rs @@ -368,10 +368,6 @@ impl CommitModal { .icon_color(Color::Placeholder) .color(Color::Muted) .icon_position(IconPosition::Start) - .tooltip(Tooltip::for_action_title( - "Switch Branch", - &zed_actions::git::Branch, - )) .on_click(cx.listener(|_, _, window, cx| { window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx); })) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 47dcc68d2137f75666ae04d2b8ffe4e87cb478f8..f8df51357da99909b28e871a8aa6202328d2412d 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -4575,10 +4575,6 @@ impl RenderOnce for PanelRepoFooter { .size(ButtonSize::None) .label_size(LabelSize::Small) .truncate(true) - .tooltip(Tooltip::for_action_title( - "Switch Branch", - &zed_actions::git::Switch, - )) .on_click(|_, window, cx| { window.dispatch_action(zed_actions::git::Switch.boxed_clone(), cx); }); From fbe06238e45c2fec6955c70b86eb26fd6a864266 Mon Sep 17 00:00:00 2001 From: Xiaobo Liu Date: Mon, 22 Sep 2025 21:12:19 +0800 Subject: [PATCH 177/721] cli: Refactor URL prefix checks (#38375) use slice apply to prefix. Release Notes: - N/A --------- Signed-off-by: Xiaobo Liu --- crates/cli/src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index d0d30f72d7aea7d7f6cf0355caf12b1f2a36eedb..b1a7ebbd8c445e9eb09d572bff42b81dc995dea1 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -20,6 +20,8 @@ use util::paths::PathWithPosition; #[cfg(any(target_os = "linux", target_os = "freebsd"))] use std::io::IsTerminal; +const URL_PREFIX: [&'static str; 5] = ["zed://", "http://", "https://", "file://", "ssh://"]; + struct Detect; trait InstalledApp { @@ -310,12 +312,7 @@ fn main() -> Result<()> { let wsl = None; for path in args.paths_with_position.iter() { - if path.starts_with("zed://") - || path.starts_with("http://") - || path.starts_with("https://") - || path.starts_with("file://") - || path.starts_with("ssh://") - { + if URL_PREFIX.iter().any(|&prefix| path.starts_with(prefix)) { urls.push(path.to_string()); } else if path == "-" && args.paths_with_position.len() == 1 { let file = NamedTempFile::new()?; From b97843ea02994f029db9cf740fae89ead4417c55 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 22 Sep 2025 16:52:33 +0200 Subject: [PATCH 178/721] Add quick "Edit debug.json" button to debugger control strip (#38600) This button already exists in the main menu, as well as the "New Session" view in the debugger panel. However, this view disappears after starting the debugging session. This PR adds the same button to the debugger control strip that remains accessible. This is convenient for people editing their debug.json frequently. Site-node: I feel like the `Cog` icon would be more appropriate, but I picked `Code` to stay consistent with the "New Session" view. Before: image After: image Release Notes: - Added "Edit debug.json" button to debugger control strip --- crates/debugger_ui/src/debugger_panel.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index f1a1b4dc738f82f729832c60648392af8b9921ed..787bca01acb204a4a50b18a34f3567137f92aa0e 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -625,6 +625,15 @@ impl DebugPanel { }) }; + let edit_debug_json_button = || { + IconButton::new("debug-edit-debug-json", IconName::Code) + .icon_size(IconSize::Small) + .on_click(|_, window, cx| { + window.dispatch_action(zed_actions::OpenProjectDebugTasks.boxed_clone(), cx); + }) + .tooltip(Tooltip::text("Edit debug.json")) + }; + let documentation_button = || { IconButton::new("debug-open-documentation", IconName::CircleHelp) .icon_size(IconSize::Small) @@ -899,8 +908,9 @@ impl DebugPanel { ) .when(is_side, |this| { this.child(new_session_button()) - .child(logs_button()) + .child(edit_debug_json_button()) .child(documentation_button()) + .child(logs_button()) }), ) .child( @@ -951,8 +961,9 @@ impl DebugPanel { )) .when(!is_side, |this| { this.child(new_session_button()) - .child(logs_button()) + .child(edit_debug_json_button()) .child(documentation_button()) + .child(logs_button()) }), ), ), From dccbb47fbca1fc7fca283d0fffc0aa9eee6e1f68 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 22 Sep 2025 08:56:15 -0600 Subject: [PATCH 179/721] Use a consistent default for window scaling (#38527) (And make it 2, because most macs have retina screens) Release Notes: - N/A --- crates/gpui/src/platform/mac/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e8b42c57b8239f53118487f51bd194178c3c21c0..d5b1ea900d81adf83448960f485ed767c5549716 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1566,7 +1566,7 @@ fn get_scale_factor(native_window: id) -> f32 { let factor = unsafe { let screen: id = msg_send![native_window, screen]; if screen.is_null() { - return 1.0; + return 2.0; } NSScreen::backingScaleFactor(screen) as f32 }; From 4e6e424fd78f2c024eff88efaaca4878379a5472 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 22 Sep 2025 17:07:40 +0200 Subject: [PATCH 180/721] acp: Support model selection for ACP agents (#38652) It requires the agent to implement the (still unstable) model selection API. Will allow us to test it out before stabilizing. Release Notes: - N/A --- Cargo.lock | 9 +- Cargo.toml | 2 +- crates/acp_thread/src/connection.rs | 51 +++--- crates/agent2/src/agent.rs | 119 ++++++++------ crates/agent2/src/tests/mod.rs | 29 ++-- crates/agent_servers/src/acp.rs | 103 +++++++++++- crates/agent_ui/src/acp/model_selector.rs | 154 +++++++++++------- .../src/acp/model_selector_popover.rs | 4 +- crates/agent_ui/src/acp/thread_view.rs | 32 ++-- crates/picker/Cargo.toml | 1 + crates/picker/src/picker.rs | 67 +++++++- crates/ui/src/components/context_menu.rs | 6 +- tooling/workspace-hack/Cargo.toml | 4 +- 13 files changed, 391 insertions(+), 190 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbe2467499ad1c5d6f67c4de82546e2b560451bb..e51968b0262a91d3a1ed78a10656e75b9d0d4523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "agent-client-protocol" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2526e80463b9742afed4829aedd6ae5632d6db778c6cc1fecb80c960c3521b" +checksum = "00e33b9f4bd34d342b6f80b7156d3a37a04aeec16313f264001e52d6a9118600" dependencies = [ "anyhow", "async-broadcast", @@ -4932,7 +4932,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.0", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -12677,6 +12677,7 @@ dependencies = [ "schemars 1.0.1", "serde", "serde_json", + "theme", "ui", "workspace", "workspace-hack", @@ -20853,7 +20854,7 @@ dependencies = [ "windows-sys 0.48.0", "windows-sys 0.52.0", "windows-sys 0.59.0", - "windows-sys 0.60.2", + "windows-sys 0.61.0", "winnow", "zeroize", "zvariant", diff --git a/Cargo.toml b/Cargo.toml index d4812908ac8292caf8371ce1d6dd9c9ee4042ca0..fd552c6e9d117bd03b251f231dee8294b02ba928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -439,7 +439,7 @@ zlog_settings = { path = "crates/zlog_settings" } # External crates # -agent-client-protocol = { version = "0.4.0", features = ["unstable"] } +agent-client-protocol = { version = "0.4.2", features = ["unstable"] } aho-corasick = "1.1" alacritty_terminal = "0.25.1-rc1" any_vec = "0.14" diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index 10c9dd22b6ec476f17fabeae7f6bd4f1a9672db7..fe66f954370f8118d054ee56f1e9f68f2de7e6f4 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -68,7 +68,7 @@ pub trait AgentConnection { /// /// If the agent does not support model selection, returns [None]. /// This allows sharing the selector in UI components. - fn model_selector(&self) -> Option> { + fn model_selector(&self, _session_id: &acp::SessionId) -> Option> { None } @@ -177,61 +177,48 @@ pub trait AgentModelSelector: 'static { /// If the session doesn't exist or the model is invalid, it returns an error. /// /// # Parameters - /// - `session_id`: The ID of the session (thread) to apply the model to. /// - `model`: The model to select (should be one from [list_models]). /// - `cx`: The GPUI app context. /// /// # Returns /// A task resolving to `Ok(())` on success or an error. - fn select_model( - &self, - session_id: acp::SessionId, - model_id: AgentModelId, - cx: &mut App, - ) -> Task>; + fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task>; /// Retrieves the currently selected model for a specific session (thread). /// /// # Parameters - /// - `session_id`: The ID of the session (thread) to query. /// - `cx`: The GPUI app context. /// /// # Returns /// A task resolving to the selected model (always set) or an error (e.g., session not found). - fn selected_model( - &self, - session_id: &acp::SessionId, - cx: &mut App, - ) -> Task>; + fn selected_model(&self, cx: &mut App) -> Task>; /// Whenever the model list is updated the receiver will be notified. - fn watch(&self, cx: &mut App) -> watch::Receiver<()>; -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AgentModelId(pub SharedString); - -impl std::ops::Deref for AgentModelId { - type Target = SharedString; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl fmt::Display for AgentModelId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) + /// Optional for agents that don't update their model list. + fn watch(&self, _cx: &mut App) -> Option> { + None } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AgentModelInfo { - pub id: AgentModelId, + pub id: acp::ModelId, pub name: SharedString, + pub description: Option, pub icon: Option, } +impl From for AgentModelInfo { + fn from(info: acp::ModelInfo) -> Self { + Self { + id: info.model_id, + name: info.name.into(), + description: info.description.map(|desc| desc.into()), + icon: None, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AgentModelGroupName(pub SharedString); diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index 86fb50242c64917248df5c620782af066e639b54..36ab1be9ef79221b530258c4fdd55be2ac1e8b29 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -56,7 +56,7 @@ struct Session { pub struct LanguageModels { /// Access language model by ID - models: HashMap>, + models: HashMap>, /// Cached list for returning language model information model_list: acp_thread::AgentModelList, refresh_models_rx: watch::Receiver<()>, @@ -132,10 +132,7 @@ impl LanguageModels { self.refresh_models_rx.clone() } - pub fn model_from_id( - &self, - model_id: &acp_thread::AgentModelId, - ) -> Option> { + pub fn model_from_id(&self, model_id: &acp::ModelId) -> Option> { self.models.get(model_id).cloned() } @@ -146,12 +143,13 @@ impl LanguageModels { acp_thread::AgentModelInfo { id: Self::model_id(model), name: model.name().0, + description: None, icon: Some(provider.icon()), } } - fn model_id(model: &Arc) -> acp_thread::AgentModelId { - acp_thread::AgentModelId(format!("{}/{}", model.provider_id().0, model.id().0).into()) + fn model_id(model: &Arc) -> acp::ModelId { + acp::ModelId(format!("{}/{}", model.provider_id().0, model.id().0).into()) } fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> { @@ -836,10 +834,15 @@ impl NativeAgentConnection { } } -impl AgentModelSelector for NativeAgentConnection { +struct NativeAgentModelSelector { + session_id: acp::SessionId, + connection: NativeAgentConnection, +} + +impl acp_thread::AgentModelSelector for NativeAgentModelSelector { fn list_models(&self, cx: &mut App) -> Task> { log::debug!("NativeAgentConnection::list_models called"); - let list = self.0.read(cx).models.model_list.clone(); + let list = self.connection.0.read(cx).models.model_list.clone(); Task::ready(if list.is_empty() { Err(anyhow::anyhow!("No models available")) } else { @@ -847,24 +850,24 @@ impl AgentModelSelector for NativeAgentConnection { }) } - fn select_model( - &self, - session_id: acp::SessionId, - model_id: acp_thread::AgentModelId, - cx: &mut App, - ) -> Task> { - log::debug!("Setting model for session {}: {}", session_id, model_id); + fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task> { + log::debug!( + "Setting model for session {}: {}", + self.session_id, + model_id + ); let Some(thread) = self + .connection .0 .read(cx) .sessions - .get(&session_id) + .get(&self.session_id) .map(|session| session.thread.clone()) else { return Task::ready(Err(anyhow!("Session not found"))); }; - let Some(model) = self.0.read(cx).models.model_from_id(&model_id) else { + let Some(model) = self.connection.0.read(cx).models.model_from_id(&model_id) else { return Task::ready(Err(anyhow!("Invalid model ID {}", model_id))); }; @@ -872,33 +875,32 @@ impl AgentModelSelector for NativeAgentConnection { thread.set_model(model.clone(), cx); }); - update_settings_file(self.0.read(cx).fs.clone(), cx, move |settings, _cx| { - let provider = model.provider_id().0.to_string(); - let model = model.id().0.to_string(); - settings - .agent - .get_or_insert_default() - .set_model(LanguageModelSelection { - provider: provider.into(), - model, - }); - }); + update_settings_file( + self.connection.0.read(cx).fs.clone(), + cx, + move |settings, _cx| { + let provider = model.provider_id().0.to_string(); + let model = model.id().0.to_string(); + settings + .agent + .get_or_insert_default() + .set_model(LanguageModelSelection { + provider: provider.into(), + model, + }); + }, + ); Task::ready(Ok(())) } - fn selected_model( - &self, - session_id: &acp::SessionId, - cx: &mut App, - ) -> Task> { - let session_id = session_id.clone(); - + fn selected_model(&self, cx: &mut App) -> Task> { let Some(thread) = self + .connection .0 .read(cx) .sessions - .get(&session_id) + .get(&self.session_id) .map(|session| session.thread.clone()) else { return Task::ready(Err(anyhow!("Session not found"))); @@ -915,8 +917,8 @@ impl AgentModelSelector for NativeAgentConnection { ))) } - fn watch(&self, cx: &mut App) -> watch::Receiver<()> { - self.0.read(cx).models.watch() + fn watch(&self, cx: &mut App) -> Option> { + Some(self.connection.0.read(cx).models.watch()) } } @@ -972,8 +974,11 @@ impl acp_thread::AgentConnection for NativeAgentConnection { Task::ready(Ok(())) } - fn model_selector(&self) -> Option> { - Some(Rc::new(self.clone()) as Rc) + fn model_selector(&self, session_id: &acp::SessionId) -> Option> { + Some(Rc::new(NativeAgentModelSelector { + session_id: session_id.clone(), + connection: self.clone(), + }) as Rc) } fn prompt( @@ -1196,9 +1201,7 @@ mod tests { use crate::HistoryEntryId; use super::*; - use acp_thread::{ - AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo, MentionUri, - }; + use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelInfo, MentionUri}; use fs::FakeFs; use gpui::TestAppContext; use indoc::indoc; @@ -1292,7 +1295,25 @@ mod tests { .unwrap(), ); - let models = cx.update(|cx| connection.list_models(cx)).await.unwrap(); + // Create a thread/session + let acp_thread = cx + .update(|cx| { + Rc::new(connection.clone()).new_thread(project.clone(), Path::new("/a"), cx) + }) + .await + .unwrap(); + + let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone()); + + let models = cx + .update(|cx| { + connection + .model_selector(&session_id) + .unwrap() + .list_models(cx) + }) + .await + .unwrap(); let acp_thread::AgentModelList::Grouped(models) = models else { panic!("Unexpected model group"); @@ -1302,8 +1323,9 @@ mod tests { IndexMap::from_iter([( AgentModelGroupName("Fake".into()), vec![AgentModelInfo { - id: AgentModelId("fake/fake".into()), + id: acp::ModelId("fake/fake".into()), name: "Fake".into(), + description: None, icon: Some(ui::IconName::ZedAssistant), }] )]) @@ -1360,8 +1382,9 @@ mod tests { let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone()); // Select a model - let model_id = AgentModelId("fake/fake".into()); - cx.update(|cx| connection.select_model(session_id.clone(), model_id.clone(), cx)) + let selector = connection.model_selector(&session_id).unwrap(); + let model_id = acp::ModelId("fake/fake".into()); + cx.update(|cx| selector.select_model(model_id.clone(), cx)) .await .unwrap(); diff --git a/crates/agent2/src/tests/mod.rs b/crates/agent2/src/tests/mod.rs index c0f693afe6dc0decdce4447471191bd78cf345f1..2e63aa5856501f880fec94f7659b13be321b03b3 100644 --- a/crates/agent2/src/tests/mod.rs +++ b/crates/agent2/src/tests/mod.rs @@ -1850,8 +1850,18 @@ async fn test_agent_connection(cx: &mut TestAppContext) { .unwrap(); let connection = NativeAgentConnection(agent.clone()); + // Create a thread using new_thread + let connection_rc = Rc::new(connection.clone()); + let acp_thread = cx + .update(|cx| connection_rc.new_thread(project, cwd, cx)) + .await + .expect("new_thread should succeed"); + + // Get the session_id from the AcpThread + let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone()); + // Test model_selector returns Some - let selector_opt = connection.model_selector(); + let selector_opt = connection.model_selector(&session_id); assert!( selector_opt.is_some(), "agent2 should always support ModelSelector" @@ -1868,23 +1878,16 @@ async fn test_agent_connection(cx: &mut TestAppContext) { }; assert!(!listed_models.is_empty(), "should have at least one model"); assert_eq!( - listed_models[&AgentModelGroupName("Fake".into())][0].id.0, + listed_models[&AgentModelGroupName("Fake".into())][0] + .id + .0 + .as_ref(), "fake/fake" ); - // Create a thread using new_thread - let connection_rc = Rc::new(connection.clone()); - let acp_thread = cx - .update(|cx| connection_rc.new_thread(project, cwd, cx)) - .await - .expect("new_thread should succeed"); - - // Get the session_id from the AcpThread - let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone()); - // Test selected_model returns the default let model = cx - .update(|cx| selector.selected_model(&session_id, cx)) + .update(|cx| selector.selected_model(cx)) .await .expect("selected_model should succeed"); let model = cx diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index b8c75a01a2e2965c255e32bd3c0746b26d78ecab..b14c0467c58d3f41e32e602996560e2cc672d76a 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -44,6 +44,7 @@ pub struct AcpConnection { pub struct AcpSession { thread: WeakEntity, suppress_abort_err: bool, + models: Option>>, session_modes: Option>>, } @@ -264,6 +265,7 @@ impl AgentConnection for AcpConnection { })?; let modes = response.modes.map(|modes| Rc::new(RefCell::new(modes))); + let models = response.models.map(|models| Rc::new(RefCell::new(models))); if let Some(default_mode) = default_mode { if let Some(modes) = modes.as_ref() { @@ -326,10 +328,12 @@ impl AgentConnection for AcpConnection { ) })?; + let session = AcpSession { thread: thread.downgrade(), suppress_abort_err: false, - session_modes: modes + session_modes: modes, + models, }; sessions.borrow_mut().insert(session_id, session); @@ -450,6 +454,27 @@ impl AgentConnection for AcpConnection { } } + fn model_selector( + &self, + session_id: &acp::SessionId, + ) -> Option> { + let sessions = self.sessions.clone(); + let sessions_ref = sessions.borrow(); + let Some(session) = sessions_ref.get(session_id) else { + return None; + }; + + if let Some(models) = session.models.as_ref() { + Some(Rc::new(AcpModelSelector::new( + session_id.clone(), + self.connection.clone(), + models.clone(), + )) as _) + } else { + None + } + } + fn into_any(self: Rc) -> Rc { self } @@ -500,6 +525,82 @@ impl acp_thread::AgentSessionModes for AcpSessionModes { } } +struct AcpModelSelector { + session_id: acp::SessionId, + connection: Rc, + state: Rc>, +} + +impl AcpModelSelector { + fn new( + session_id: acp::SessionId, + connection: Rc, + state: Rc>, + ) -> Self { + Self { + session_id, + connection, + state, + } + } +} + +impl acp_thread::AgentModelSelector for AcpModelSelector { + fn list_models(&self, _cx: &mut App) -> Task> { + Task::ready(Ok(acp_thread::AgentModelList::Flat( + self.state + .borrow() + .available_models + .clone() + .into_iter() + .map(acp_thread::AgentModelInfo::from) + .collect(), + ))) + } + + fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task> { + let connection = self.connection.clone(); + let session_id = self.session_id.clone(); + let old_model_id; + { + let mut state = self.state.borrow_mut(); + old_model_id = state.current_model_id.clone(); + state.current_model_id = model_id.clone(); + }; + let state = self.state.clone(); + cx.foreground_executor().spawn(async move { + let result = connection + .set_session_model(acp::SetSessionModelRequest { + session_id, + model_id, + meta: None, + }) + .await; + + if result.is_err() { + state.borrow_mut().current_model_id = old_model_id; + } + + result?; + + Ok(()) + }) + } + + fn selected_model(&self, _cx: &mut App) -> Task> { + let state = self.state.borrow(); + Task::ready( + state + .available_models + .iter() + .find(|m| m.model_id == state.current_model_id) + .cloned() + .map(acp_thread::AgentModelInfo::from) + .ok_or_else(|| anyhow::anyhow!("Model not found")), + ) + } +} + struct ClientDelegate { sessions: Rc>>, cx: AsyncApp, diff --git a/crates/agent_ui/src/acp/model_selector.rs b/crates/agent_ui/src/acp/model_selector.rs index 95c0478aa3cf6b1ca78cf391a5bd734820c41454..381bdb01edec49e222c9bd9b3a97ce9ba21a9789 100644 --- a/crates/agent_ui/src/acp/model_selector.rs +++ b/crates/agent_ui/src/acp/model_selector.rs @@ -1,7 +1,6 @@ use std::{cmp::Reverse, rc::Rc, sync::Arc}; use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector}; -use agent_client_protocol as acp; use anyhow::Result; use collections::IndexMap; use futures::FutureExt; @@ -10,20 +9,19 @@ use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, W use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use ui::{ - AnyElement, App, Context, IntoElement, ListItem, ListItemSpacing, SharedString, Window, - prelude::*, rems, + AnyElement, App, Context, DocumentationAside, DocumentationEdge, DocumentationSide, + IntoElement, ListItem, ListItemSpacing, SharedString, Window, prelude::*, rems, }; use util::ResultExt; pub type AcpModelSelector = Picker; pub fn acp_model_selector( - session_id: acp::SessionId, selector: Rc, window: &mut Window, cx: &mut Context, ) -> AcpModelSelector { - let delegate = AcpModelPickerDelegate::new(session_id, selector, window, cx); + let delegate = AcpModelPickerDelegate::new(selector, window, cx); Picker::list(delegate, window, cx) .show_scrollbar(true) .width(rems(20.)) @@ -36,61 +34,63 @@ enum AcpModelPickerEntry { } pub struct AcpModelPickerDelegate { - session_id: acp::SessionId, selector: Rc, filtered_entries: Vec, models: Option, selected_index: usize, + selected_description: Option<(usize, SharedString)>, selected_model: Option, _refresh_models_task: Task<()>, } impl AcpModelPickerDelegate { fn new( - session_id: acp::SessionId, selector: Rc, window: &mut Window, cx: &mut Context, ) -> Self { - let mut rx = selector.watch(cx); - let refresh_models_task = cx.spawn_in(window, { - let session_id = session_id.clone(); - async move |this, cx| { - async fn refresh( - this: &WeakEntity>, - session_id: &acp::SessionId, - cx: &mut AsyncWindowContext, - ) -> Result<()> { - let (models_task, selected_model_task) = this.update(cx, |this, cx| { - ( - this.delegate.selector.list_models(cx), - this.delegate.selector.selected_model(session_id, cx), - ) - })?; - - let (models, selected_model) = futures::join!(models_task, selected_model_task); + let rx = selector.watch(cx); + let refresh_models_task = { + cx.spawn_in(window, { + async move |this, cx| { + async fn refresh( + this: &WeakEntity>, + cx: &mut AsyncWindowContext, + ) -> Result<()> { + let (models_task, selected_model_task) = this.update(cx, |this, cx| { + ( + this.delegate.selector.list_models(cx), + this.delegate.selector.selected_model(cx), + ) + })?; - this.update_in(cx, |this, window, cx| { - this.delegate.models = models.ok(); - this.delegate.selected_model = selected_model.ok(); - this.refresh(window, cx) - }) - } + let (models, selected_model) = + futures::join!(models_task, selected_model_task); - refresh(&this, &session_id, cx).await.log_err(); - while let Ok(()) = rx.recv().await { - refresh(&this, &session_id, cx).await.log_err(); + this.update_in(cx, |this, window, cx| { + this.delegate.models = models.ok(); + this.delegate.selected_model = selected_model.ok(); + this.refresh(window, cx) + }) + } + + refresh(&this, cx).await.log_err(); + if let Some(mut rx) = rx { + while let Ok(()) = rx.recv().await { + refresh(&this, cx).await.log_err(); + } + } } - } - }); + }) + }; Self { - session_id, selector, filtered_entries: Vec::new(), models: None, selected_model: None, selected_index: 0, + selected_description: None, _refresh_models_task: refresh_models_task, } } @@ -182,7 +182,7 @@ impl PickerDelegate for AcpModelPickerDelegate { self.filtered_entries.get(self.selected_index) { self.selector - .select_model(self.session_id.clone(), model_info.id.clone(), cx) + .select_model(model_info.id.clone(), cx) .detach_and_log_err(cx); self.selected_model = Some(model_info.clone()); let current_index = self.selected_index; @@ -233,31 +233,46 @@ impl PickerDelegate for AcpModelPickerDelegate { }; Some( - ListItem::new(ix) - .inset(true) - .spacing(ListItemSpacing::Sparse) - .toggle_state(selected) - .start_slot::(model_info.icon.map(|icon| { - Icon::new(icon) - .color(model_icon_color) - .size(IconSize::Small) - })) + div() + .id(("model-picker-menu-child", ix)) + .when_some(model_info.description.clone(), |this, description| { + this + .on_hover(cx.listener(move |menu, hovered, _, cx| { + if *hovered { + menu.delegate.selected_description = Some((ix, description.clone())); + } else if matches!(menu.delegate.selected_description, Some((id, _)) if id == ix) { + menu.delegate.selected_description = None; + } + cx.notify(); + })) + }) .child( - h_flex() - .w_full() - .pl_0p5() - .gap_1p5() - .w(px(240.)) - .child(Label::new(model_info.name.clone()).truncate()), + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .toggle_state(selected) + .start_slot::(model_info.icon.map(|icon| { + Icon::new(icon) + .color(model_icon_color) + .size(IconSize::Small) + })) + .child( + h_flex() + .w_full() + .pl_0p5() + .gap_1p5() + .w(px(240.)) + .child(Label::new(model_info.name.clone()).truncate()), + ) + .end_slot(div().pr_3().when(is_selected, |this| { + this.child( + Icon::new(IconName::Check) + .color(Color::Accent) + .size(IconSize::Small), + ) + })), ) - .end_slot(div().pr_3().when(is_selected, |this| { - this.child( - Icon::new(IconName::Check) - .color(Color::Accent) - .size(IconSize::Small), - ) - })) - .into_any_element(), + .into_any_element() ) } } @@ -292,6 +307,21 @@ impl PickerDelegate for AcpModelPickerDelegate { .into_any(), ) } + + fn documentation_aside( + &self, + _window: &mut Window, + _cx: &mut Context>, + ) -> Option { + self.selected_description.as_ref().map(|(_, description)| { + let description = description.clone(); + DocumentationAside::new( + DocumentationSide::Left, + DocumentationEdge::Bottom, + Rc::new(move |_| Label::new(description.clone()).into_any_element()), + ) + }) + } } fn info_list_to_picker_entries( @@ -371,6 +401,7 @@ async fn fuzzy_search( #[cfg(test)] mod tests { + use agent_client_protocol as acp; use gpui::TestAppContext; use super::*; @@ -383,8 +414,9 @@ mod tests { models .into_iter() .map(|model| acp_thread::AgentModelInfo { - id: acp_thread::AgentModelId(model.to_string().into()), + id: acp::ModelId(model.to_string().into()), name: model.to_string().into(), + description: None, icon: None, }) .collect::>(), diff --git a/crates/agent_ui/src/acp/model_selector_popover.rs b/crates/agent_ui/src/acp/model_selector_popover.rs index fa771c695ecf8175859d145b8d08d2cf3447a77a..55f530c81b1cead74fd4ec4f6cc29ececcf2bf7e 100644 --- a/crates/agent_ui/src/acp/model_selector_popover.rs +++ b/crates/agent_ui/src/acp/model_selector_popover.rs @@ -1,7 +1,6 @@ use std::rc::Rc; use acp_thread::AgentModelSelector; -use agent_client_protocol as acp; use gpui::{Entity, FocusHandle}; use picker::popover_menu::PickerPopoverMenu; use ui::{ @@ -20,7 +19,6 @@ pub struct AcpModelSelectorPopover { impl AcpModelSelectorPopover { pub(crate) fn new( - session_id: acp::SessionId, selector: Rc, menu_handle: PopoverMenuHandle, focus_handle: FocusHandle, @@ -28,7 +26,7 @@ impl AcpModelSelectorPopover { cx: &mut Context, ) -> Self { Self { - selector: cx.new(move |cx| acp_model_selector(session_id, selector, window, cx)), + selector: cx.new(move |cx| acp_model_selector(selector, window, cx)), menu_handle, focus_handle, } diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 8658e2c285997c18ece2b9783c25fbcaa614dc83..391486a68eca87e238f9efb88288bc970e3eb412 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -577,23 +577,21 @@ impl AcpThreadView { AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx); - this.model_selector = - thread - .read(cx) - .connection() - .model_selector() - .map(|selector| { - cx.new(|cx| { - AcpModelSelectorPopover::new( - thread.read(cx).session_id().clone(), - selector, - PopoverMenuHandle::default(), - this.focus_handle(cx), - window, - cx, - ) - }) - }); + this.model_selector = thread + .read(cx) + .connection() + .model_selector(thread.read(cx).session_id()) + .map(|selector| { + cx.new(|cx| { + AcpModelSelectorPopover::new( + selector, + PopoverMenuHandle::default(), + this.focus_handle(cx), + window, + cx, + ) + }) + }); let mode_selector = thread .read(cx) diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index d785cb5b3a96502165b10e2bf0def0d8bf66cd67..23c867b6f30aa64d5916e8939d836dda27ebf6c9 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -22,6 +22,7 @@ gpui.workspace = true menu.workspace = true schemars.workspace = true serde.workspace = true +theme.workspace = true ui.workspace = true workspace.workspace = true workspace-hack.workspace = true diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 8816fb5424ff25788cec9cb602d2960ab753c135..247fcbdd875ffc2e52d90d9b1309f874c508e588 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -18,11 +18,12 @@ use head::Head; use schemars::JsonSchema; use serde::Deserialize; use std::{ops::Range, sync::Arc, time::Duration}; +use theme::ThemeSettings; use ui::{ - Color, Divider, Label, ListItem, ListItemSpacing, ScrollAxes, Scrollbars, WithScrollbar, - prelude::*, v_flex, + Color, Divider, DocumentationAside, DocumentationEdge, DocumentationSide, Label, ListItem, + ListItemSpacing, ScrollAxes, Scrollbars, WithScrollbar, prelude::*, utils::WithRemSize, v_flex, }; -use workspace::ModalView; +use workspace::{ModalView, item::Settings}; enum ElementContainer { List(ListState), @@ -222,6 +223,14 @@ pub trait PickerDelegate: Sized + 'static { ) -> Option { None } + + fn documentation_aside( + &self, + _window: &mut Window, + _cx: &mut Context>, + ) -> Option { + None + } } impl Focusable for Picker { @@ -781,8 +790,15 @@ impl ModalView for Picker {} impl Render for Picker { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx); + let window_size = window.viewport_size(); + let rem_size = window.rem_size(); + let is_wide_window = window_size.width / rem_size > rems_from_px(800.).0; + + let aside = self.delegate.documentation_aside(window, cx); + let editor_position = self.delegate.editor_position(); - v_flex() + let menu = v_flex() .key_context("Picker") .size_full() .when_some(self.width, |el, width| el.w(width)) @@ -865,6 +881,47 @@ impl Render for Picker { } } Head::Empty(empty_head) => Some(div().child(empty_head.clone())), - }) + }); + + let Some(aside) = aside else { + return menu; + }; + + let render_aside = |aside: DocumentationAside, cx: &mut Context| { + WithRemSize::new(ui_font_size) + .occlude() + .elevation_2(cx) + .w_full() + .p_2() + .overflow_hidden() + .when(is_wide_window, |this| this.max_w_96()) + .when(!is_wide_window, |this| this.max_w_48()) + .child((aside.render)(cx)) + }; + + if is_wide_window { + div().relative().child(menu).child( + h_flex() + .absolute() + .when(aside.side == DocumentationSide::Left, |this| { + this.right_full().mr_1() + }) + .when(aside.side == DocumentationSide::Right, |this| { + this.left_full().ml_1() + }) + .when(aside.edge == DocumentationEdge::Top, |this| this.top_0()) + .when(aside.edge == DocumentationEdge::Bottom, |this| { + this.bottom_0() + }) + .child(render_aside(aside, cx)), + ) + } else { + v_flex() + .w_full() + .gap_1() + .justify_end() + .child(render_aside(aside, cx)) + .child(menu) + } } } diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index e57f02be915fdecec7a5af4894c6f4fdd72f48bc..7b61789b3c87d54ff231e1d635266d6502fb944f 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -180,9 +180,9 @@ pub enum DocumentationEdge { #[derive(Clone)] pub struct DocumentationAside { - side: DocumentationSide, - edge: DocumentationEdge, - render: Rc AnyElement>, + pub side: DocumentationSide, + pub edge: DocumentationEdge, + pub render: Rc AnyElement>, } impl DocumentationAside { diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 68fd84b32b64e15b0ea63ef851ec5aac457179c2..342d675bf38c3f9233d3dee4f8eefd77bfbc7836 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -600,10 +600,10 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti winapi = { version = "0.3", default-features = false, features = ["cfg", "commapi", "consoleapi", "evntrace", "fileapi", "handleapi", "impl-debug", "impl-default", "in6addr", "inaddr", "ioapiset", "knownfolders", "minwinbase", "minwindef", "namedpipeapi", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "synchapi", "sysinfoapi", "timezoneapi", "winbase", "windef", "winerror", "winioctl", "winnt", "winreg", "winsock2", "winuser"] } windows-core = { version = "0.61" } windows-numerics = { version = "0.2" } -windows-sys-4db8c43aad08e7ae = { package = "windows-sys", version = "0.60", features = ["Win32_Globalization", "Win32_System_Com", "Win32_UI_Shell"] } windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } +windows-sys-d4189bed749088b6 = { package = "windows-sys", version = "0.61", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } [target.x86_64-pc-windows-msvc.build-dependencies] codespan-reporting = { version = "0.12" } @@ -627,10 +627,10 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti winapi = { version = "0.3", default-features = false, features = ["cfg", "commapi", "consoleapi", "evntrace", "fileapi", "handleapi", "impl-debug", "impl-default", "in6addr", "inaddr", "ioapiset", "knownfolders", "minwinbase", "minwindef", "namedpipeapi", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "synchapi", "sysinfoapi", "timezoneapi", "winbase", "windef", "winerror", "winioctl", "winnt", "winreg", "winsock2", "winuser"] } windows-core = { version = "0.61" } windows-numerics = { version = "0.2" } -windows-sys-4db8c43aad08e7ae = { package = "windows-sys", version = "0.60", features = ["Win32_Globalization", "Win32_System_Com", "Win32_UI_Shell"] } windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } +windows-sys-d4189bed749088b6 = { package = "windows-sys", version = "0.61", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } [target.x86_64-unknown-linux-musl.dependencies] aes = { version = "0.8", default-features = false, features = ["zeroize"] } From d4fd59f0a299b021025b31d1872a4e7058858dbe Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 22 Sep 2025 19:07:16 +0300 Subject: [PATCH 181/721] vim: Add support for `gt` and `gT` (#38570) Vim mode currently supports `gt` (go to next tab) and `gT` (go to previous tab) but not with count. Implement the expected behavior as defined by vim: - `gt` moves to tab `` - `gT` moves to previous tab `` times (with wraparound) Release Notes: - Improved vim `gt` and `gT` to support count, e.g. `5gt` - go to tab 5, `8gT` - go to 8th previous tab with wraparound. --- assets/keymaps/vim.json | 4 +- crates/vim/src/normal.rs | 132 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 590e84cf7fc10f7af5dd317bc114b75390414e4f..8f5f99e96f708dcc08cc1a9c1fcfc799d6ba43e7 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -95,8 +95,8 @@ "g g": "vim::StartOfDocument", "g h": "editor::Hover", "g B": "editor::BlameHover", - "g t": "pane::ActivateNextItem", - "g shift-t": "pane::ActivatePreviousItem", + "g t": "vim::GoToTab", + "g shift-t": "vim::GoToPreviousTab", "g d": "editor::GoToDefinition", "g shift-d": "editor::GoToDeclaration", "g y": "editor::GoToTypeDefinition", diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 7dfdb973c7603e9ef28bf757a9a716e729b72170..5d227ffd8bb6d92acc0546fcb9b9767962f6b417 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -28,7 +28,7 @@ use editor::Editor; use editor::{Anchor, SelectionEffects}; use editor::{Bias, ToPoint}; use editor::{display_map::ToDisplayPoint, movement}; -use gpui::{Context, Window, actions}; +use gpui::{Action, Context, Window, actions}; use language::{Point, SelectionGoal}; use log::error; use multi_buffer::MultiBufferRow; @@ -94,6 +94,10 @@ actions!( Redo, /// Undoes all changes to the most recently changed line. UndoLastLine, + /// Go to tab page (with count support). + GoToTab, + /// Go to previous tab page (with count support). + GoToPreviousTab, ] ); @@ -116,6 +120,8 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::toggle_comments); Vim::action(editor, cx, Vim::paste); Vim::action(editor, cx, Vim::show_location); + Vim::action(editor, cx, Vim::go_to_tab); + Vim::action(editor, cx, Vim::go_to_previous_tab); Vim::action(editor, cx, |vim, _: &DeleteLeft, window, cx| { vim.record_current_action(cx); @@ -984,6 +990,54 @@ impl Vim { self.switch_mode(Mode::Insert, true, window, cx); } } + + fn go_to_tab(&mut self, _: &GoToTab, window: &mut Window, cx: &mut Context) { + let count = Vim::take_count(cx); + Vim::take_forced_motion(cx); + + if let Some(tab_index) = count { + // gt goes to tab (1-based). + let zero_based_index = tab_index.saturating_sub(1); + window.dispatch_action( + workspace::pane::ActivateItem(zero_based_index).boxed_clone(), + cx, + ); + } else { + // If no count is provided, go to the next tab. + window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx); + } + } + + fn go_to_previous_tab( + &mut self, + _: &GoToPreviousTab, + window: &mut Window, + cx: &mut Context, + ) { + let count = Vim::take_count(cx); + Vim::take_forced_motion(cx); + + if let Some(count) = count { + // gT with count goes back that many tabs with wraparound (not the same as gt!). + if let Some(workspace) = self.workspace(window) { + let pane = workspace.read(cx).active_pane().read(cx); + let item_count = pane.items().count(); + if item_count > 0 { + let current_index = pane.active_item_index(); + let target_index = (current_index as isize - count as isize) + .rem_euclid(item_count as isize) + as usize; + window.dispatch_action( + workspace::pane::ActivateItem(target_index).boxed_clone(), + cx, + ); + } + } + } else { + // No count provided, go to the previous tab. + window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), cx); + } + } } #[cfg(test)] mod test { @@ -2119,4 +2173,80 @@ mod test { Mode::Normal, ); } + + #[gpui::test] + async fn test_go_to_tab_with_count(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + // Open 4 tabs. + cx.simulate_keystrokes(": tabnew"); + cx.simulate_keystrokes("enter"); + cx.simulate_keystrokes(": tabnew"); + cx.simulate_keystrokes("enter"); + cx.simulate_keystrokes(": tabnew"); + cx.simulate_keystrokes("enter"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.items(cx).count(), 4); + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3); + }); + + cx.simulate_keystrokes("1 g t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 0); + }); + + cx.simulate_keystrokes("3 g t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 2); + }); + + cx.simulate_keystrokes("4 g t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3); + }); + + cx.simulate_keystrokes("1 g t"); + cx.simulate_keystrokes("g t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 1); + }); + } + + #[gpui::test] + async fn test_go_to_previous_tab_with_count(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + // Open 4 tabs. + cx.simulate_keystrokes(": tabnew"); + cx.simulate_keystrokes("enter"); + cx.simulate_keystrokes(": tabnew"); + cx.simulate_keystrokes("enter"); + cx.simulate_keystrokes(": tabnew"); + cx.simulate_keystrokes("enter"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.items(cx).count(), 4); + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3); + }); + + cx.simulate_keystrokes("2 g shift-t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 1); + }); + + cx.simulate_keystrokes("g shift-t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 0); + }); + + // Wraparound: gT from first tab should go to last. + cx.simulate_keystrokes("g shift-t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3); + }); + + cx.simulate_keystrokes("6 g shift-t"); + cx.workspace(|workspace, _, cx| { + assert_eq!(workspace.active_pane().read(cx).active_item_index(), 1); + }); + } } From 9e64b7b911ea23d0758b7e5b8aad1e8c411fe72f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 22 Sep 2025 18:12:35 +0200 Subject: [PATCH 182/721] terminal: Escape args in alacritty on Windows (#38650) Release Notes: - N/A --- crates/terminal/src/terminal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d1a4c8af9687c87a8c63b598262d0bdf797fada4..987e3272602763f93d350c07b10246707b0ea2ec 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -428,7 +428,7 @@ impl TerminalBuilder { drain_on_exit: true, env: env.clone().into_iter().collect(), #[cfg(windows)] - escape_args: false, + escape_args: true, } }; From 003163eb4f08e87eace4aaa40f41bdfb5a7c1f19 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 22 Sep 2025 10:22:37 -0600 Subject: [PATCH 183/721] Move my keybinding fixes to the right platform (#38654) In cffb883108ec07ec2f51446cb35eac19b89e625f I put the fixed keybindings on the wrong platform Release Notes: - Fix syntax node shortcuts --- assets/keymaps/default-macos.json | 2 ++ assets/keymaps/default-windows.json | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 3d5887de75bf13218985c33ab37b8b54ca9ea0a1..4cf25c3b71047b7eb19791cee91f062d5720fe61 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -550,6 +550,8 @@ "cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection "cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection "cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up + "ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version) + "ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version) "cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down "cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand "cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 9165840d695af73a41aedded9b8037ffbce8ccbf..5608d01379de75e612270da243c846d4da8d775f 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -497,8 +497,6 @@ "shift-alt-down": "editor::DuplicateLineDown", "shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection "shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection - "ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version) - "ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version) "ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection "ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word "ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand From c88fdaf02d82160a5d321c28f238320c08e965e5 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 22 Sep 2025 12:33:12 -0400 Subject: [PATCH 184/721] Implement Markdown link embedding on paste (#38639) This PR adds automatic markdown URL embedding on paste when you are in text associated with the Markdown language and you have a valid URL in your clipboard. This the default behavior in VS Code and GitHub, when pasting a URL in Markdown. It works in both singleton buffers and multi buffers. One thing that is a bit unfortunate is that, previously, `do_paste` use to simply call `Editor::insert()`, in the case of pasting content that was copied from an external application, and now, we are duplicating some of `insert()`'s logic in place, in order to have control over transforming the edits before they are inserted. Release Notes: - Added automatic Markdown URL embedding on paste. --------- Co-authored-by: Cole Miller <53574922+cole-miller@users.noreply.github.com> --- crates/editor/src/editor.rs | 112 +++++++++++++--- crates/editor/src/editor_tests.rs | 211 ++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8b0fc5512731eff70b1e9ac41b6bfe16a65babfa..fbf70322b890ab9a2a3c1f9e915a5debae2e4e64 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -12452,13 +12452,14 @@ impl Editor { return; } - let clipboard_text = Cow::Borrowed(text); + let clipboard_text = Cow::Borrowed(text.as_str()); self.transact(window, cx, |this, window, cx| { let had_active_edit_prediction = this.has_active_edit_prediction(); + let old_selections = this.selections.all::(cx); + let cursor_offset = this.selections.last::(cx).head(); if let Some(mut clipboard_selections) = clipboard_selections { - let old_selections = this.selections.all::(cx); let all_selections_were_entire_line = clipboard_selections.iter().all(|s| s.is_entire_line); let first_selection_indent_column = @@ -12466,7 +12467,6 @@ impl Editor { if clipboard_selections.len() != old_selections.len() { clipboard_selections.drain(..); } - let cursor_offset = this.selections.last::(cx).head(); let mut auto_indent_on_paste = true; this.buffer.update(cx, |buffer, cx| { @@ -12489,22 +12489,36 @@ impl Editor { start_offset = end_offset + 1; original_indent_column = Some(clipboard_selection.first_line_indent); } else { - to_insert = clipboard_text.as_str(); + to_insert = &*clipboard_text; entire_line = all_selections_were_entire_line; original_indent_column = first_selection_indent_column } - // If the corresponding selection was empty when this slice of the - // clipboard text was written, then the entire line containing the - // selection was copied. If this selection is also currently empty, - // then paste the line before the current line of the buffer. - let range = if selection.is_empty() && handle_entire_lines && entire_line { - let column = selection.start.to_point(&snapshot).column as usize; - let line_start = selection.start - column; - line_start..line_start - } else { - selection.range() - }; + let (range, to_insert) = + if selection.is_empty() && handle_entire_lines && entire_line { + // If the corresponding selection was empty when this slice of the + // clipboard text was written, then the entire line containing the + // selection was copied. If this selection is also currently empty, + // then paste the line before the current line of the buffer. + let column = selection.start.to_point(&snapshot).column as usize; + let line_start = selection.start - column; + (line_start..line_start, Cow::Borrowed(to_insert)) + } else { + let language = snapshot.language_at(selection.head()); + let range = selection.range(); + if let Some(language) = language + && language.name() == "Markdown".into() + { + edit_for_markdown_paste( + &snapshot, + range, + to_insert, + url::Url::parse(to_insert).ok(), + ) + } else { + (range, Cow::Borrowed(to_insert)) + } + }; edits.push((range, to_insert)); original_indent_columns.push(original_indent_column); @@ -12527,7 +12541,53 @@ impl Editor { let selections = this.selections.all::(cx); this.change_selections(Default::default(), window, cx, |s| s.select(selections)); } else { - this.insert(&clipboard_text, window, cx); + let url = url::Url::parse(&clipboard_text).ok(); + + let auto_indent_mode = if !clipboard_text.is_empty() { + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }) + } else { + None + }; + + let selection_anchors = this.buffer.update(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + + let anchors = old_selections + .iter() + .map(|s| { + let anchor = snapshot.anchor_after(s.head()); + s.map(|_| anchor) + }) + .collect::>(); + + let mut edits = Vec::new(); + + for selection in old_selections.iter() { + let language = snapshot.language_at(selection.head()); + let range = selection.range(); + + let (edit_range, edit_text) = if let Some(language) = language + && language.name() == "Markdown".into() + { + edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone()) + } else { + (range, clipboard_text.clone()) + }; + + edits.push((edit_range, edit_text)); + } + + drop(snapshot); + buffer.edit(edits, auto_indent_mode, cx); + + anchors + }); + + this.change_selections(Default::default(), window, cx, |s| { + s.select_anchors(selection_anchors); + }); } let trigger_in_words = @@ -21679,6 +21739,26 @@ impl Editor { } } +fn edit_for_markdown_paste<'a>( + buffer: &MultiBufferSnapshot, + range: Range, + to_insert: &'a str, + url: Option, +) -> (Range, Cow<'a, str>) { + if url.is_none() { + return (range, Cow::Borrowed(to_insert)); + }; + + let old_text = buffer.text_for_range(range.clone()).collect::(); + + let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() { + Cow::Borrowed(to_insert) + } else { + Cow::Owned(format!("[{old_text}]({to_insert})")) + }; + (range, new_text) +} + fn vim_enabled(cx: &App) -> bool { vim_mode_setting::VimModeSetting::try_get(cx) .map(|vim_mode| vim_mode.0) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 05742cd00bb834550ee20377ff46da6649272f43..9f888702f99b0b916d35625806c18e53043d0101 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -25974,6 +25974,217 @@ let result = variable * 2;", ); } +#[gpui::test] +async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text( + cx: &mut gpui::TestAppContext, +) { + init_test(cx, |_| {}); + + let url = "https://zed.dev"; + + let markdown_language = Arc::new(Language::new( + LanguageConfig { + name: "Markdown".into(), + ..LanguageConfig::default() + }, + None, + )); + + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx)); + cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)"); + + cx.update_editor(|editor, window, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(url.to_string())); + editor.paste(&Paste, window, cx); + }); + + cx.assert_editor_state(&format!( + "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)" + )); +} + +#[gpui::test] +async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text( + cx: &mut gpui::TestAppContext, +) { + init_test(cx, |_| {}); + + let url = "https://zed.dev"; + + let markdown_language = Arc::new(Language::new( + LanguageConfig { + name: "Markdown".into(), + ..LanguageConfig::default() + }, + None, + )); + + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx)); + cx.set_state(&format!( + "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»" + )); + + cx.update_editor(|editor, window, cx| { + editor.copy(&Copy, window, cx); + }); + + cx.set_state(&format!( + "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}" + )); + + cx.update_editor(|editor, window, cx| { + editor.paste(&Paste, window, cx); + }); + + cx.assert_editor_state(&format!( + "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}" + )); +} + +#[gpui::test] +async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link( + cx: &mut gpui::TestAppContext, +) { + init_test(cx, |_| {}); + + let url = "https://zed.dev"; + + let markdown_language = Arc::new(Language::new( + LanguageConfig { + name: "Markdown".into(), + ..LanguageConfig::default() + }, + None, + )); + + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx)); + cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»"); + + cx.update_editor(|editor, window, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(url.to_string())); + editor.paste(&Paste, window, cx); + }); + + cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ")); +} + +#[gpui::test] +async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link( + cx: &mut gpui::TestAppContext, +) { + init_test(cx, |_| {}); + + let text = "Awesome"; + + let markdown_language = Arc::new(Language::new( + LanguageConfig { + name: "Markdown".into(), + ..LanguageConfig::default() + }, + None, + )); + + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx)); + cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»"); + + cx.update_editor(|editor, window, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(text.to_string())); + editor.paste(&Paste, window, cx); + }); + + cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ")); +} + +#[gpui::test] +async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language( + cx: &mut gpui::TestAppContext, +) { + init_test(cx, |_| {}); + + let url = "https://zed.dev"; + + let markdown_language = Arc::new(Language::new( + LanguageConfig { + name: "Rust".into(), + ..LanguageConfig::default() + }, + None, + )); + + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx)); + cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)"); + + cx.update_editor(|editor, window, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(url.to_string())); + editor.paste(&Paste, window, cx); + }); + + cx.assert_editor_state(&format!( + "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)" + )); +} + +#[gpui::test] +async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer( + cx: &mut TestAppContext, +) { + init_test(cx, |_| {}); + + let url = "https://zed.dev"; + + let markdown_language = Arc::new(Language::new( + LanguageConfig { + name: "Markdown".into(), + ..LanguageConfig::default() + }, + None, + )); + + let (editor, cx) = cx.add_window_view(|window, cx| { + let multi_buffer = MultiBuffer::build_multi( + [ + ("this will embed -> link", vec![Point::row_range(0..1)]), + ("this will replace -> link", vec![Point::row_range(0..1)]), + ], + cx, + ); + let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(vec![ + Point::new(0, 19)..Point::new(0, 23), + Point::new(1, 21)..Point::new(1, 25), + ]) + }); + let first_buffer_id = multi_buffer + .read(cx) + .excerpt_buffer_ids() + .into_iter() + .next() + .unwrap(); + let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap(); + first_buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(markdown_language.clone()), cx); + }); + + editor + }); + let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await; + + cx.update_editor(|editor, window, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(url.to_string())); + editor.paste(&Paste, window, cx); + }); + + cx.assert_editor_state(&format!( + "this will embed -> [link]({url})ˇ\nthis will replace -> {url}ˇ" + )); +} + #[track_caller] fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec { editor From a0514af58955a21401b4c10918a45c9c241a4a74 Mon Sep 17 00:00:00 2001 From: Miao Date: Tue, 23 Sep 2025 00:56:40 +0800 Subject: [PATCH 185/721] editor: Make buffer search bar capture CopyPath & CopyRelativePath actions (#38645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #38495 Cause: - When the Find input is focused, CopyPath/CopyRelativePath were handled by the editor and stopped during the bubble phase, preventing BufferSearchBar from relaying to the file-backed editor. Release Notes: - Fixes “Workspace: Copy Relative Path” not copying while the Find bar is focused. --- crates/editor/src/editor.rs | 4 ++++ crates/search/src/buffer_search.rs | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fbf70322b890ab9a2a3c1f9e915a5debae2e4e64..38ebdb4909b051d96700447617b392a7741714a7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -19308,6 +19308,8 @@ impl Editor { && let Some(path) = path.to_str() { cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); + } else { + cx.propagate(); } } @@ -19321,6 +19323,8 @@ impl Editor { && let Some(path) = path.to_str() { cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); + } else { + cx.propagate(); } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index dd096cde851b21983be80c5ce64b78338f54d78e..925d390cb3eb5489025818e4826aba691ac1bfa8 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -28,7 +28,7 @@ use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; use std::sync::Arc; -use zed_actions::outline::ToggleOutline; +use zed_actions::{outline::ToggleOutline, workspace::CopyPath, workspace::CopyRelativePath}; use ui::{ BASE_REM_SIZE_IN_PX, IconButton, IconButtonShape, IconName, Tooltip, h_flex, prelude::*, @@ -425,6 +425,16 @@ impl Render for BufferSearchBar { active_searchable_item.relay_action(Box::new(ToggleOutline), window, cx); } })) + .on_action(cx.listener(|this, _: &CopyPath, window, cx| { + if let Some(active_searchable_item) = &mut this.active_searchable_item { + active_searchable_item.relay_action(Box::new(CopyPath), window, cx); + } + })) + .on_action(cx.listener(|this, _: &CopyRelativePath, window, cx| { + if let Some(active_searchable_item) = &mut this.active_searchable_item { + active_searchable_item.relay_action(Box::new(CopyRelativePath), window, cx); + } + })) .when(replacement, |this| { this.on_action(cx.listener(Self::toggle_replace)) .when(in_replace, |this| { From d4adb515539193e617752df144c42baa9d8a4f03 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 22 Sep 2025 14:59:24 -0400 Subject: [PATCH 186/721] languages: Update package.json and tsconfig.json schemas (#38655) Closes: https://github.com/zed-industries/zed/issues/34382 - Add support for `tsconfig.*.json` not just `tsconfig.json`. - Updated JSON schemas to [SchemaStore/schemastore@281aa4a](https://github.com/SchemaStore/schemastore/tree/281aa4aa4ac21385814423f86a54d1b8ccfc17a1) (2025-09-21) - [tsconfig.json](https://github.com/SchemaStore/schemastore/commits/master/src/schemas/json/tsconfig.json) @ [281aa4a](https://raw.githubusercontent.com/SchemaStore/schemastore/281aa4aa4ac21385814423f86a54d1b8ccfc17a1/src/schemas/json/tsconfig.json) - [package.json](https://github.com/SchemaStore/schemastore/commits/master/src/schemas/json/package.json) @ [281aa4a](https://raw.githubusercontent.com/SchemaStore/schemastore/281aa4aa4ac21385814423f86a54d1b8ccfc17a1/src/schemas/json/package.json) See also: - [discord thread](https://discord.com/channels/869392257814519848/1419298937290096760) - https://github.com/zed-industries/zed/issues/21994#issuecomment-3319321308 Release Notes: - Updated package.json and tsconfig.json schemas to newest release (2025-09-21). Match `tsconfig.*.json` too. --- assets/settings/default.json | 2 +- crates/languages/src/json.rs | 2 +- .../languages/src/json/schemas/package.json | 150 +++++++++++- .../languages/src/json/schemas/tsconfig.json | 225 +++++------------- 4 files changed, 212 insertions(+), 167 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 091231521470ebec50cf1351a76063e9205a3d24..7730ba8cf63f94ddab0ecf6c1d989c9d66c590d4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1514,7 +1514,7 @@ // } // "file_types": { - "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"], + "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json", "tsconfig*.json"], "Shell Script": [".env.*"] }, // Settings for which version of Node.js and NPM to use when installing diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 496b0389e6e331f5c1d694d3ad30b5abffbee106..7e698cbf095b5679aefda4230b2b5b08c24b0825 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -181,7 +181,7 @@ impl JsonLspAdapter { #[allow(unused_mut)] let mut schemas = serde_json::json!([ { - "fileMatch": ["tsconfig.json"], + "fileMatch": ["tsconfig*.json"], "schema":tsconfig_schema }, { diff --git a/crates/languages/src/json/schemas/package.json b/crates/languages/src/json/schemas/package.json index 664149eca92b81946420c98405219440c7be7c08..a24583fa8848891d661114291951d4df28f463fd 100644 --- a/crates/languages/src/json/schemas/package.json +++ b/crates/languages/src/json/schemas/package.json @@ -160,6 +160,11 @@ "$ref": "#/definitions/packageExportsEntryOrFallback", "description": "The module path that is resolved when this specifier is imported as an ECMAScript module using an `import` declaration or the dynamic `import(...)` function." }, + "module-sync": { + "$ref": "#/definitions/packageExportsEntryOrFallback", + "$comment": "https://nodejs.org/api/packages.html#conditional-exports#:~:text=%22module-sync%22", + "description": "The same as `import`, but can be used with require(esm) in Node 20+. This requires the files to not use any top-level awaits." + }, "node": { "$ref": "#/definitions/packageExportsEntryOrFallback", "description": "The module path that is resolved when this environment is Node.js." @@ -304,6 +309,33 @@ "required": [ "url" ] + }, + "devEngineDependency": { + "description": "Specifies requirements for development environment components such as operating systems, runtimes, or package managers. Used to ensure consistent development environments across the team.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the dependency, with allowed values depending on the parent field" + }, + "version": { + "type": "string", + "description": "The version range for the dependency" + }, + "onFail": { + "type": "string", + "enum": [ + "ignore", + "warn", + "error", + "download" + ], + "description": "What action to take if validation fails" + } + } } }, "type": "object", @@ -755,7 +787,7 @@ ] }, "resolutions": { - "description": "Resolutions is used to support selective version resolutions using yarn, which lets you define custom package versions or ranges inside your dependencies. For npm, use overrides instead. See: https://classic.yarnpkg.com/en/docs/selective-version-resolutions", + "description": "Resolutions is used to support selective version resolutions using yarn, which lets you define custom package versions or ranges inside your dependencies. For npm, use overrides instead. See: https://yarnpkg.com/configuration/manifest#resolutions", "type": "object" }, "overrides": { @@ -810,6 +842,82 @@ "type": "string" } }, + "devEngines": { + "description": "Define the runtime and package manager for developing the current project.", + "type": "object", + "properties": { + "os": { + "oneOf": [ + { + "$ref": "#/definitions/devEngineDependency" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/devEngineDependency" + } + } + ], + "description": "Specifies which operating systems are supported for development" + }, + "cpu": { + "oneOf": [ + { + "$ref": "#/definitions/devEngineDependency" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/devEngineDependency" + } + } + ], + "description": "Specifies which CPU architectures are supported for development" + }, + "libc": { + "oneOf": [ + { + "$ref": "#/definitions/devEngineDependency" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/devEngineDependency" + } + } + ], + "description": "Specifies which C standard libraries are supported for development" + }, + "runtime": { + "oneOf": [ + { + "$ref": "#/definitions/devEngineDependency" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/devEngineDependency" + } + } + ], + "description": "Specifies which JavaScript runtimes (like Node.js, Deno, Bun) are supported for development. Values should use WinterCG Runtime Keys (see https://runtime-keys.proposal.wintercg.org/)" + }, + "packageManager": { + "oneOf": [ + { + "$ref": "#/definitions/devEngineDependency" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/devEngineDependency" + } + } + ], + "description": "Specifies which package managers are supported for development" + } + } + }, "preferGlobal": { "type": "boolean", "description": "DEPRECATED: This option used to trigger an npm warning, but it will no longer warn. It is purely there for informational purposes. It is now recommended that you install any binaries as local devDependencies wherever possible." @@ -973,6 +1081,7 @@ "additionalProperties": false }, "peerDependencyRules": { + "type": "object", "properties": { "ignoreMissing": { "description": "pnpm will not print warnings about missing peer dependencies from this list.", @@ -1032,6 +1141,10 @@ "description": "When true, installation won't fail if some of the patches from the \"patchedDependencies\" field were not applied.", "type": "boolean" }, + "allowUnusedPatches": { + "description": "When true, installation won't fail if some of the patches from the \"patchedDependencies\" field were not applied.", + "type": "boolean" + }, "updateConfig": { "type": "object", "properties": { @@ -1122,6 +1235,41 @@ } }, "additionalProperties": false + }, + "stackblitz": { + "description": "Defines the StackBlitz configuration for the project.", + "type": "object", + "properties": { + "installDependencies": { + "description": "StackBlitz automatically installs npm dependencies when opening a project.", + "type": "boolean" + }, + "startCommand": { + "description": "A terminal command to be executed when opening the project, after installing npm dependencies.", + "type": [ + "string", + "boolean" + ] + }, + "compileTrigger": { + "description": "The compileTrigger option controls how file changes in the editor are written to the WebContainers in-memory filesystem. ", + "oneOf": [ + { + "type": "string", + "enum": [ + "auto", + "keystroke", + "save" + ] + } + ] + }, + "env": { + "description": "A map of default environment variables that will be set in each top-level shell process.", + "type": "object" + } + }, + "additionalProperties": false } }, "anyOf": [ diff --git a/crates/languages/src/json/schemas/tsconfig.json b/crates/languages/src/json/schemas/tsconfig.json index 4b9088725401e27dfc24c14d7c58acfae4355631..e734062d65b1f330495e96ea55c2e28388e5bcc8 100644 --- a/crates/languages/src/json/schemas/tsconfig.json +++ b/crates/languages/src/json/schemas/tsconfig.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", + "$comment": "Note that this schema uses 'null' in various places. The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058)", "allowTrailingCommas": true, "allOf": [ { @@ -49,7 +50,6 @@ "filesDefinition": { "properties": { "files": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included.", "type": ["array", "null"], "uniqueItems": true, @@ -62,7 +62,6 @@ "excludeDefinition": { "properties": { "exclude": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specifies a list of files to be excluded from compilation. The 'exclude' property only affects the files included via the 'include' property and not the 'files' property. Glob patterns require TypeScript version 2.0 or later.", "type": ["array", "null"], "uniqueItems": true, @@ -75,7 +74,6 @@ "includeDefinition": { "properties": { "include": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later.", "type": ["array", "null"], "uniqueItems": true, @@ -118,41 +116,35 @@ "buildOptions": { "properties": { "dry": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "~", "type": ["boolean", "null"], "default": false }, "force": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Build all projects, including those that appear to be up to date", "type": ["boolean", "null"], "default": false, "markdownDescription": "Build all projects, including those that appear to be up to date\n\nSee more: https://www.typescriptlang.org/tsconfig#force" }, "verbose": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable verbose logging", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable verbose logging\n\nSee more: https://www.typescriptlang.org/tsconfig#verbose" }, "incremental": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Save .tsbuildinfo files to allow for incremental compilation of projects.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Save .tsbuildinfo files to allow for incremental compilation of projects.\n\nSee more: https://www.typescriptlang.org/tsconfig#incremental" }, "assumeChangesOnlyAffectDirectDependencies": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Have recompiles in projects that use `incremental` and `watch` mode assume that changes within a file will only affect files directly depending on it.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Have recompiles in projects that use `incremental` and `watch` mode assume that changes within a file will only affect files directly depending on it.\n\nSee more: https://www.typescriptlang.org/tsconfig#assumeChangesOnlyAffectDirectDependencies" }, "traceResolution": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Log paths used during the `moduleResolution` process.", "type": ["boolean", "null"], "default": false, @@ -165,7 +157,6 @@ "watchOptionsDefinition": { "properties": { "watchOptions": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["object", "null"], "description": "Settings for the watch mode in TypeScript.", "properties": { @@ -174,31 +165,26 @@ "type": ["string", "null"] }, "watchFile": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify how the TypeScript watch mode works.", "type": ["string", "null"], "markdownDescription": "Specify how the TypeScript watch mode works.\n\nSee more: https://www.typescriptlang.org/tsconfig#watchFile" }, "watchDirectory": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify how directories are watched on systems that lack recursive file-watching functionality.", "type": ["string", "null"], "markdownDescription": "Specify how directories are watched on systems that lack recursive file-watching functionality.\n\nSee more: https://www.typescriptlang.org/tsconfig#watchDirectory" }, "fallbackPolling": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify what approach the watcher should use if the system runs out of native file watchers.", "type": ["string", "null"], "markdownDescription": "Specify what approach the watcher should use if the system runs out of native file watchers.\n\nSee more: https://www.typescriptlang.org/tsconfig#fallbackPolling" }, "synchronousWatchDirectory": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Synchronously call callbacks and update the state of directory watchers on platforms that don`t support recursive watching natively.", "type": ["boolean", "null"], "markdownDescription": "Synchronously call callbacks and update the state of directory watchers on platforms that don`t support recursive watching natively.\n\nSee more: https://www.typescriptlang.org/tsconfig#synchronousWatchDirectory" }, "excludeFiles": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Remove a list of files from the watch mode's processing.", "type": ["array", "null"], "uniqueItems": true, @@ -208,7 +194,6 @@ "markdownDescription": "Remove a list of files from the watch mode's processing.\n\nSee more: https://www.typescriptlang.org/tsconfig#excludeFiles" }, "excludeDirectories": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Remove a list of directories from the watch process.", "type": ["array", "null"], "uniqueItems": true, @@ -224,37 +209,31 @@ "compilerOptionsDefinition": { "properties": { "compilerOptions": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["object", "null"], "description": "Instructs the TypeScript compiler how to compile .ts files.", "properties": { "allowArbitraryExtensions": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable importing files with any extension, provided a declaration file is present.", "type": ["boolean", "null"], "markdownDescription": "Enable importing files with any extension, provided a declaration file is present.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowArbitraryExtensions" }, "allowImportingTsExtensions": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", - "description": "Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set.", + "description": "Allow imports to include TypeScript file extensions. Requires either '--noEmit' or '--emitDeclarationOnly' to be set.", "type": ["boolean", "null"], - "markdownDescription": "Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowImportingTsExtensions" + "markdownDescription": "Allow imports to include TypeScript file extensions. Requires either '--noEmit' or '--emitDeclarationOnly' to be set.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowImportingTsExtensions" }, "charset": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "No longer supported. In early versions, manually set the text encoding for reading files.", "type": ["string", "null"], "markdownDescription": "No longer supported. In early versions, manually set the text encoding for reading files.\n\nSee more: https://www.typescriptlang.org/tsconfig#charset" }, "composite": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable constraints that allow a TypeScript project to be used with project references.", "type": ["boolean", "null"], "default": true, "markdownDescription": "Enable constraints that allow a TypeScript project to be used with project references.\n\nSee more: https://www.typescriptlang.org/tsconfig#composite" }, "customConditions": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Conditions to set in addition to the resolver-specific defaults when resolving imports.", "type": ["array", "null"], "uniqueItems": true, @@ -264,52 +243,50 @@ "markdownDescription": "Conditions to set in addition to the resolver-specific defaults when resolving imports.\n\nSee more: https://www.typescriptlang.org/tsconfig#customConditions" }, "declaration": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Generate .d.ts files from TypeScript and JavaScript files in your project.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Generate .d.ts files from TypeScript and JavaScript files in your project.\n\nSee more: https://www.typescriptlang.org/tsconfig#declaration" }, "declarationDir": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the output directory for generated declaration files.", "type": ["string", "null"], "markdownDescription": "Specify the output directory for generated declaration files.\n\nSee more: https://www.typescriptlang.org/tsconfig#declarationDir" }, "diagnostics": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Output compiler performance information after building.", "type": ["boolean", "null"], "markdownDescription": "Output compiler performance information after building.\n\nSee more: https://www.typescriptlang.org/tsconfig#diagnostics" }, "disableReferencedProjectLoad": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Reduce the number of projects loaded automatically by TypeScript.", "type": ["boolean", "null"], "markdownDescription": "Reduce the number of projects loaded automatically by TypeScript.\n\nSee more: https://www.typescriptlang.org/tsconfig#disableReferencedProjectLoad" }, "noPropertyAccessFromIndexSignature": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enforces using indexed accessors for keys declared using an indexed type", "type": ["boolean", "null"], "markdownDescription": "Enforces using indexed accessors for keys declared using an indexed type\n\nSee more: https://www.typescriptlang.org/tsconfig#noPropertyAccessFromIndexSignature" }, "emitBOM": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.\n\nSee more: https://www.typescriptlang.org/tsconfig#emitBOM" }, "emitDeclarationOnly": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Only output d.ts files and not JavaScript files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Only output d.ts files and not JavaScript files.\n\nSee more: https://www.typescriptlang.org/tsconfig#emitDeclarationOnly" }, + "erasableSyntaxOnly": { + "description": "Do not allow runtime constructs that are not part of ECMAScript.", + "type": ["boolean", "null"], + "default": false, + "markdownDescription": "Do not allow runtime constructs that are not part of ECMAScript.\n\nSee more: https://www.typescriptlang.org/tsconfig#erasableSyntaxOnly" + }, "exactOptionalPropertyTypes": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Differentiate between undefined and not present when type checking", "type": ["boolean", "null"], "default": false, @@ -320,21 +297,18 @@ "type": ["boolean", "null"] }, "tsBuildInfoFile": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the folder for .tsbuildinfo incremental compilation files.", "default": ".tsbuildinfo", "type": ["string", "null"], "markdownDescription": "Specify the folder for .tsbuildinfo incremental compilation files.\n\nSee more: https://www.typescriptlang.org/tsconfig#tsBuildInfoFile" }, "inlineSourceMap": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Include sourcemap files inside the emitted JavaScript.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Include sourcemap files inside the emitted JavaScript.\n\nSee more: https://www.typescriptlang.org/tsconfig#inlineSourceMap" }, "inlineSources": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Include source code in the sourcemaps inside the emitted JavaScript.", "type": ["boolean", "null"], "default": false, @@ -351,76 +325,70 @@ ] }, "reactNamespace": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit.", "type": ["string", "null"], "default": "React", "markdownDescription": "Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit.\n\nSee more: https://www.typescriptlang.org/tsconfig#reactNamespace" }, "jsxFactory": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'", "type": ["string", "null"], "default": "React.createElement", "markdownDescription": "Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'\n\nSee more: https://www.typescriptlang.org/tsconfig#jsxFactory" }, "jsxFragmentFactory": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'.", "type": ["string", "null"], "default": "React.Fragment", "markdownDescription": "Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'.\n\nSee more: https://www.typescriptlang.org/tsconfig#jsxFragmentFactory" }, "jsxImportSource": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx`.", "type": ["string", "null"], "default": "react", "markdownDescription": "Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx`.\n\nSee more: https://www.typescriptlang.org/tsconfig#jsxImportSource" }, "listFiles": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Print all of the files read during the compilation.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Print all of the files read during the compilation.\n\nSee more: https://www.typescriptlang.org/tsconfig#listFiles" }, "mapRoot": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the location where debugger should locate map files instead of generated locations.", "type": ["string", "null"], "markdownDescription": "Specify the location where debugger should locate map files instead of generated locations.\n\nSee more: https://www.typescriptlang.org/tsconfig#mapRoot" }, "module": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify what module code is generated.", "type": ["string", "null"], "anyOf": [ { "enum": [ - "CommonJS", - "AMD", - "System", - "UMD", - "ES6", - "ES2015", - "ES2020", - "ESNext", - "None", - "ES2022", - "Node16", - "NodeNext", - "Preserve" + "commonjs", + "amd", + "system", + "umd", + "es6", + "es2015", + "es2020", + "esnext", + "none", + "es2022", + "node16", + "node18", + "node20", + "nodenext", + "preserve" ] }, { - "pattern": "^([Cc][Oo][Mm][Mm][Oo][Nn][Jj][Ss]|[AaUu][Mm][Dd]|[Ss][Yy][Ss][Tt][Ee][Mm]|[Ee][Ss]([356]|20(1[567]|2[02])|[Nn][Ee][Xx][Tt])|[Nn][Oo][dD][Ee]16|[Nn][Oo][Dd][Ee][Nn][Ee][Xx][Tt]|[Nn][Oo][Nn][Ee]|[Pp][Rr][Ee][Ss][Ee][Rr][Vv][Ee])$" + "pattern": "^([Cc][Oo][Mm][Mm][Oo][Nn][Jj][Ss]|[AaUu][Mm][Dd]|[Ss][Yy][Ss][Tt][Ee][Mm]|[Ee][Ss]([356]|20(1[567]|2[02])|[Nn][Ee][Xx][Tt])|[Nn][Oo][dD][Ee]1[68]|[Nn][Oo][Dd][Ee][Nn][Ee][Xx][Tt]|[Nn][Oo][Nn][Ee]|[Pp][Rr][Ee][Ss][Ee][Rr][Vv][Ee])$" } ], "markdownDescription": "Specify what module code is generated.\n\nSee more: https://www.typescriptlang.org/tsconfig#module" }, "moduleResolution": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify how TypeScript looks up a file from a given module specifier.", "type": ["string", "null"], "anyOf": [ @@ -449,7 +417,6 @@ "markdownDescription": "Specify how TypeScript looks up a file from a given module specifier.\n\nSee more: https://www.typescriptlang.org/tsconfig#moduleResolution" }, "newLine": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Set the newline character for emitting files.", "type": ["string", "null"], "default": "lf", @@ -464,208 +431,191 @@ "markdownDescription": "Set the newline character for emitting files.\n\nSee more: https://www.typescriptlang.org/tsconfig#newLine" }, "noEmit": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable emitting file from a compilation.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable emitting file from a compilation.\n\nSee more: https://www.typescriptlang.org/tsconfig#noEmit" }, "noEmitHelpers": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable generating custom helper functions like `__extends` in compiled output.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable generating custom helper functions like `__extends` in compiled output.\n\nSee more: https://www.typescriptlang.org/tsconfig#noEmitHelpers" }, "noEmitOnError": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable emitting files if any type checking errors are reported.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable emitting files if any type checking errors are reported.\n\nSee more: https://www.typescriptlang.org/tsconfig#noEmitOnError" }, "noImplicitAny": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable error reporting for expressions and declarations with an implied `any` type..", "type": ["boolean", "null"], "markdownDescription": "Enable error reporting for expressions and declarations with an implied `any` type..\n\nSee more: https://www.typescriptlang.org/tsconfig#noImplicitAny" }, "noImplicitThis": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable error reporting when `this` is given the type `any`.", "type": ["boolean", "null"], "markdownDescription": "Enable error reporting when `this` is given the type `any`.\n\nSee more: https://www.typescriptlang.org/tsconfig#noImplicitThis" }, "noUnusedLocals": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable error reporting when a local variable isn't read.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable error reporting when a local variable isn't read.\n\nSee more: https://www.typescriptlang.org/tsconfig#noUnusedLocals" }, "noUnusedParameters": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Raise an error when a function parameter isn't read", "type": ["boolean", "null"], "default": false, "markdownDescription": "Raise an error when a function parameter isn't read\n\nSee more: https://www.typescriptlang.org/tsconfig#noUnusedParameters" }, "noLib": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable including any library files, including the default lib.d.ts.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable including any library files, including the default lib.d.ts.\n\nSee more: https://www.typescriptlang.org/tsconfig#noLib" }, "noResolve": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project.\n\nSee more: https://www.typescriptlang.org/tsconfig#noResolve" }, "noStrictGenericChecks": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable strict checking of generic signatures in function types.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable strict checking of generic signatures in function types.\n\nSee more: https://www.typescriptlang.org/tsconfig#noStrictGenericChecks" }, + "out": { + "description": "DEPRECATED. Specify an output for the build. It is recommended to use `outFile` instead.", + "type": ["string", "null"], + "markdownDescription": "Specify an output for the build. It is recommended to use `outFile` instead.\n\nSee more: https://www.typescriptlang.org/tsconfig/#out" + }, "skipDefaultLibCheck": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Skip type checking .d.ts files that are included with TypeScript.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Skip type checking .d.ts files that are included with TypeScript.\n\nSee more: https://www.typescriptlang.org/tsconfig#skipDefaultLibCheck" }, "skipLibCheck": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Skip type checking all .d.ts files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Skip type checking all .d.ts files.\n\nSee more: https://www.typescriptlang.org/tsconfig#skipLibCheck" }, "outFile": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output.", "type": ["string", "null"], "markdownDescription": "Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output.\n\nSee more: https://www.typescriptlang.org/tsconfig#outFile" }, "outDir": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify an output folder for all emitted files.", "type": ["string", "null"], "markdownDescription": "Specify an output folder for all emitted files.\n\nSee more: https://www.typescriptlang.org/tsconfig#outDir" }, "preserveConstEnums": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable erasing `const enum` declarations in generated code.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable erasing `const enum` declarations in generated code.\n\nSee more: https://www.typescriptlang.org/tsconfig#preserveConstEnums" }, "preserveSymlinks": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable resolving symlinks to their realpath. This correlates to the same flag in node.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable resolving symlinks to their realpath. This correlates to the same flag in node.\n\nSee more: https://www.typescriptlang.org/tsconfig#preserveSymlinks" }, "preserveValueImports": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Preserve unused imported values in the JavaScript output that would otherwise be removed", "type": ["boolean", "null"], "default": false, "markdownDescription": "Preserve unused imported values in the JavaScript output that would otherwise be removed\n\nSee more: https://www.typescriptlang.org/tsconfig#preserveValueImports" }, "preserveWatchOutput": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable wiping the console in watch mode", "type": ["boolean", "null"], "markdownDescription": "Disable wiping the console in watch mode\n\nSee more: https://www.typescriptlang.org/tsconfig#preserveWatchOutput" }, "pretty": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable color and formatting in output to make compiler errors easier to read", "type": ["boolean", "null"], "default": true, "markdownDescription": "Enable color and formatting in output to make compiler errors easier to read\n\nSee more: https://www.typescriptlang.org/tsconfig#pretty" }, "removeComments": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable emitting comments.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable emitting comments.\n\nSee more: https://www.typescriptlang.org/tsconfig#removeComments" }, + "rewriteRelativeImportExtensions": { + "description": "Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files.", + "type": ["boolean", "null"], + "default": false, + "markdownDescription": "Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files.\n\nSee more: https://www.typescriptlang.org/tsconfig#rewriteRelativeImportExtensions" + }, "rootDir": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the root folder within your source files.", "type": ["string", "null"], "markdownDescription": "Specify the root folder within your source files.\n\nSee more: https://www.typescriptlang.org/tsconfig#rootDir" }, "isolatedModules": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Ensure that each file can be safely transpiled without relying on other imports.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Ensure that each file can be safely transpiled without relying on other imports.\n\nSee more: https://www.typescriptlang.org/tsconfig#isolatedModules" }, "sourceMap": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Create source map files for emitted JavaScript files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Create source map files for emitted JavaScript files.\n\nSee more: https://www.typescriptlang.org/tsconfig#sourceMap" }, "sourceRoot": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the root path for debuggers to find the reference source code.", "type": ["string", "null"], "markdownDescription": "Specify the root path for debuggers to find the reference source code.\n\nSee more: https://www.typescriptlang.org/tsconfig#sourceRoot" }, "suppressExcessPropertyErrors": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable reporting of excess property errors during the creation of object literals.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable reporting of excess property errors during the creation of object literals.\n\nSee more: https://www.typescriptlang.org/tsconfig#suppressExcessPropertyErrors" }, "suppressImplicitAnyIndexErrors": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Suppress `noImplicitAny` errors when indexing objects that lack index signatures.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Suppress `noImplicitAny` errors when indexing objects that lack index signatures.\n\nSee more: https://www.typescriptlang.org/tsconfig#suppressImplicitAnyIndexErrors" }, "stripInternal": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable emitting declarations that have `@internal` in their JSDoc comments.", "type": ["boolean", "null"], "markdownDescription": "Disable emitting declarations that have `@internal` in their JSDoc comments.\n\nSee more: https://www.typescriptlang.org/tsconfig#stripInternal" }, "target": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Set the JavaScript language version for emitted JavaScript and include compatible library declarations.", "type": ["string", "null"], - "default": "ES3", + "default": "es3", "anyOf": [ { "enum": [ - "ES3", - "ES5", - "ES6", - "ES2015", - "ES2016", - "ES2017", - "ES2018", - "ES2019", - "ES2020", - "ES2021", - "ES2022", - "ES2023", - "ES2024", - "ESNext" + "es3", + "es5", + "es6", + "es2015", + "es2016", + "es2017", + "es2018", + "es2019", + "es2020", + "es2021", + "es2022", + "es2023", + "es2024", + "esnext" ] }, { @@ -675,7 +625,6 @@ "markdownDescription": "Set the JavaScript language version for emitted JavaScript and include compatible library declarations.\n\nSee more: https://www.typescriptlang.org/tsconfig#target" }, "useUnknownInCatchVariables": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Default catch clause variables as `unknown` instead of `any`.", "type": ["boolean", "null"], "default": false, @@ -720,86 +669,72 @@ "default": "useFsEvents" }, "experimentalDecorators": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable experimental support for TC39 stage 2 draft decorators.", "type": ["boolean", "null"], "markdownDescription": "Enable experimental support for TC39 stage 2 draft decorators.\n\nSee more: https://www.typescriptlang.org/tsconfig#experimentalDecorators" }, "emitDecoratorMetadata": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Emit design-type metadata for decorated declarations in source files.", "type": ["boolean", "null"], "markdownDescription": "Emit design-type metadata for decorated declarations in source files.\n\nSee more: https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata" }, "allowUnusedLabels": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable error reporting for unused labels.", "type": ["boolean", "null"], "markdownDescription": "Disable error reporting for unused labels.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowUnusedLabels" }, "noImplicitReturns": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable error reporting for codepaths that do not explicitly return in a function.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable error reporting for codepaths that do not explicitly return in a function.\n\nSee more: https://www.typescriptlang.org/tsconfig#noImplicitReturns" }, "noUncheckedIndexedAccess": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Add `undefined` to a type when accessed using an index.", "type": ["boolean", "null"], "markdownDescription": "Add `undefined` to a type when accessed using an index.\n\nSee more: https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess" }, "noFallthroughCasesInSwitch": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable error reporting for fallthrough cases in switch statements.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable error reporting for fallthrough cases in switch statements.\n\nSee more: https://www.typescriptlang.org/tsconfig#noFallthroughCasesInSwitch" }, "noImplicitOverride": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Ensure overriding members in derived classes are marked with an override modifier.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Ensure overriding members in derived classes are marked with an override modifier.\n\nSee more: https://www.typescriptlang.org/tsconfig#noImplicitOverride" }, "allowUnreachableCode": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable error reporting for unreachable code.", "type": ["boolean", "null"], "markdownDescription": "Disable error reporting for unreachable code.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowUnreachableCode" }, "forceConsistentCasingInFileNames": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Ensure that casing is correct in imports.", "type": ["boolean", "null"], "default": true, "markdownDescription": "Ensure that casing is correct in imports.\n\nSee more: https://www.typescriptlang.org/tsconfig#forceConsistentCasingInFileNames" }, "generateCpuProfile": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Emit a v8 CPU profile of the compiler run for debugging.", "type": ["string", "null"], "default": "profile.cpuprofile", "markdownDescription": "Emit a v8 CPU profile of the compiler run for debugging.\n\nSee more: https://www.typescriptlang.org/tsconfig#generateCpuProfile" }, "baseUrl": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the base directory to resolve non-relative module names.", "type": ["string", "null"], "markdownDescription": "Specify the base directory to resolve non-relative module names.\n\nSee more: https://www.typescriptlang.org/tsconfig#baseUrl" }, "paths": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify a set of entries that re-map imports to additional lookup locations.", "type": ["object", "null"], "additionalProperties": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["array", "null"], "uniqueItems": true, "items": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["string", "null"], "description": "Path mapping to be computed relative to baseUrl option." } @@ -807,11 +742,9 @@ "markdownDescription": "Specify a set of entries that re-map imports to additional lookup locations.\n\nSee more: https://www.typescriptlang.org/tsconfig#paths" }, "plugins": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify a list of language service plugins to include.", "type": ["array", "null"], "items": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["object", "null"], "properties": { "name": { @@ -823,7 +756,6 @@ "markdownDescription": "Specify a list of language service plugins to include.\n\nSee more: https://www.typescriptlang.org/tsconfig#plugins" }, "rootDirs": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Allow multiple folders to be treated as one when resolving modules.", "type": ["array", "null"], "uniqueItems": true, @@ -833,7 +765,6 @@ "markdownDescription": "Allow multiple folders to be treated as one when resolving modules.\n\nSee more: https://www.typescriptlang.org/tsconfig#rootDirs" }, "typeRoots": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify multiple folders that act like `./node_modules/@types`.", "type": ["array", "null"], "uniqueItems": true, @@ -843,7 +774,6 @@ "markdownDescription": "Specify multiple folders that act like `./node_modules/@types`.\n\nSee more: https://www.typescriptlang.org/tsconfig#typeRoots" }, "types": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify type package names to be included without being referenced in a source file.", "type": ["array", "null"], "uniqueItems": true, @@ -853,59 +783,50 @@ "markdownDescription": "Specify type package names to be included without being referenced in a source file.\n\nSee more: https://www.typescriptlang.org/tsconfig#types" }, "traceResolution": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable tracing of the name resolution process. Requires TypeScript version 2.0 or later.", "type": ["boolean", "null"], "default": false }, "allowJs": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowJs" }, "noErrorTruncation": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable truncating types in error messages.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable truncating types in error messages.\n\nSee more: https://www.typescriptlang.org/tsconfig#noErrorTruncation" }, "allowSyntheticDefaultImports": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Allow 'import x from y' when a module doesn't have a default export.", "type": ["boolean", "null"], "markdownDescription": "Allow 'import x from y' when a module doesn't have a default export.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowSyntheticDefaultImports" }, "noImplicitUseStrict": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable adding 'use strict' directives in emitted JavaScript files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable adding 'use strict' directives in emitted JavaScript files.\n\nSee more: https://www.typescriptlang.org/tsconfig#noImplicitUseStrict" }, "listEmittedFiles": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Print the names of emitted files after a compilation.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Print the names of emitted files after a compilation.\n\nSee more: https://www.typescriptlang.org/tsconfig#listEmittedFiles" }, "disableSizeLimit": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Remove the 20mb cap on total source code size for JavaScript files in the TypeScript language server.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Remove the 20mb cap on total source code size for JavaScript files in the TypeScript language server.\n\nSee more: https://www.typescriptlang.org/tsconfig#disableSizeLimit" }, "lib": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify a set of bundled library declaration files that describe the target runtime environment.", "type": ["array", "null"], "uniqueItems": true, "items": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["string", "null"], "anyOf": [ { @@ -954,6 +875,7 @@ "ESNext.BigInt", "ESNext.Collection", "ESNext.Intl", + "ESNext.Iterator", "ESNext.Object", "ESNext.Promise", "ESNext.Regexp", @@ -1001,7 +923,9 @@ "ES2017.Date", "ES2023.Collection", "ESNext.Decorators", - "ESNext.Disposable" + "ESNext.Disposable", + "ESNext.Error", + "ESNext.Sharedmemory" ] }, { @@ -1056,26 +980,29 @@ }, "markdownDescription": "Specify a set of bundled library declaration files that describe the target runtime environment.\n\nSee more: https://www.typescriptlang.org/tsconfig#lib" }, + "libReplacement": { + "description": "Enable lib replacement.", + "type": ["boolean", "null"], + "default": true, + "markdownDescription": "Enable lib replacement.\n\nSee more: https://www.typescriptlang.org/tsconfig#libReplacement" + }, "moduleDetection": { "description": "Specify how TypeScript determine a file as module.", "enum": ["auto", "legacy", "force"] }, "strictNullChecks": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "When type checking, take into account `null` and `undefined`.", "type": ["boolean", "null"], "default": false, "markdownDescription": "When type checking, take into account `null` and `undefined`.\n\nSee more: https://www.typescriptlang.org/tsconfig#strictNullChecks" }, "maxNodeModuleJsDepth": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`.", "type": ["number", "null"], "default": 0, "markdownDescription": "Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`.\n\nSee more: https://www.typescriptlang.org/tsconfig#maxNodeModuleJsDepth" }, "importHelpers": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Allow importing helper functions from tslib once per project, instead of including them per-file.", "type": ["boolean", "null"], "default": false, @@ -1087,104 +1014,89 @@ "enum": ["remove", "preserve", "error"] }, "alwaysStrict": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Ensure 'use strict' is always emitted.", "type": ["boolean", "null"], "markdownDescription": "Ensure 'use strict' is always emitted.\n\nSee more: https://www.typescriptlang.org/tsconfig#alwaysStrict" }, "strict": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable all strict type checking options.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable all strict type checking options.\n\nSee more: https://www.typescriptlang.org/tsconfig#strict" }, "strictBindCallApply": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Check that the arguments for `bind`, `call`, and `apply` methods match the original function.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Check that the arguments for `bind`, `call`, and `apply` methods match the original function.\n\nSee more: https://www.typescriptlang.org/tsconfig#strictBindCallApply" }, "downlevelIteration": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Emit more compliant, but verbose and less performant JavaScript for iteration.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Emit more compliant, but verbose and less performant JavaScript for iteration.\n\nSee more: https://www.typescriptlang.org/tsconfig#downlevelIteration" }, "checkJs": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable error reporting in type-checked JavaScript files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable error reporting in type-checked JavaScript files.\n\nSee more: https://www.typescriptlang.org/tsconfig#checkJs" }, "strictFunctionTypes": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "When assigning functions, check to ensure parameters and the return values are subtype-compatible.", "type": ["boolean", "null"], "default": false, "markdownDescription": "When assigning functions, check to ensure parameters and the return values are subtype-compatible.\n\nSee more: https://www.typescriptlang.org/tsconfig#strictFunctionTypes" }, "strictPropertyInitialization": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Check for class properties that are declared but not set in the constructor.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Check for class properties that are declared but not set in the constructor.\n\nSee more: https://www.typescriptlang.org/tsconfig#strictPropertyInitialization" }, "esModuleInterop": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility.\n\nSee more: https://www.typescriptlang.org/tsconfig#esModuleInterop" }, "allowUmdGlobalAccess": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Allow accessing UMD globals from modules.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Allow accessing UMD globals from modules.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowUmdGlobalAccess" }, "keyofStringsOnly": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Make keyof only return strings instead of string, numbers or symbols. Legacy option.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Make keyof only return strings instead of string, numbers or symbols. Legacy option.\n\nSee more: https://www.typescriptlang.org/tsconfig#keyofStringsOnly" }, "useDefineForClassFields": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Emit ECMAScript-standard-compliant class fields.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Emit ECMAScript-standard-compliant class fields.\n\nSee more: https://www.typescriptlang.org/tsconfig#useDefineForClassFields" }, "declarationMap": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Create sourcemaps for d.ts files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Create sourcemaps for d.ts files.\n\nSee more: https://www.typescriptlang.org/tsconfig#declarationMap" }, "resolveJsonModule": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable importing .json files", "type": ["boolean", "null"], "default": false, "markdownDescription": "Enable importing .json files\n\nSee more: https://www.typescriptlang.org/tsconfig#resolveJsonModule" }, "resolvePackageJsonExports": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Use the package.json 'exports' field when resolving package imports.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Use the package.json 'exports' field when resolving package imports.\n\nSee more: https://www.typescriptlang.org/tsconfig#resolvePackageJsonExports" }, "resolvePackageJsonImports": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Use the package.json 'imports' field when resolving imports.", "type": ["boolean", "null"], "default": false, @@ -1195,7 +1107,6 @@ "type": ["boolean", "null"] }, "extendedDiagnostics": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Output more detailed compiler performance information after building.", "type": ["boolean", "null"], "default": false, @@ -1206,46 +1117,39 @@ "type": ["boolean", "null"] }, "disableSourceOfProjectReferenceRedirect": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable preferring source files instead of declaration files when referencing composite projects", "type": ["boolean", "null"], "markdownDescription": "Disable preferring source files instead of declaration files when referencing composite projects\n\nSee more: https://www.typescriptlang.org/tsconfig#disableSourceOfProjectReferenceRedirect" }, "disableSolutionSearching": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Opt a project out of multi-project reference checking when editing.", "type": ["boolean", "null"], "markdownDescription": "Opt a project out of multi-project reference checking when editing.\n\nSee more: https://www.typescriptlang.org/tsconfig#disableSolutionSearching" }, "verbatimModuleSyntax": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting.", "type": ["boolean", "null"], "markdownDescription": "Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting.\n\nSee more: https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax" }, "noCheck": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Disable full type checking (only critical parse and emit errors will be reported)", "type": ["boolean", "null"], "default": false, "markdownDescription": "Disable full type checking (only critical parse and emit errors will be reported)\n\nSee more: https://www.typescriptlang.org/tsconfig#noCheck" }, "isolatedDeclarations": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Require sufficient annotation on exports so other tools can trivially generate declaration files.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Require sufficient annotation on exports so other tools can trivially generate declaration files.\n\nSee more: https://www.typescriptlang.org/tsconfig#isolatedDeclarations" }, "noUncheckedSideEffectImports": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Check side effect imports.", "type": ["boolean", "null"], "default": false, "markdownDescription": "Check side effect imports.\n\nSee more: https://www.typescriptlang.org/tsconfig#noUncheckedSideEffectImports" }, "strictBuiltinIteratorReturn": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'.", "type": ["boolean", "null"], "default": false, @@ -1258,18 +1162,15 @@ "typeAcquisitionDefinition": { "properties": { "typeAcquisition": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["object", "null"], "description": "Auto type (.d.ts) acquisition options for this project. Requires TypeScript version 2.1 or later.", "properties": { "enable": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable auto type acquisition", "type": ["boolean", "null"], "default": false }, "include": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specifies a list of type declarations to be included in auto type acquisition. Ex. [\"jquery\", \"lodash\"]", "type": ["array", "null"], "uniqueItems": true, @@ -1278,7 +1179,6 @@ } }, "exclude": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Specifies a list of type declarations to be excluded from auto type acquisition. Ex. [\"jquery\", \"lodash\"]", "type": ["array", "null"], "uniqueItems": true, @@ -1293,17 +1193,14 @@ "referencesDefinition": { "properties": { "references": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["array", "null"], "uniqueItems": true, "description": "Referenced projects. Requires TypeScript version 3.0 or later.", "items": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["object", "null"], "description": "Project reference.", "properties": { "path": { - "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "type": ["string", "null"], "description": "Path to referenced tsconfig or to folder containing tsconfig." } From e602cfadd3a27ccd6ed7c3ea865661fd9a978195 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 22 Sep 2025 15:35:44 -0400 Subject: [PATCH 187/721] Restore user-defined ordering of profiles (#38665) This PR fixes a regression where settings profiles were no longer ordered in the same order that the user defined in their settings. Release Notes: - N/A --- crates/settings/src/settings_content.rs | 4 +- .../src/settings_profile_selector.rs | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 38bff4d6a1428f017bcd65be3d27e945aebccabd..27c0976fb64dd4ecb473df937cf27f7a21ff3adc 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -16,7 +16,7 @@ pub use terminal::*; pub use theme::*; pub use workspace::*; -use collections::HashMap; +use collections::{HashMap, IndexMap}; use gpui::{App, SharedString}; use release_channel::ReleaseChannel; use schemars::JsonSchema; @@ -182,7 +182,7 @@ pub struct UserSettingsContent { pub linux: Option>, #[serde(default)] - pub profiles: HashMap, + pub profiles: IndexMap, } pub struct ExtensionsSettingsContent { diff --git a/crates/settings_profile_selector/src/settings_profile_selector.rs b/crates/settings_profile_selector/src/settings_profile_selector.rs index 0bba83beafb2ddc1c20767aefd15cc2095ca71ba..b2f01cf5c9a2b759eee3988762e43f07efc6952d 100644 --- a/crates/settings_profile_selector/src/settings_profile_selector.rs +++ b/crates/settings_profile_selector/src/settings_profile_selector.rs @@ -578,4 +578,42 @@ mod tests { assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 10.0); }); } + + #[gpui::test] + async fn test_settings_profile_selector_is_in_user_configuration_order( + cx: &mut TestAppContext, + ) { + // Must be unique names (HashMap) + let profiles_json = json!({ + "z": {}, + "e": {}, + "d": {}, + " ": {}, + "r": {}, + "u": {}, + "l": {}, + "3": {}, + "s": {}, + "!": {}, + }); + let (workspace, cx) = init_test(profiles_json.clone(), cx).await; + + cx.dispatch_action(settings_profile_selector::Toggle); + let picker = active_settings_profile_picker(&workspace, cx); + + picker.read_with(cx, |picker, _| { + assert_eq!(picker.delegate.matches.len(), 11); + assert_eq!(picker.delegate.matches[0].string, display_name(&None)); + assert_eq!(picker.delegate.matches[1].string, "z"); + assert_eq!(picker.delegate.matches[2].string, "e"); + assert_eq!(picker.delegate.matches[3].string, "d"); + assert_eq!(picker.delegate.matches[4].string, " "); + assert_eq!(picker.delegate.matches[5].string, "r"); + assert_eq!(picker.delegate.matches[6].string, "u"); + assert_eq!(picker.delegate.matches[7].string, "l"); + assert_eq!(picker.delegate.matches[8].string, "3"); + assert_eq!(picker.delegate.matches[9].string, "s"); + assert_eq!(picker.delegate.matches[10].string, "!"); + }); + } } From 80dcabe95c8b708ed0057299df5ad135e54925fc Mon Sep 17 00:00:00 2001 From: Nia Date: Mon, 22 Sep 2025 22:37:51 +0200 Subject: [PATCH 188/721] perf: Better docs, internal refactors (#38664) Release Notes: - N/A --- .cargo/config.toml | 3 +- tooling/perf/Cargo.toml | 3 +- tooling/perf/src/lib.rs | 84 +++++--- tooling/perf/src/main.rs | 418 ++++++++++++++++++++++----------------- 4 files changed, 300 insertions(+), 208 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da793fc48b62f7f03cd1d36a505fa1e1ef2a45a..f4e1d6f79c810205d71531fef5e56401f7e0d095 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,7 +4,7 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"] [alias] xtask = "run --package xtask --" -perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--config", "target.'cfg(true)'.runner='target/release/perf'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"] +perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--config", "target.'cfg(true)'.runner='cargo run -p perf --release'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"] perf-compare = ["run", "--release", "-p", "perf", "--", "compare"] [target.x86_64-unknown-linux-gnu] @@ -25,4 +25,3 @@ rustflags = [ [env] MACOSX_DEPLOYMENT_TARGET = "10.15.7" -CARGO_WORKSPACE_DIR = { value = "", relative = true } diff --git a/tooling/perf/Cargo.toml b/tooling/perf/Cargo.toml index 4766b58d8a760aa995dba7092d33c436559019c2..2b5ae6571ac45a3540860e66d0956fb9e0b05a72 100644 --- a/tooling/perf/Cargo.toml +++ b/tooling/perf/Cargo.toml @@ -18,7 +18,8 @@ pedantic = "warn" style = "warn" missing_docs_in_private_items = "warn" as_underscore = "deny" -allow_attributes_without_reason = "deny" +allow_attributes = "deny" +allow_attributes_without_reason = "deny" # This covers `expect` also, since we deny `allow` let_underscore_must_use = "forbid" undocumented_unsafe_blocks = "forbid" missing_safety_doc = "forbid" diff --git a/tooling/perf/src/lib.rs b/tooling/perf/src/lib.rs index 30909f646b061895e10f5c860149e2370892ccd2..18fc2984a6951189be1afc2a1bb76950a4a838a4 100644 --- a/tooling/perf/src/lib.rs +++ b/tooling/perf/src/lib.rs @@ -3,7 +3,7 @@ use collections::HashMap; use serde::{Deserialize, Serialize}; -use std::time::Duration; +use std::{num::NonZero, time::Duration}; pub mod consts { //! Preset idenitifiers and constants so that the profiler and proc macro agree @@ -80,6 +80,8 @@ pub enum FailKind { Profile, /// Failed due to an incompatible version for the test. VersionMismatch, + /// Could not parse metadata for a test. + BadMetadata, /// Skipped due to filters applied on the perf run. Skipped, } @@ -87,9 +89,10 @@ pub enum FailKind { impl std::fmt::Display for FailKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - FailKind::Triage => f.write_str("failed in triage"), - FailKind::Profile => f.write_str("failed while profiling"), + FailKind::Triage => f.write_str("errored in triage"), + FailKind::Profile => f.write_str("errored while profiling"), FailKind::VersionMismatch => f.write_str("test version mismatch"), + FailKind::BadMetadata => f.write_str("bad test metadata"), FailKind::Skipped => f.write_str("skipped"), } } @@ -108,8 +111,9 @@ pub struct TestMdata { /// INVARIANT: If `version` <= `MDATA_VER`, this tool *must* be able to /// correctly parse the output of this test. pub version: u32, - /// How many iterations to pass this test, if this is preset. - pub iterations: Option, + /// How many iterations to pass this test if this is preset, or how many + /// iterations a test ended up running afterwards if determined at runtime. + pub iterations: Option>, /// The importance of this particular test. See the docs on `Importance` for /// details. pub importance: Importance, @@ -134,16 +138,20 @@ impl Timings { reason = "We only care about a couple sig figs anyways" )] #[must_use] - pub fn iters_per_sec(&self, total_iters: usize) -> f64 { - (1000. / self.mean.as_millis() as f64) * total_iters as f64 + pub fn iters_per_sec(&self, total_iters: NonZero) -> f64 { + (1000. / self.mean.as_millis() as f64) * total_iters.get() as f64 } } +/// Aggregate results, meant to be used for a given importance category. Each +/// test name corresponds to its benchmark results, iteration count, and weight. +type CategoryInfo = HashMap, u8)>; + /// Aggregate output of all tests run by this handler. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Output { - /// A list of test outputs. Format is `(test_name, iter_count, timings)`. - /// The latter being set indicates the test succeeded. + /// A list of test outputs. Format is `(test_name, mdata, timings)`. + /// The latter being `Ok(_)` indicates the test succeeded. /// /// INVARIANT: If the test succeeded, the second field is `Some(mdata)` and /// `mdata.iterations` is `Some(_)`. @@ -162,7 +170,7 @@ impl Output { &mut self, name: impl AsRef, mut mdata: TestMdata, - iters: usize, + iters: NonZero, timings: Timings, ) { mdata.iterations = Some(iters); @@ -179,7 +187,7 @@ impl Output { &mut self, name: impl AsRef, mut mdata: Option, - attempted_iters: Option, + attempted_iters: Option>, kind: FailKind, ) { if let Some(ref mut mdata) = mdata { @@ -195,7 +203,7 @@ impl Output { self.tests.is_empty() } - /// Sorts the runs in the output in the order that we want it printed. + /// Sorts the runs in the output in the order that we want them printed. pub fn sort(&mut self) { self.tests.sort_unstable_by(|a, b| match (a, b) { // Tests where we got no metadata go at the end. @@ -218,16 +226,20 @@ impl Output { /// Merges the output of two runs, appending a prefix to the results of the new run. /// To be used in conjunction with `Output::blank()`, or else only some tests will have /// a prefix set. - pub fn merge(&mut self, other: Self, pref_other: impl AsRef) { + pub fn merge<'a>(&mut self, other: Self, pref_other: impl Into>) { + let pref = if let Some(pref) = pref_other.into() { + "crates/".to_string() + pref + "::" + } else { + String::new() + }; self.tests = std::mem::take(&mut self.tests) .into_iter() - .chain(other.tests.into_iter().map(|(name, md, tm)| { - let mut new_name = "crates/".to_string(); - new_name.push_str(pref_other.as_ref()); - new_name.push_str("::"); - new_name.push_str(&name); - (new_name, md, tm) - })) + .chain( + other + .tests + .into_iter() + .map(|(name, md, tm)| (pref.clone() + &name, md, tm)), + ) .collect(); } @@ -273,8 +285,8 @@ impl Output { r_total_denominator += u32::from(weight); } let mean = r_total_numerator / f64::from(r_total_denominator); - // TODO: also aggregate standard deviation? that's harder to keep - // meaningful, though, since we dk which tests are correlated + // TODO: also aggregate standard deviation? That's harder to keep + // meaningful, though, since we dk which tests are correlated. Some((cat, PerfDelta { max, mean, min })) }) .collect(); @@ -282,9 +294,9 @@ impl Output { PerfReport { deltas } } - /// Collapses the `PerfReport` into a `HashMap` of `Importance` <-> tests - /// each represented as a map of `name, (Timings, iterations, weight)`. - fn collapse(self) -> HashMap> { + /// Collapses the `PerfReport` into a `HashMap` over `Importance`, with + /// each importance category having its tests contained. + fn collapse(self) -> HashMap { let mut categories = HashMap::>::default(); for entry in self.tests { if let Some(mdata) = entry.1 @@ -402,10 +414,28 @@ impl std::fmt::Display for PerfReport { // a little jankily like this. write!(f, "|:---|---:|---:|---:|")?; for (cat, delta) in sorted.into_iter().rev() { + const SIGN_POS: &str = "↑"; + const SIGN_NEG: &str = "↓"; + const SIGN_NEUTRAL: &str = "±"; + + let prettify = |time: f64| { + let sign = if time > 0.05 { + SIGN_POS + } else if time < 0.05 && time > -0.05 { + SIGN_NEUTRAL + } else { + SIGN_NEG + }; + format!("{} {:.1}%", sign, time.abs() * 100.) + }; + + // Pretty-print these instead of just using the float display impl. write!( f, - "\n| {cat} | {:.3} | {:.3} | {:.3} |", - delta.max, delta.mean, delta.min + "\n| {cat} | {} | {} | {} |", + prettify(delta.max), + prettify(delta.mean), + prettify(delta.min) )?; } Ok(()) diff --git a/tooling/perf/src/main.rs b/tooling/perf/src/main.rs index 2610adc66f88dfa675df975219f5b2937011e81b..b960d2dce60023b677c7f6cde12e36a0d66d88ae 100644 --- a/tooling/perf/src/main.rs +++ b/tooling/perf/src/main.rs @@ -3,8 +3,7 @@ //! for usage details on the actual attribute. //! //! # Setup -//! Make sure `hyperfine` is installed and in the shell path, then run -//! `cargo build -p perf --release` to build the profiler. +//! Make sure `hyperfine` is installed and in the shell path. //! //! # Usage //! Calling this tool rebuilds the targeted crate(s) with some cfg flags set for the @@ -44,21 +43,25 @@ //! This should probably not be called manually unless you're working on the profiler //! itself; use the `cargo perf-test` alias (after building this crate) instead. -#[allow(clippy::wildcard_imports, reason = "Our crate")] -use perf::*; +use perf::{FailKind, Importance, Output, TestMdata, Timings, consts}; use std::{ fs::OpenOptions, io::Write, + num::NonZero, path::{Path, PathBuf}, process::{Command, Stdio}, + sync::atomic::{AtomicBool, Ordering}, time::{Duration, Instant}, }; /// How many iterations to attempt the first time a test is run. -const DEFAULT_ITER_COUNT: usize = 3; +const DEFAULT_ITER_COUNT: NonZero = NonZero::new(3).unwrap(); /// Multiplier for the iteration count when a test doesn't pass the noise cutoff. -const ITER_COUNT_MUL: usize = 4; +const ITER_COUNT_MUL: NonZero = NonZero::new(4).unwrap(); + +/// Do we keep stderr empty while running the tests? +static QUIET: AtomicBool = AtomicBool::new(false); /// Report a failure into the output and skip an iteration. macro_rules! fail { @@ -84,14 +87,59 @@ enum OutputKind<'a> { Json(&'a Path), } +impl OutputKind<'_> { + /// Logs the output of a run as per the `OutputKind`. + fn log(&self, output: &Output, t_bin: &str) { + match self { + OutputKind::Markdown => print!("{output}"), + OutputKind::Json(ident) => { + let wspace_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let runs_dir = PathBuf::from(&wspace_dir).join(consts::RUNS_DIR); + std::fs::create_dir_all(&runs_dir).unwrap(); + assert!( + !ident.to_string_lossy().is_empty(), + "FATAL: Empty filename specified!" + ); + // Get the test binary's crate's name; a path like + // target/release-fast/deps/gpui-061ff76c9b7af5d7 + // would be reduced to just "gpui". + let test_bin_stripped = Path::new(t_bin) + .file_name() + .unwrap() + .to_str() + .unwrap() + .rsplit_once('-') + .unwrap() + .0; + let mut file_path = runs_dir.join(ident); + file_path + .as_mut_os_string() + .push(format!(".{test_bin_stripped}.json")); + let mut out_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&file_path) + .unwrap(); + out_file + .write_all(&serde_json::to_vec(&output).unwrap()) + .unwrap(); + if !QUIET.load(Ordering::Relaxed) { + eprintln!("JSON output written to {}", file_path.display()); + } + } + } + } +} + /// Runs a given metadata-returning function from a test handler, parsing its /// output into a `TestMdata`. -fn parse_mdata(test_bin: &str, mdata_fn: &str) -> Result { - let mut cmd = Command::new(test_bin); +fn parse_mdata(t_bin: &str, mdata_fn: &str) -> Result { + let mut cmd = Command::new(t_bin); cmd.args([mdata_fn, "--exact", "--nocapture"]); let out = cmd .output() - .expect("FATAL: Could not run test binary {test_bin}"); + .expect("FATAL: Could not run test binary {t_bin}"); assert!(out.status.success()); let stdout = String::from_utf8_lossy(&out.stdout); let mut version = None; @@ -104,36 +152,53 @@ fn parse_mdata(test_bin: &str, mdata_fn: &str) -> Result { { let mut items = line.split_whitespace(); // For v0, we know the ident always comes first, then one field. - match items.next().unwrap() { + match items.next().ok_or(FailKind::BadMetadata)? { consts::VERSION_LINE_NAME => { - let v = items.next().unwrap().parse::().unwrap(); + let v = items + .next() + .ok_or(FailKind::BadMetadata)? + .parse::() + .map_err(|_| FailKind::BadMetadata)?; if v > consts::MDATA_VER { return Err(FailKind::VersionMismatch); } version = Some(v); } consts::ITER_COUNT_LINE_NAME => { - iterations = Some(items.next().unwrap().parse::().unwrap()); + // This should never be zero! + iterations = Some( + items + .next() + .ok_or(FailKind::BadMetadata)? + .parse::() + .map_err(|_| FailKind::BadMetadata)? + .try_into() + .map_err(|_| FailKind::BadMetadata)?, + ); } consts::IMPORTANCE_LINE_NAME => { - importance = match items.next().unwrap() { + importance = match items.next().ok_or(FailKind::BadMetadata)? { "critical" => Importance::Critical, "important" => Importance::Important, "average" => Importance::Average, "iffy" => Importance::Iffy, "fluff" => Importance::Fluff, - _ => unreachable!(), + _ => return Err(FailKind::BadMetadata), }; } consts::WEIGHT_LINE_NAME => { - weight = items.next().unwrap().parse::().unwrap(); + weight = items + .next() + .ok_or(FailKind::BadMetadata)? + .parse::() + .map_err(|_| FailKind::BadMetadata)?; } _ => unreachable!(), } } Ok(TestMdata { - version: version.unwrap(), + version: version.ok_or(FailKind::BadMetadata)?, // Iterations may be determined by us and thus left unspecified. iterations, // In principle this should always be set, but just for the sake of @@ -150,7 +215,7 @@ fn compare_profiles(args: &[String]) { let ident_new = args.first().expect("FATAL: missing identifier for new run"); let ident_old = args.get(1).expect("FATAL: missing identifier for old run"); // TODO: move this to a constant also tbh - let wspace_dir = std::env::var("CARGO_WORKSPACE_DIR").unwrap(); + let wspace_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let runs_dir = PathBuf::from(&wspace_dir).join(consts::RUNS_DIR); // Use the blank outputs initially, so we can merge into these with prefixes. @@ -193,59 +258,22 @@ fn compare_profiles(args: &[String]) { println!("{res}"); } -#[expect(clippy::too_many_lines, reason = "This will be split up soon!")] -fn main() { - let args = std::env::args().collect::>(); - // We get passed the test we need to run as the 1st argument after our own name. - let test_bin = args - .get(1) - .expect("FATAL: No test binary or command; this shouldn't be manually invoked!"); - - // We're being asked to compare two results, not run the profiler. - if test_bin == "compare" { - compare_profiles(&args[2..]); - return; - } - - // Whether to skip printing some information to stderr. - let mut quiet = false; - // Minimum test importance we care about this run. - let mut thresh = Importance::Iffy; - // Where to print the output of this run. - let mut out_kind = OutputKind::Markdown; - - for arg in args.iter().skip(2) { - match arg.as_str() { - "--critical" => thresh = Importance::Critical, - "--important" => thresh = Importance::Important, - "--average" => thresh = Importance::Average, - "--iffy" => thresh = Importance::Iffy, - "--fluff" => thresh = Importance::Fluff, - "--quiet" => quiet = true, - s if s.starts_with("--json") => { - out_kind = OutputKind::Json(Path::new( - s.strip_prefix("--json=") - .expect("FATAL: Invalid json parameter; pass --json=filename"), - )); - } - _ => (), - } - } - if !quiet { - eprintln!("Starting perf check"); - } - - let mut cmd = Command::new(test_bin); +/// Runs a test binary, filtering out tests which aren't marked for perf triage +/// and giving back the list of tests we care about. +/// +/// The output of this is an iterator over `test_fn_name, test_mdata_name`. +fn get_tests(t_bin: &str) -> impl ExactSizeIterator { + let mut cmd = Command::new(t_bin); // --format=json is nightly-only :( cmd.args(["--list", "--format=terse"]); let out = cmd .output() - .expect("FATAL: Could not run test binary {test_bin}"); + .expect("FATAL: Could not run test binary {t_bin}"); assert!( out.status.success(), - "FATAL: Cannot do perf check - test binary {test_bin} returned an error" + "FATAL: Cannot do perf check - test binary {t_bin} returned an error" ); - if !quiet { + if !QUIET.load(Ordering::Relaxed) { eprintln!("Test binary ran successfully; starting profile..."); } // Parse the test harness output to look for tests we care about. @@ -273,34 +301,156 @@ fn main() { test_list.sort_unstable(); test_list.dedup(); - let len = test_list.len(); - // Tests should come in pairs with their mdata fn! assert!( - len.is_multiple_of(2), - "Malformed tests in test binary {test_bin}" + test_list.len().is_multiple_of(2), + "Malformed tests in test binary {t_bin}" ); + let out = test_list + .chunks_exact_mut(2) + .map(|pair| { + // Be resilient against changes to these constants. + if consts::SUF_NORMAL < consts::SUF_MDATA { + (pair[0].to_owned(), pair[1].to_owned()) + } else { + (pair[1].to_owned(), pair[0].to_owned()) + } + }) + .collect::>(); + out.into_iter() +} + +/// Triage a test to determine the correct number of iterations that it should run. +/// Specifically, repeatedly runs the given test until its execution time exceeds +/// `thresh`, calling `step(iterations)` after every failed run to determine the new +/// iteration count. Returns `None` if the test errored or `step` returned `None`, +/// else `Some(iterations)`. +/// +/// # Panics +/// This will panic if `step(usize)` is not monotonically increasing. +fn triage_test( + t_bin: &str, + t_name: &str, + thresh: Duration, + mut step: impl FnMut(NonZero) -> Option>, +) -> Option> { + let mut iter_count = DEFAULT_ITER_COUNT; + loop { + let mut cmd = Command::new(t_bin); + cmd.args([t_name, "--exact"]); + cmd.env(consts::ITER_ENV_VAR, format!("{iter_count}")); + // Don't let the child muck up our stdin/out/err. + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::null()); + cmd.stderr(Stdio::null()); + let pre = Instant::now(); + // Discard the output beyond ensuring success. + let out = cmd.spawn().unwrap().wait(); + let post = Instant::now(); + if !out.unwrap().success() { + break None; + } + if post - pre > thresh { + break Some(iter_count); + } + let new = step(iter_count)?; + assert!( + new > iter_count, + "FATAL: step must be monotonically increasing" + ); + iter_count = new; + } +} + +/// Profiles a given test with hyperfine, returning the mean and standard deviation +/// for its runtime. If the test errors, returns `None` instead. +fn hyp_profile(t_bin: &str, t_name: &str, iterations: NonZero) -> Option { + let mut perf_cmd = Command::new("hyperfine"); + // Warm up the cache and print markdown output to stdout, which we parse. + perf_cmd.args([ + "--style", + "none", + "--warmup", + "1", + "--export-markdown", + "-", + &format!("{t_bin} {t_name}"), + ]); + perf_cmd.env(consts::ITER_ENV_VAR, format!("{iterations}")); + let p_out = perf_cmd.output().unwrap(); + if !p_out.status.success() { + return None; + } + + let cmd_output = String::from_utf8_lossy(&p_out.stdout); + // Can't use .last() since we have a trailing newline. Sigh. + let results_line = cmd_output.lines().nth(3).unwrap(); + // Grab the values out of the pretty-print. + // TODO: Parse json instead. + let mut res_iter = results_line.split_whitespace(); + // Durations are given in milliseconds, so account for that. + let mean = Duration::from_secs_f64(res_iter.nth(4).unwrap().parse::().unwrap() / 1000.); + let stddev = Duration::from_secs_f64(res_iter.nth(1).unwrap().parse::().unwrap() / 1000.); + + Some(Timings { mean, stddev }) +} + +fn main() { + let args = std::env::args().collect::>(); + // We get passed the test we need to run as the 1st argument after our own name. + let t_bin = args + .get(1) + .expect("FATAL: No test binary or command; this shouldn't be manually invoked!"); + + // We're being asked to compare two results, not run the profiler. + if t_bin == "compare" { + compare_profiles(&args[2..]); + return; + } + + // Minimum test importance we care about this run. + let mut thresh = Importance::Iffy; + // Where to print the output of this run. + let mut out_kind = OutputKind::Markdown; + + for arg in args.iter().skip(2) { + match arg.as_str() { + "--critical" => thresh = Importance::Critical, + "--important" => thresh = Importance::Important, + "--average" => thresh = Importance::Average, + "--iffy" => thresh = Importance::Iffy, + "--fluff" => thresh = Importance::Fluff, + "--quiet" => QUIET.store(true, Ordering::Relaxed), + s if s.starts_with("--json") => { + out_kind = OutputKind::Json(Path::new( + s.strip_prefix("--json=") + .expect("FATAL: Invalid json parameter; pass --json=ident"), + )); + } + _ => (), + } + } + if !QUIET.load(Ordering::Relaxed) { + eprintln!("Starting perf check"); + } + let mut output = Output::default(); // Spawn and profile an instance of each perf-sensitive test, via hyperfine. // Each test is a pair of (test, metadata-returning-fn), so grab both. We also // know the list is sorted. - for (idx, t_pair) in test_list.chunks_exact(2).enumerate() { - if !quiet { - eprint!("\rProfiling test {}/{}", idx + 1, len / 2); + let i = get_tests(t_bin); + let len = i.len(); + for (idx, (ref t_name, ref t_mdata)) in i.enumerate() { + if !QUIET.load(Ordering::Relaxed) { + eprint!("\rProfiling test {}/{}", idx + 1, len); } - // Be resilient against changes to these constants. - let (t_name, t_mdata) = if consts::SUF_NORMAL < consts::SUF_MDATA { - (t_pair[0], t_pair[1]) - } else { - (t_pair[1], t_pair[0]) - }; // Pretty-printable stripped name for the test. let t_name_pretty = t_name.replace(consts::SUF_NORMAL, ""); // Get the metadata this test reports for us. - let t_mdata = match parse_mdata(test_bin, t_mdata) { + let t_mdata = match parse_mdata(t_bin, t_mdata) { Ok(mdata) => mdata, Err(err) => fail!(output, t_name_pretty, err), }; @@ -312,78 +462,28 @@ fn main() { // Time test execution to see how many iterations we need to do in order // to account for random noise. This is skipped for tests with fixed // iteration counts. - let mut errored = false; - let final_iter_count = t_mdata.iterations.unwrap_or_else(|| { - let mut iter_count = DEFAULT_ITER_COUNT; - loop { - let mut cmd = Command::new(test_bin); - cmd.args([t_name, "--exact"]); - cmd.env(consts::ITER_ENV_VAR, format!("{iter_count}")); - // Don't let the child muck up our stdin/out/err. - cmd.stdin(Stdio::null()); - cmd.stdout(Stdio::null()); - cmd.stderr(Stdio::null()); - let pre = Instant::now(); - // Discard the output beyond ensuring success. - let out = cmd.spawn().unwrap().wait(); - let post = Instant::now(); - if !out.unwrap().success() { - errored = true; - break iter_count; - } - if post - pre > consts::NOISE_CUTOFF { - break iter_count; - } else if let Some(c) = iter_count.checked_mul(ITER_COUNT_MUL) { - iter_count = c; + let final_iter_count = t_mdata.iterations.or_else(|| { + triage_test(t_bin, t_name, consts::NOISE_CUTOFF, |c| { + if let Some(c) = c.checked_mul(ITER_COUNT_MUL) { + Some(c) } else { // This should almost never happen, but maybe..? eprintln!( - "WARNING: Running nearly usize::MAX iterations of test {t_name_pretty}" + "WARNING: Ran nearly usize::MAX iterations of test {t_name_pretty}; skipping" ); - break iter_count; + None } - } + }) }); // Don't profile failing tests. - if errored { + let Some(final_iter_count) = final_iter_count else { fail!(output, t_name_pretty, t_mdata, FailKind::Triage); - } + }; // Now profile! - let mut perf_cmd = Command::new("hyperfine"); - // Warm up the cache and print markdown output to stdout. - // TODO: json - perf_cmd.args([ - "--style", - "none", - "--warmup", - "1", - "--export-markdown", - "-", - &format!("{test_bin} {t_name}"), - ]); - perf_cmd.env(consts::ITER_ENV_VAR, format!("{final_iter_count}")); - let p_out = perf_cmd.output().unwrap(); - if p_out.status.success() { - let cmd_output = String::from_utf8_lossy(&p_out.stdout); - // Can't use .last() since we have a trailing newline. Sigh. - let results_line = cmd_output.lines().nth(3).unwrap(); - // Grab the values out of the pretty-print. - // TODO: Parse json instead. - let mut res_iter = results_line.split_whitespace(); - // Durations are given in milliseconds, so account for that. - let mean = - Duration::from_secs_f64(res_iter.nth(4).unwrap().parse::().unwrap() / 1000.); - let stddev = - Duration::from_secs_f64(res_iter.nth(1).unwrap().parse::().unwrap() / 1000.); - - output.success( - t_name_pretty, - t_mdata, - final_iter_count, - Timings { mean, stddev }, - ); + if let Some(timings) = hyp_profile(t_bin, t_name, final_iter_count) { + output.success(t_name_pretty, t_mdata, final_iter_count, timings); } else { fail!( output, @@ -394,7 +494,7 @@ fn main() { ); } } - if !quiet { + if !QUIET.load(Ordering::Relaxed) { if output.is_empty() { eprintln!("Nothing to do."); } else { @@ -409,43 +509,5 @@ fn main() { return; } - match out_kind { - OutputKind::Markdown => print!("{output}"), - OutputKind::Json(user_path) => { - let wspace_dir = std::env::var("CARGO_WORKSPACE_DIR").unwrap(); - let runs_dir = PathBuf::from(&wspace_dir).join(consts::RUNS_DIR); - std::fs::create_dir_all(&runs_dir).unwrap(); - assert!( - !user_path.to_string_lossy().is_empty(), - "FATAL: Empty filename specified!" - ); - // Get the test binary's crate's name; a path like - // target/release-fast/deps/gpui-061ff76c9b7af5d7 - // would be reduced to just "gpui". - let test_bin_stripped = Path::new(test_bin) - .file_name() - .unwrap() - .to_str() - .unwrap() - .rsplit_once('-') - .unwrap() - .0; - let mut file_path = runs_dir.join(user_path); - file_path - .as_mut_os_string() - .push(format!(".{test_bin_stripped}.json")); - let mut out_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&file_path) - .unwrap(); - out_file - .write_all(&serde_json::to_vec(&output).unwrap()) - .unwrap(); - if !quiet { - eprintln!("JSON output written to {}", file_path.display()); - } - } - } + out_kind.log(&output, t_bin); } From e484f49ee8a3c6d9cd390beb65fdff57f05bac7d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 22 Sep 2025 16:40:56 -0400 Subject: [PATCH 189/721] language_models: Treat a `block_reason` from Gemini as a refusal (#38670) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR updates the Gemini provider to treat a `prompt_feedback.block_reason` as a refusal, as Gemini does not seem to return a `stop_reason` to use in this case. Screenshot 2025-09-22 at 4 23 15 PM Previously this would just result in no feedback to the user. Release Notes: - Added an error message when a Gemini response contains a `block_reason`. --- crates/language_models/src/provider/google.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index 70a7a27defdfba609765710902845f921a8333ac..48712e33a7afe4f1b669b551e66423a8c8b2c995 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -612,6 +612,24 @@ impl GoogleEventMapper { convert_usage(&self.usage), ))) } + + if let Some(prompt_feedback) = event.prompt_feedback + && let Some(block_reason) = prompt_feedback.block_reason.as_deref() + { + self.stop_reason = match block_reason { + "SAFETY" | "OTHER" | "BLOCKLIST" | "PROHIBITED_CONTENT" | "IMAGE_SAFETY" => { + StopReason::Refusal + } + _ => { + log::error!("Unexpected Google block_reason: {block_reason}"); + StopReason::Refusal + } + }; + events.push(Ok(LanguageModelCompletionEvent::Stop(self.stop_reason))); + + return events; + } + if let Some(candidates) = event.candidates { for candidate in candidates { if let Some(finish_reason) = candidate.finish_reason.as_deref() { From 46d19d8a47ca059913c2fcb1a06c6543a62b434c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 22 Sep 2025 23:03:37 +0200 Subject: [PATCH 190/721] helix: Fix helix-paste mode in line mode (#38663) In particular, * if the selection ends at the beginning of the next line, and the current line under the cursor is empty, we paste at the selection's end. * if however the current line under the cursor is empty, we need to move to the beginning of the next line to avoid pasting above the end of current selection In addition, in line mode, we always move the cursor to the end of the inserted text. Otherwise, while it looks fine visually, inserting/appending ends up in the next logical line which is not desirable. Release Notes: - N/A --- crates/vim/src/helix/paste.rs | 52 +++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/crates/vim/src/helix/paste.rs b/crates/vim/src/helix/paste.rs index ecfdaa499257ad91d8518f488be9a4d4dbb51f1c..957d459dac50892e8173f4f1ac12459277b6d6ae 100644 --- a/crates/vim/src/helix/paste.rs +++ b/crates/vim/src/helix/paste.rs @@ -84,13 +84,22 @@ impl Vim { let display_point = if line_mode { if action.before { movement::line_beginning(&display_map, sel.start, false) - } else if sel.end.column() == 0 { + } else if sel.start.column() > 0 + && sel.end.column() == 0 + && sel.start != sel.end + { sel.end } else { - movement::right( - &display_map, - movement::line_end(&display_map, sel.end, false), - ) + let point = movement::line_end(&display_map, sel.end, false); + if sel.end.column() == 0 && point.column() > 0 { + // If the selection ends at the beginning of the next line, and the current line + // under the cursor is not empty, we paste at the selection's end. + sel.end + } else { + // If however the current line under the cursor is empty, we need to move + // to the beginning of the next line to avoid pasting above the end of current selection. + movement::right(&display_map, point) + } } } else if action.before { sel.start @@ -123,6 +132,12 @@ impl Vim { let offset = anchor.to_offset(&snapshot); if action.before { offset.saturating_sub(len)..offset + } else if line_mode { + // In line mode, we always move the cursor to the end of the inserted text. + // Otherwise, while it looks fine visually, inserting/appending ends up + // in the next logical line which is not desirable. + debug_assert!(len > 0); + offset..(offset + len - 1) } else { offset..(offset + len) } @@ -386,8 +401,8 @@ mod test { indoc! {" The quick brown fox jumps over - «n - ˇ»the lazy dog."}, + «nˇ» + the lazy dog."}, Mode::HelixNormal, ); @@ -405,8 +420,27 @@ mod test { indoc! {" The quick brown fox jumps over - «n - ˇ»the lazy dog."}, + «nˇ» + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.set_state( + indoc! {" + + The quick brown + fox jumps overˇ + the lazy dog."}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("x y up up p"); + cx.assert_state( + indoc! {" + + «fox jumps overˇ» + The quick brown + fox jumps over + the lazy dog."}, Mode::HelixNormal, ); } From a90abb1009f0c99c7ee8cd42d4a236dda53b45c7 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:36:10 +0200 Subject: [PATCH 191/721] Bump Rust to 1.90 (#38436) Release Notes: - N/A --------- Co-authored-by: Nia Espera Co-authored-by: Julia Ryan --- Cargo.lock | 1 - Dockerfile-collab | 2 +- crates/collab/src/db/queries/channels.rs | 5 --- crates/command_palette/src/persistence.rs | 5 ++- crates/context_server/src/listener.rs | 10 ------ .../src/wasm_host/wit/since_v0_0_6.rs | 1 + .../src/wasm_host/wit/since_v0_2_0.rs | 1 + .../src/wasm_host/wit/since_v0_3_0.rs | 1 + .../src/wasm_host/wit/since_v0_4_0.rs | 1 + .../src/wasm_host/wit/since_v0_5_0.rs | 1 + .../src/wasm_host/wit/since_v0_6_0.rs | 1 + crates/git_ui/src/git_panel_settings.rs | 8 ----- crates/gpui/examples/data_table.rs | 2 +- crates/gpui/src/text_system/line_layout.rs | 6 ++-- crates/project_panel/src/project_panel.rs | 6 ++-- crates/task/src/vscode_debug_format.rs | 7 ----- crates/theme/Cargo.toml | 1 - crates/theme/src/schema.rs | 26 ---------------- crates/theme_importer/src/main.rs | 31 ------------------- crates/vim/src/object.rs | 4 +-- flake.lock | 18 +++++------ rust-toolchain.toml | 2 +- script/generate-licenses | 2 +- script/generate-licenses-csv | 2 +- script/generate-licenses.ps1 | 2 +- 25 files changed, 33 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e51968b0262a91d3a1ed78a10656e75b9d0d4523..450d59e73b6ce48f86abe17d6bd27df98f1e7df4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17195,7 +17195,6 @@ dependencies = [ "fs", "futures 0.3.31", "gpui", - "indexmap 2.9.0", "log", "palette", "parking_lot", diff --git a/Dockerfile-collab b/Dockerfile-collab index c1621d6ee67e42117315ea49eac99f6f6260f4b7..a85fe93f198475534cb7396abe594f9d02eeb57b 100644 --- a/Dockerfile-collab +++ b/Dockerfile-collab @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.2 -FROM rust:1.89-bookworm as builder +FROM rust:1.90-bookworm as builder WORKDIR app COPY . . diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 4bb82865e73968e2861777d5cd0f700675366e81..3f5c8b6a885f4fa47bf868e3a2c564cc2067428e 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1129,8 +1129,3 @@ async fn max_order(parent_path: &str, tx: &TransactionHandle) -> Result { enum QueryIds { Id, } - -#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] -enum QueryUserIds { - UserId, -} diff --git a/crates/command_palette/src/persistence.rs b/crates/command_palette/src/persistence.rs index 01cf403083b2de4ed7919801ab33e4aae947007e..feaed72570d56f4895ff05eef891fc81c2e5e0b6 100644 --- a/crates/command_palette/src/persistence.rs +++ b/crates/command_palette/src/persistence.rs @@ -10,6 +10,7 @@ use db::{ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; +#[cfg(test)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub(crate) struct SerializedCommandInvocation { pub(crate) command_name: String, @@ -39,6 +40,7 @@ impl Column for SerializedCommandUsage { } } +#[cfg(test)] impl Column for SerializedCommandInvocation { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let (command_name, next_index): (String, i32) = Column::column(statement, start_index)?; @@ -84,8 +86,9 @@ impl CommandPaletteDB { .await } + #[cfg(test)] query! { - pub fn get_last_invoked(command: &str) -> Result> { + pub(crate) fn get_last_invoked(command: &str) -> Result> { SELECT command_name, user_query, diff --git a/crates/context_server/src/listener.rs b/crates/context_server/src/listener.rs index 4e5da2566ee25ee70e1687cf5f0806e19789a824..b71d59d760242d7f927e35dc1fef2351b462af32 100644 --- a/crates/context_server/src/listener.rs +++ b/crates/context_server/src/listener.rs @@ -438,13 +438,3 @@ struct RawRequest { #[serde(skip_serializing_if = "Option::is_none")] params: Option>, } - -#[derive(Serialize, Deserialize)] -struct RawResponse { - jsonrpc: &'static str, - id: RequestId, - #[serde(skip_serializing_if = "Option::is_none")] - error: Option, - #[serde(skip_serializing_if = "Option::is_none")] - result: Option>, -} diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs b/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs index 084c24f2ec2d5c0071e894acf1a4a1050ed14c40..2fc29abadb2eb60d051b37e072727931aee72d69 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs @@ -23,6 +23,7 @@ wasmtime::component::bindgen!({ }); mod settings { + #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/since_v0.0.6/settings.rs")); } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs index c6c3a8475f4b230947e03285e851c332fcdef7f6..9475438b660d2e126ae6ca24d276795d51d4ce8b 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs @@ -30,6 +30,7 @@ wasmtime::component::bindgen!({ pub use self::zed::extension::*; mod settings { + #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/since_v0.2.0/settings.rs")); } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs index a2d02cc07a2496e13b9d344ee339a0a4f1e1e124..b6a75ba7dda6ded2e074a2ece35b4b3f881f1619 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs @@ -30,6 +30,7 @@ wasmtime::component::bindgen!({ }); mod settings { + #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/since_v0.3.0/settings.rs")); } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs index 4e2650bba7d4b44557f091e697bf8f25a9403fa2..7c8be1322f94e35ded911d64e13f5afb4bf3702c 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs @@ -30,6 +30,7 @@ wasmtime::component::bindgen!({ }); mod settings { + #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/since_v0.4.0/settings.rs")); } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs index bb73d77f7f0470a0b700e87931be9cac2bb51a86..6d04663de7772e9c965cf1b88840727cfdcb4b59 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs @@ -31,6 +31,7 @@ wasmtime::component::bindgen!({ }); mod settings { + #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/since_v0.5.0/settings.rs")); } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index be4f3ca71a3f392965488bd2d30eab556d8fb300..790a75e896dc0a440bc27d8972c09b879020e9c2 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -52,6 +52,7 @@ wasmtime::component::bindgen!({ pub use self::zed::extension::*; mod settings { + #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/since_v0.6.0/settings.rs")); } diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index b137988539510a3d3242656bd1f6cc6d85a07703..342b0105cd5f92b8228572391cd4ddac7256a7a7 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -9,14 +9,6 @@ use ui::{ }; use workspace::dock::DockPosition; -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ScrollbarSettingsContent { - /// When to show the scrollbar in the git panel. - /// - /// Default: inherits editor scrollbar settings - pub show: Option>, -} - #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarSettings { pub show: Option, diff --git a/crates/gpui/examples/data_table.rs b/crates/gpui/examples/data_table.rs index 10e22828a8e8f5c8778cbcb06a087d4bdfac3adc..e176c44d530ecbc6d5d3140f5c2defaa30a6149e 100644 --- a/crates/gpui/examples/data_table.rs +++ b/crates/gpui/examples/data_table.rs @@ -238,7 +238,7 @@ impl RenderOnce for TableRow { .flex_row() .border_b_1() .border_color(rgb(0xE0E0E0)) - .bg(if self.ix % 2 == 0 { + .bg(if self.ix.is_multiple_of(2) { rgb(0xFFFFFF) } else { rgb(0xFAFAFA) diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 43694702a82566b8f84199dcfc4ff996da93588e..eff4e640efb28a9b70c0da2008cd2293ee2dae47 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -634,15 +634,15 @@ struct CacheKeyRef<'a> { force_width: Option, } -impl PartialEq for (dyn AsCacheKeyRef + '_) { +impl PartialEq for dyn AsCacheKeyRef + '_ { fn eq(&self, other: &dyn AsCacheKeyRef) -> bool { self.as_cache_key_ref() == other.as_cache_key_ref() } } -impl Eq for (dyn AsCacheKeyRef + '_) {} +impl Eq for dyn AsCacheKeyRef + '_ {} -impl Hash for (dyn AsCacheKeyRef + '_) { +impl Hash for dyn AsCacheKeyRef + '_ { fn hash(&self, state: &mut H) { self.as_cache_key_ref().hash(state) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index debe3fc32a002b7a78806e3d6979f028cdc665b0..9b6d9093b219479f811e019c811ad4d7a22d32f8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -3579,7 +3579,7 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; let entries = visible .index - .get_or_init(|| visible.entries.iter().map(|e| (e.path.clone())).collect()); + .get_or_init(|| visible.entries.iter().map(|e| e.path.clone()).collect()); let base_index = ix + entry_range.start; for (i, entry) in visible.entries[entry_range].iter().enumerate() { let global_index = base_index + i; @@ -3623,7 +3623,7 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; let entries = visible .index - .get_or_init(|| visible.entries.iter().map(|e| (e.path.clone())).collect()); + .get_or_init(|| visible.entries.iter().map(|e| e.path.clone()).collect()); for entry in visible.entries[entry_range].iter() { let status = git_status_setting .then_some(entry.git_summary) @@ -4968,7 +4968,7 @@ impl ProjectPanel { visible_worktree .entries .iter() - .map(|e| (e.path.clone())) + .map(|e| e.path.clone()) .collect() }); diff --git a/crates/task/src/vscode_debug_format.rs b/crates/task/src/vscode_debug_format.rs index 5e21cd65302a11a8106881077b0f08c118ed215e..9b3f2e808b750e60d494c0b92abf78bb8c698227 100644 --- a/crates/task/src/vscode_debug_format.rs +++ b/crates/task/src/vscode_debug_format.rs @@ -6,13 +6,6 @@ use crate::{ DebugScenario, DebugTaskFile, EnvVariableReplacer, TcpArgumentsTemplate, VariableName, }; -#[derive(Clone, Debug, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -enum Request { - Launch, - Attach, -} - // TODO support preLaunchTask linkage with other tasks #[derive(Clone, Debug, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 9f84b523af48e5e2645ea7b850f526db2c2ebb7f..306733bf3496ae0c122b73fbd109eb46f3662b8a 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -23,7 +23,6 @@ derive_more.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true -indexmap.workspace = true log.workspace = true palette = { workspace = true, default-features = false, features = ["std"] } parking_lot.workspace = true diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 945517daea904e3bb5bdfe4954813a81af2a9e2b..2d7e1ff9d823eae0d48b375592c6d1f91318f472 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -1,11 +1,9 @@ #![allow(missing_docs)] use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla}; -use indexmap::IndexMap; use palette::FromColor; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{AccentContent, PlayerColorContent}; pub use settings::{FontWeightContent, WindowBackgroundContent}; use crate::{StatusColorsRefinement, ThemeColorsRefinement}; @@ -49,30 +47,6 @@ pub struct ThemeContent { pub style: settings::ThemeStyleContent, } -/// The content of a serialized theme. -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] -#[serde(default)] -pub struct ThemeStyleContent { - #[serde(default, rename = "background.appearance")] - pub window_background_appearance: Option, - - #[serde(default)] - pub accents: Vec, - - #[serde(flatten, default)] - pub colors: settings::ThemeColorsContent, - - #[serde(flatten, default)] - pub status: settings::StatusColorsContent, - - #[serde(default)] - pub players: Vec, - - /// The styles for syntax nodes. - #[serde(default)] - pub syntax: IndexMap, -} - /// Returns the syntax style overrides in the [`ThemeContent`]. pub fn syntax_overrides(this: &settings::ThemeStyleContent) -> Vec<(String, HighlightStyle)> { this.syntax diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index e10d21e4e297fef1ec96d98dea0e45de0ec7e73f..0ea6bbc4bcfba6196031b705f92771c753b9dc50 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -19,37 +19,6 @@ use crate::vscode::VsCodeThemeConverter; const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/schema/themes/v0.2.0.json"; -#[derive(Debug, Deserialize)] -struct FamilyMetadata { - #[expect( - unused, - reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" - )] - pub name: String, - #[expect( - unused, - reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" - )] - pub author: String, - #[expect( - unused, - reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" - )] - pub themes: Vec, - - /// Overrides for specific syntax tokens. - /// - /// Use this to ensure certain Zed syntax tokens are matched - /// to an exact set of scopes when it is not otherwise possible - /// to rely on the default mappings in the theme importer. - #[serde(default)] - #[expect( - unused, - reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove" - )] - pub syntax: IndexMap>, -} - #[derive(Debug, Clone, Copy, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ThemeAppearanceJson { diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index e6eb46c0faeba8a80608259189577d73fcbb2599..8ae163b17266bf77716d6643495c1b55657f8c5f 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -1039,7 +1039,7 @@ fn text_object( .text_object_ranges(offset..offset, TreeSitterOptions::default()) .filter_map(|(r, m)| if m == target { Some(r) } else { None }) .collect(); - matches.sort_by_key(|r| (r.end - r.start)); + matches.sort_by_key(|r| r.end - r.start); if let Some(buffer_range) = matches.first() { let range = excerpt.map_range_from_buffer(buffer_range.clone()); return Some(range.start.to_display_point(map)..range.end.to_display_point(map)); @@ -1050,7 +1050,7 @@ fn text_object( .text_object_ranges(offset..offset, TreeSitterOptions::default()) .filter_map(|(r, m)| if m == around { Some(r) } else { None }) .collect(); - matches.sort_by_key(|r| (r.end - r.start)); + matches.sort_by_key(|r| r.end - r.start); let around_range = matches.first()?; let mut matches: Vec> = buffer diff --git a/flake.lock b/flake.lock index d96f0a998ff47958a7b605d61e1bf539929555f5..ced528afa60cb567b291c6962c80e503845b08fc 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1755993354, - "narHash": "sha256-FCRRAzSaL/+umLIm3RU3O/+fJ2ssaPHseI2SSFL8yZU=", + "lastModified": 1758215636, + "narHash": "sha256-8nkzkPbdxze8CxWhKWlcLbJEU1vfLM/nVqRlTy17V54=", "owner": "ipetkov", "repo": "crane", - "rev": "25bd41b24426c7734278c2ff02e53258851db914", + "rev": "a669fe77a8b0cd6f11419d89ea45a16691ca5121", "type": "github" }, "original": { @@ -33,10 +33,10 @@ "nixpkgs": { "locked": { "lastModified": 315532800, - "narHash": "sha256-E8CyvVDZuIsF7puIw+OLkrFmhj3qUV+iwPcNbBhdcxM=", - "rev": "a918bb3594dd243c2f8534b3be01b3cb4ed35fd1", + "narHash": "sha256-YPoFUJMpbuPvIS4FJBn2Sv/iWsui9S26gu2ufFWEY0g=", + "rev": "a1f79a1770d05af18111fbbe2a3ab2c42c0f6cd0", "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre854010.a918bb3594dd/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre864673.a1f79a1770d0/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -58,11 +58,11 @@ ] }, "locked": { - "lastModified": 1756607787, - "narHash": "sha256-ciwAdgtlAN1PCaidWK6RuWsTBL8DVuyDCGM+X3ein5Q=", + "lastModified": 1758508617, + "narHash": "sha256-kx2uELmVnAbiekj/YFfWR26OXqXedImkhe2ocnbumTA=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "f46d294b87ebb9f7124f1ce13aa2a5f5acc0f3eb", + "rev": "d2bac276ac7e669a1f09c48614538a37e3eb6d0f", "type": "github" }, "original": { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 2c909e0e1e66a7198c32e48805206cde77d9152c..6ef0865182f1d5ef79cebdcbeacd52d5f71b72ae 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.89" +channel = "1.90" profile = "minimal" components = [ "rustfmt", "clippy" ] targets = [ diff --git a/script/generate-licenses b/script/generate-licenses index 771ce2363d0e46856393d0aea3d72d484519353a..5deed400e4e173b4ce4bb88e4b36469cd176a9ad 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -2,7 +2,7 @@ set -euo pipefail -CARGO_ABOUT_VERSION="0.7" +CARGO_ABOUT_VERSION="0.8" OUTPUT_FILE="${1:-$(pwd)/assets/licenses.md}" TEMPLATE_FILE="script/licenses/template.md.hbs" diff --git a/script/generate-licenses-csv b/script/generate-licenses-csv index 7ffe0f3f1445d113c24644c61e00f90a4463a066..0e40c69d47a0d769e25fe1d45df18bdc439720d8 100755 --- a/script/generate-licenses-csv +++ b/script/generate-licenses-csv @@ -2,7 +2,7 @@ set -euo pipefail -CARGO_ABOUT_VERSION="0.6" +CARGO_ABOUT_VERSION="0.8" OUTPUT_FILE="${1:-$(pwd)/assets/licenses.csv}" TEMPLATE_FILE="script/licenses/template.csv.hbs" diff --git a/script/generate-licenses.ps1 b/script/generate-licenses.ps1 index 52a6fe0118b9979be23d0c584bc0facdd4ce8f1e..ab7df73e5696e7fbbceb236766ae35c3360a3afe 100644 --- a/script/generate-licenses.ps1 +++ b/script/generate-licenses.ps1 @@ -1,4 +1,4 @@ -$CARGO_ABOUT_VERSION="0.7" +$CARGO_ABOUT_VERSION="0.8" $outputFile=$args[0] ? $args[0] : "$(Get-Location)/assets/licenses.md" $templateFile="script/licenses/template.md.hbs" From e9abd5b28b2bae826aead58b3fef30393a937aaf Mon Sep 17 00:00:00 2001 From: Jens Kouros Date: Mon, 22 Sep 2025 23:40:50 +0200 Subject: [PATCH 192/721] docs: Mention required matching configs when developing language server extensions (#38674) This took me quite a while to find out when I developed my first language extension. I had non-matching entries in the `languages` array and in the name field of `config.toml`, and it was especially tricky because the zed extension would start up, but not the language server. I sure which this had been in the docs, so I am contributing it now! --- docs/src/extensions/languages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/extensions/languages.md b/docs/src/extensions/languages.md index 376ac342f87252e6128e5107d057b32790c06e0a..5c63b880c875701e1721b8d6298dc49da6b45a98 100644 --- a/docs/src/extensions/languages.md +++ b/docs/src/extensions/languages.md @@ -366,7 +366,7 @@ TBD: `#set! tag` Zed uses the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) to provide advanced language support. -An extension may provide any number of language servers. To provide a language server from your extension, add an entry to your `extension.toml` with the name of your language server and the language(s) it applies to: +An extension may provide any number of language servers. To provide a language server from your extension, add an entry to your `extension.toml` with the name of your language server and the language(s) it applies to. The entry in the list of `languages` has to match the `name` field from the `config.toml` file for that language: ```toml [language_servers.my-language-server] From c9e3b32366df2de3bf24dc5d36b7739eb24bf417 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 22 Sep 2025 19:18:38 -0300 Subject: [PATCH 193/721] zeta2: Provider setup (#38676) Creates a new `EditPredictionProvider` for zeta2, that requests completions from a new cloud endpoint including context from the new `edit_prediction_context` crate. This is not ready for use, but it allows us to iterate. Release Notes: - N/A --------- Co-authored-by: Michael Sloan Co-authored-by: Bennet Co-authored-by: Bennet Bo Fenner --- Cargo.lock | 29 + Cargo.toml | 2 + crates/cloud_llm_client/Cargo.toml | 1 + .../cloud_llm_client/src/cloud_llm_client.rs | 2 + .../cloud_llm_client/src/predict_edits_v3.rs | 118 ++ crates/edit_prediction_context/Cargo.toml | 1 + .../src/declaration.rs | 31 + .../src/declaration_scoring.rs | 83 +- .../src/edit_prediction_context.rs | 104 +- crates/edit_prediction_context/src/excerpt.rs | 85 +- .../edit_prediction_context/src/reference.rs | 4 +- .../src/syntax_index.rs | 69 +- .../src/text_similarity.rs | 4 + .../src/edit_prediction_tools.rs | 16 +- .../settings/src/settings_content/language.rs | 11 + crates/zed/Cargo.toml | 1 + .../zed/src/zed/edit_prediction_registry.rs | 46 +- crates/zeta2/Cargo.toml | 37 + crates/zeta2/LICENSE-GPL | 1 + crates/zeta2/src/zeta2.rs | 1130 +++++++++++++++++ 20 files changed, 1589 insertions(+), 186 deletions(-) create mode 100644 crates/cloud_llm_client/src/predict_edits_v3.rs create mode 100644 crates/zeta2/Cargo.toml create mode 120000 crates/zeta2/LICENSE-GPL create mode 100644 crates/zeta2/src/zeta2.rs diff --git a/Cargo.lock b/Cargo.lock index 450d59e73b6ce48f86abe17d6bd27df98f1e7df4..5e704d56697b1460c9a3a705f852e3287185bdf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3216,6 +3216,7 @@ name = "cloud_llm_client" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "pretty_assertions", "serde", "serde_json", @@ -5177,6 +5178,7 @@ dependencies = [ "anyhow", "arrayvec", "clap", + "cloud_llm_client", "collections", "futures 0.3.31", "gpui", @@ -21370,6 +21372,7 @@ dependencies = [ "zed_actions", "zed_env_vars", "zeta", + "zeta2", "zlog", "zlog_settings", ] @@ -21647,6 +21650,32 @@ dependencies = [ "zlog", ] +[[package]] +name = "zeta2" +version = "0.1.0" +dependencies = [ + "anyhow", + "arrayvec", + "client", + "cloud_llm_client", + "edit_prediction", + "edit_prediction_context", + "futures 0.3.31", + "gpui", + "language", + "language_model", + "log", + "project", + "release_channel", + "serde_json", + "thiserror 2.0.12", + "util", + "uuid", + "workspace", + "workspace-hack", + "worktree", +] + [[package]] name = "zeta_cli" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index fd552c6e9d117bd03b251f231dee8294b02ba928..6e1950aaeea715dd85c98a443b6116a619b0e3f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,6 +199,7 @@ members = [ "crates/zed_actions", "crates/zed_env_vars", "crates/zeta", + "crates/zeta2", "crates/zeta_cli", "crates/zlog", "crates/zlog_settings", @@ -432,6 +433,7 @@ zed = { path = "crates/zed" } zed_actions = { path = "crates/zed_actions" } zed_env_vars = { path = "crates/zed_env_vars" } zeta = { path = "crates/zeta" } +zeta2 = { path = "crates/zeta2" } zlog = { path = "crates/zlog" } zlog_settings = { path = "crates/zlog_settings" } diff --git a/crates/cloud_llm_client/Cargo.toml b/crates/cloud_llm_client/Cargo.toml index 6f090d3c6ea67d8bb189212fb9704b618554f671..700893dd4030e2eb7b9eab2286319ec08df2f522 100644 --- a/crates/cloud_llm_client/Cargo.toml +++ b/crates/cloud_llm_client/Cargo.toml @@ -13,6 +13,7 @@ path = "src/cloud_llm_client.rs" [dependencies] anyhow.workspace = true +chrono.workspace = true serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true strum = { workspace = true, features = ["derive"] } diff --git a/crates/cloud_llm_client/src/cloud_llm_client.rs b/crates/cloud_llm_client/src/cloud_llm_client.rs index 24923a318441afeaa2521064b4f433ab9ee1e55f..e0cc42af76156466c31ead17d6421f3634d3ad7c 100644 --- a/crates/cloud_llm_client/src/cloud_llm_client.rs +++ b/crates/cloud_llm_client/src/cloud_llm_client.rs @@ -1,3 +1,5 @@ +pub mod predict_edits_v3; + use std::str::FromStr; use std::sync::Arc; diff --git a/crates/cloud_llm_client/src/predict_edits_v3.rs b/crates/cloud_llm_client/src/predict_edits_v3.rs new file mode 100644 index 0000000000000000000000000000000000000000..60621b1f14714439b0527078c07e2865799172f3 --- /dev/null +++ b/crates/cloud_llm_client/src/predict_edits_v3.rs @@ -0,0 +1,118 @@ +use chrono::Duration; +use serde::{Deserialize, Serialize}; +use std::{ops::Range, path::PathBuf}; +use uuid::Uuid; + +use crate::PredictEditsGitInfo; + +// TODO: snippet ordering within file / relative to excerpt + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PredictEditsRequest { + pub excerpt: String, + pub excerpt_path: PathBuf, + /// Within file + pub excerpt_range: Range, + /// Within `excerpt` + pub cursor_offset: usize, + /// Within `signatures` + pub excerpt_parent: Option, + pub signatures: Vec, + pub referenced_declarations: Vec, + pub events: Vec, + #[serde(default)] + pub can_collect_data: bool, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub diagnostic_groups: Vec, + /// Info about the git repository state, only present when can_collect_data is true. + #[serde(skip_serializing_if = "Option::is_none", default)] + pub git_info: Option, + #[serde(default)] + pub debug_info: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "event")] +pub enum Event { + BufferChange { + path: Option, + old_path: Option, + diff: String, + predicted: bool, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Signature { + pub text: String, + pub text_is_truncated: bool, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub parent_index: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReferencedDeclaration { + pub path: PathBuf, + pub text: String, + pub text_is_truncated: bool, + /// Range of `text` within file, potentially truncated according to `text_is_truncated` + pub range: Range, + /// Range within `text` + pub signature_range: Range, + /// Index within `signatures`. + #[serde(skip_serializing_if = "Option::is_none", default)] + pub parent_index: Option, + pub score_components: ScoreComponents, + pub signature_score: f32, + pub declaration_score: f32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ScoreComponents { + pub is_same_file: bool, + pub is_referenced_nearby: bool, + pub is_referenced_in_breadcrumb: bool, + pub reference_count: usize, + pub same_file_declaration_count: usize, + pub declaration_count: usize, + pub reference_line_distance: u32, + pub declaration_line_distance: u32, + pub declaration_line_distance_rank: usize, + pub containing_range_vs_item_jaccard: f32, + pub containing_range_vs_signature_jaccard: f32, + pub adjacent_vs_item_jaccard: f32, + pub adjacent_vs_signature_jaccard: f32, + pub containing_range_vs_item_weighted_overlap: f32, + pub containing_range_vs_signature_weighted_overlap: f32, + pub adjacent_vs_item_weighted_overlap: f32, + pub adjacent_vs_signature_weighted_overlap: f32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiagnosticGroup { + pub language_server: String, + pub diagnostic_group: serde_json::Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PredictEditsResponse { + pub request_id: Uuid, + pub edits: Vec, + pub debug_info: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DebugInfo { + pub prompt: String, + pub prompt_planning_time: Duration, + pub model_response: String, + pub inference_time: Duration, + pub parsing_time: Duration, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Edit { + pub path: PathBuf, + pub range: Range, + pub content: String, +} diff --git a/crates/edit_prediction_context/Cargo.toml b/crates/edit_prediction_context/Cargo.toml index 48f51da3912ea5bca589e7b559d5b665b9b762d6..75880cad5f3e2807e525908656931853efa19a92 100644 --- a/crates/edit_prediction_context/Cargo.toml +++ b/crates/edit_prediction_context/Cargo.toml @@ -14,6 +14,7 @@ path = "src/edit_prediction_context.rs" [dependencies] anyhow.workspace = true arrayvec.workspace = true +cloud_llm_client.workspace = true collections.workspace = true futures.workspace = true gpui.workspace = true diff --git a/crates/edit_prediction_context/src/declaration.rs b/crates/edit_prediction_context/src/declaration.rs index 8fba85367c70e2cb1211e343bba7a6675a5a5360..653f810d439395a8825c99f4b007e05d881540ab 100644 --- a/crates/edit_prediction_context/src/declaration.rs +++ b/crates/edit_prediction_context/src/declaration.rs @@ -41,6 +41,20 @@ impl Declaration { } } + pub fn parent(&self) -> Option { + match self { + Declaration::File { declaration, .. } => declaration.parent, + Declaration::Buffer { declaration, .. } => declaration.parent, + } + } + + pub fn as_buffer(&self) -> Option<&BufferDeclaration> { + match self { + Declaration::File { .. } => None, + Declaration::Buffer { declaration, .. } => Some(declaration), + } + } + pub fn project_entry_id(&self) -> ProjectEntryId { match self { Declaration::File { @@ -52,6 +66,13 @@ impl Declaration { } } + pub fn item_range(&self) -> Range { + match self { + Declaration::File { declaration, .. } => declaration.item_range_in_file.clone(), + Declaration::Buffer { declaration, .. } => declaration.item_range.clone(), + } + } + pub fn item_text(&self) -> (Cow<'_, str>, bool) { match self { Declaration::File { declaration, .. } => ( @@ -83,6 +104,16 @@ impl Declaration { ), } } + + pub fn signature_range_in_item_text(&self) -> Range { + match self { + Declaration::File { declaration, .. } => declaration.signature_range_in_text.clone(), + Declaration::Buffer { declaration, .. } => { + declaration.signature_range.start - declaration.item_range.start + ..declaration.signature_range.end - declaration.item_range.start + } + } + } } fn expand_range_to_line_boundaries_and_truncate( diff --git a/crates/edit_prediction_context/src/declaration_scoring.rs b/crates/edit_prediction_context/src/declaration_scoring.rs index 4cbc4e83c02e0cae912813a261d99f8fe8c41b55..1638723857d225d05efce512bb9025fa89fb38f3 100644 --- a/crates/edit_prediction_context/src/declaration_scoring.rs +++ b/crates/edit_prediction_context/src/declaration_scoring.rs @@ -1,10 +1,11 @@ +use cloud_llm_client::predict_edits_v3::ScoreComponents; use itertools::Itertools as _; use language::BufferSnapshot; use ordered_float::OrderedFloat; use serde::Serialize; use std::{collections::HashMap, ops::Range}; use strum::EnumIter; -use text::{OffsetRangeExt, Point, ToPoint}; +use text::{Point, ToPoint}; use crate::{ Declaration, EditPredictionExcerpt, EditPredictionExcerptText, Identifier, @@ -15,19 +16,14 @@ use crate::{ const MAX_IDENTIFIER_DECLARATION_COUNT: usize = 16; -// TODO: -// -// * Consider adding declaration_file_count - #[derive(Clone, Debug)] pub struct ScoredSnippet { pub identifier: Identifier, pub declaration: Declaration, - pub score_components: ScoreInputs, + pub score_components: ScoreComponents, pub scores: Scores, } -// TODO: Consider having "Concise" style corresponding to `concise_text` #[derive(EnumIter, Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum SnippetStyle { Signature, @@ -90,8 +86,8 @@ pub fn scored_snippets( let declaration_count = declarations.len(); declarations - .iter() - .filter_map(|declaration| match declaration { + .into_iter() + .filter_map(|(declaration_id, declaration)| match declaration { Declaration::Buffer { buffer_id, declaration: buffer_declaration, @@ -100,24 +96,29 @@ pub fn scored_snippets( let is_same_file = buffer_id == ¤t_buffer.remote_id(); if is_same_file { - range_intersection( - &buffer_declaration.item_range.to_offset(¤t_buffer), - &excerpt.range, - ) - .is_none() - .then(|| { + let overlaps_excerpt = + range_intersection(&buffer_declaration.item_range, &excerpt.range) + .is_some(); + if overlaps_excerpt + || excerpt + .parent_declarations + .iter() + .any(|(excerpt_parent, _)| excerpt_parent == &declaration_id) + { + None + } else { let declaration_line = buffer_declaration .item_range .start .to_point(current_buffer) .row; - ( + Some(( true, (cursor_point.row as i32 - declaration_line as i32) .unsigned_abs(), declaration, - ) - }) + )) + } } else { Some((false, u32::MAX, declaration)) } @@ -238,7 +239,8 @@ fn score_snippet( let adjacent_vs_signature_weighted_overlap = weighted_overlap_coefficient(adjacent_identifier_occurrences, &item_signature_occurrences); - let score_components = ScoreInputs { + // TODO: Consider adding declaration_file_count + let score_components = ScoreComponents { is_same_file, is_referenced_nearby, is_referenced_in_breadcrumb, @@ -261,51 +263,30 @@ fn score_snippet( Some(ScoredSnippet { identifier: identifier.clone(), declaration: declaration, - scores: score_components.score(), + scores: Scores::score(&score_components), score_components, }) } -#[derive(Clone, Debug, Serialize)] -pub struct ScoreInputs { - pub is_same_file: bool, - pub is_referenced_nearby: bool, - pub is_referenced_in_breadcrumb: bool, - pub reference_count: usize, - pub same_file_declaration_count: usize, - pub declaration_count: usize, - pub reference_line_distance: u32, - pub declaration_line_distance: u32, - pub declaration_line_distance_rank: usize, - pub containing_range_vs_item_jaccard: f32, - pub containing_range_vs_signature_jaccard: f32, - pub adjacent_vs_item_jaccard: f32, - pub adjacent_vs_signature_jaccard: f32, - pub containing_range_vs_item_weighted_overlap: f32, - pub containing_range_vs_signature_weighted_overlap: f32, - pub adjacent_vs_item_weighted_overlap: f32, - pub adjacent_vs_signature_weighted_overlap: f32, -} - #[derive(Clone, Debug, Serialize)] pub struct Scores { pub signature: f32, pub declaration: f32, } -impl ScoreInputs { - fn score(&self) -> Scores { +impl Scores { + fn score(components: &ScoreComponents) -> Scores { // Score related to how likely this is the correct declaration, range 0 to 1 - let accuracy_score = if self.is_same_file { + let accuracy_score = if components.is_same_file { // TODO: use declaration_line_distance_rank - 1.0 / self.same_file_declaration_count as f32 + 1.0 / components.same_file_declaration_count as f32 } else { - 1.0 / self.declaration_count as f32 + 1.0 / components.declaration_count as f32 }; // Score related to the distance between the reference and cursor, range 0 to 1 - let distance_score = if self.is_referenced_nearby { - 1.0 / (1.0 + self.reference_line_distance as f32 / 10.0).powf(2.0) + let distance_score = if components.is_referenced_nearby { + 1.0 / (1.0 + components.reference_line_distance as f32 / 10.0).powf(2.0) } else { // same score as ~14 lines away, rationale is to not overly penalize references from parent signatures 0.5 @@ -315,10 +296,12 @@ impl ScoreInputs { let combined_score = 10.0 * accuracy_score * distance_score; Scores { - signature: combined_score * self.containing_range_vs_signature_weighted_overlap, + signature: combined_score * components.containing_range_vs_signature_weighted_overlap, // declaration score gets boosted both by being multiplied by 2 and by there being more // weighted overlap. - declaration: 2.0 * combined_score * self.containing_range_vs_item_weighted_overlap, + declaration: 2.0 + * combined_score + * components.containing_range_vs_item_weighted_overlap, } } } diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs index aed2953777d82d65b7e9cb42229d78634d5e4a3d..aeda74811296b70fc48198b1c3f72a50cfd7c31e 100644 --- a/crates/edit_prediction_context/src/edit_prediction_context.rs +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -6,62 +6,82 @@ mod reference; mod syntax_index; mod text_similarity; -use std::time::Instant; - -pub use declaration::{BufferDeclaration, Declaration, FileDeclaration, Identifier}; -pub use declaration_scoring::SnippetStyle; -pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions, EditPredictionExcerptText}; - use gpui::{App, AppContext as _, Entity, Task}; use language::BufferSnapshot; -pub use reference::references_in_excerpt; -pub use syntax_index::SyntaxIndex; use text::{Point, ToOffset as _}; -use crate::declaration_scoring::{ScoredSnippet, scored_snippets}; +pub use declaration::*; +pub use declaration_scoring::*; +pub use excerpt::*; +pub use reference::*; +pub use syntax_index::*; #[derive(Debug)] pub struct EditPredictionContext { pub excerpt: EditPredictionExcerpt, pub excerpt_text: EditPredictionExcerptText, + pub cursor_offset_in_excerpt: usize, pub snippets: Vec, - pub retrieval_duration: std::time::Duration, } impl EditPredictionContext { - pub fn gather( + pub fn gather_context_in_background( cursor_point: Point, buffer: BufferSnapshot, excerpt_options: EditPredictionExcerptOptions, - syntax_index: Entity, + syntax_index: Option>, cx: &mut App, ) -> Task> { - let start = Instant::now(); - let index_state = syntax_index.read_with(cx, |index, _cx| index.state().clone()); - cx.background_spawn(async move { - let index_state = index_state.lock().await; - - let excerpt = - EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &excerpt_options)?; - let excerpt_text = excerpt.text(&buffer); - let references = references_in_excerpt(&excerpt, &excerpt_text, &buffer); - let cursor_offset = cursor_point.to_offset(&buffer); - - let snippets = scored_snippets( + if let Some(syntax_index) = syntax_index { + let index_state = syntax_index.read_with(cx, |index, _cx| index.state().clone()); + cx.background_spawn(async move { + let index_state = index_state.lock().await; + Self::gather_context(cursor_point, &buffer, &excerpt_options, Some(&index_state)) + }) + } else { + cx.background_spawn(async move { + Self::gather_context(cursor_point, &buffer, &excerpt_options, None) + }) + } + } + + pub fn gather_context( + cursor_point: Point, + buffer: &BufferSnapshot, + excerpt_options: &EditPredictionExcerptOptions, + index_state: Option<&SyntaxIndexState>, + ) -> Option { + let excerpt = EditPredictionExcerpt::select_from_buffer( + cursor_point, + buffer, + excerpt_options, + index_state, + )?; + let excerpt_text = excerpt.text(buffer); + let cursor_offset_in_file = cursor_point.to_offset(buffer); + // TODO fix this to not need saturating_sub + let cursor_offset_in_excerpt = cursor_offset_in_file.saturating_sub(excerpt.range.start); + + let snippets = if let Some(index_state) = index_state { + let references = references_in_excerpt(&excerpt, &excerpt_text, buffer); + + scored_snippets( &index_state, &excerpt, &excerpt_text, references, - cursor_offset, - &buffer, - ); - - Some(Self { - excerpt, - excerpt_text, - snippets, - retrieval_duration: start.elapsed(), - }) + cursor_offset_in_file, + buffer, + ) + } else { + vec![] + }; + + Some(Self { + excerpt, + excerpt_text, + cursor_offset_in_excerpt, + snippets, }) } } @@ -101,24 +121,28 @@ mod tests { let context = cx .update(|cx| { - EditPredictionContext::gather( + EditPredictionContext::gather_context_in_background( cursor_point, buffer_snapshot, EditPredictionExcerptOptions { - max_bytes: 40, + max_bytes: 60, min_bytes: 10, target_before_cursor_over_total_bytes: 0.5, - include_parent_signatures: false, }, - index, + Some(index), cx, ) }) .await .unwrap(); - assert_eq!(context.snippets.len(), 1); - assert_eq!(context.snippets[0].identifier.name.as_ref(), "process_data"); + let mut snippet_identifiers = context + .snippets + .iter() + .map(|snippet| snippet.identifier.name.as_ref()) + .collect::>(); + snippet_identifiers.sort(); + assert_eq!(snippet_identifiers, vec!["main", "process_data"]); drop(buffer); } diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index d27e75bd129147986d2359585d5e048704d8828f..764c9040f247561ba6058dfd2954f42085297a4c 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -1,9 +1,11 @@ use language::BufferSnapshot; use std::ops::Range; -use text::{OffsetRangeExt as _, Point, ToOffset as _, ToPoint as _}; +use text::{Point, ToOffset as _, ToPoint as _}; use tree_sitter::{Node, TreeCursor}; use util::RangeExt; +use crate::{BufferDeclaration, declaration::DeclarationId, syntax_index::SyntaxIndexState}; + // TODO: // // - Test parent signatures @@ -27,14 +29,12 @@ pub struct EditPredictionExcerptOptions { pub min_bytes: usize, /// Target ratio of bytes before the cursor divided by total bytes in the window. pub target_before_cursor_over_total_bytes: f32, - /// Whether to include parent signatures - pub include_parent_signatures: bool, } #[derive(Debug, Clone)] pub struct EditPredictionExcerpt { pub range: Range, - pub parent_signature_ranges: Vec>, + pub parent_declarations: Vec<(DeclarationId, Range)>, pub size: usize, } @@ -50,9 +50,9 @@ impl EditPredictionExcerpt { .text_for_range(self.range.clone()) .collect::(); let parent_signatures = self - .parent_signature_ranges + .parent_declarations .iter() - .map(|range| buffer.text_for_range(range.clone()).collect::()) + .map(|(_, range)| buffer.text_for_range(range.clone()).collect::()) .collect(); EditPredictionExcerptText { body, @@ -62,8 +62,9 @@ impl EditPredictionExcerpt { /// Selects an excerpt around a buffer position, attempting to choose logical boundaries based /// on TreeSitter structure and approximately targeting a goal ratio of bytesbefore vs after the - /// cursor. When `include_parent_signatures` is true, the excerpt also includes the signatures - /// of parent outline items. + /// cursor. + /// + /// When `index` is provided, the excerpt will include the signatures of parent outline items. /// /// First tries to use AST node boundaries to select the excerpt, and falls back on line-based /// expansion. @@ -73,6 +74,7 @@ impl EditPredictionExcerpt { query_point: Point, buffer: &BufferSnapshot, options: &EditPredictionExcerptOptions, + syntax_index: Option<&SyntaxIndexState>, ) -> Option { if buffer.len() <= options.max_bytes { log::debug!( @@ -90,17 +92,9 @@ impl EditPredictionExcerpt { return None; } - // TODO: Don't compute text / annotation_range / skip converting to and from anchors. - let outline_items = if options.include_parent_signatures { - buffer - .outline_items_containing(query_range.clone(), false, None) - .into_iter() - .flat_map(|item| { - Some(ExcerptOutlineItem { - item_range: item.range.to_offset(&buffer), - signature_range: item.signature_range?.to_offset(&buffer), - }) - }) + let parent_declarations = if let Some(syntax_index) = syntax_index { + syntax_index + .buffer_declarations_containing_range(buffer.remote_id(), query_range.clone()) .collect() } else { Vec::new() @@ -109,7 +103,7 @@ impl EditPredictionExcerpt { let excerpt_selector = ExcerptSelector { query_offset, query_range, - outline_items: &outline_items, + parent_declarations: &parent_declarations, buffer, options, }; @@ -132,15 +126,15 @@ impl EditPredictionExcerpt { excerpt_selector.select_lines() } - fn new(range: Range, parent_signature_ranges: Vec>) -> Self { + fn new(range: Range, parent_declarations: Vec<(DeclarationId, Range)>) -> Self { let size = range.len() - + parent_signature_ranges + + parent_declarations .iter() - .map(|r| r.len()) + .map(|(_, range)| range.len()) .sum::(); Self { range, - parent_signature_ranges, + parent_declarations, size, } } @@ -150,20 +144,14 @@ impl EditPredictionExcerpt { // this is an issue because parent_signature_ranges may be incorrect log::error!("bug: with_expanded_range called with disjoint range"); } - let mut parent_signature_ranges = Vec::with_capacity(self.parent_signature_ranges.len()); - let mut size = new_range.len(); - for range in &self.parent_signature_ranges { - if range.contains_inclusive(&new_range) { + let mut parent_declarations = Vec::with_capacity(self.parent_declarations.len()); + for (declaration_id, range) in &self.parent_declarations { + if !range.contains_inclusive(&new_range) { break; } - parent_signature_ranges.push(range.clone()); - size += range.len(); - } - Self { - range: new_range, - parent_signature_ranges, - size, + parent_declarations.push((*declaration_id, range.clone())); } + Self::new(new_range, parent_declarations) } fn parent_signatures_size(&self) -> usize { @@ -174,16 +162,11 @@ impl EditPredictionExcerpt { struct ExcerptSelector<'a> { query_offset: usize, query_range: Range, - outline_items: &'a [ExcerptOutlineItem], + parent_declarations: &'a [(DeclarationId, &'a BufferDeclaration)], buffer: &'a BufferSnapshot, options: &'a EditPredictionExcerptOptions, } -struct ExcerptOutlineItem { - item_range: Range, - signature_range: Range, -} - impl<'a> ExcerptSelector<'a> { /// Finds the largest node that is smaller than the window size and contains `query_range`. fn select_tree_sitter_nodes(&self) -> Option { @@ -396,13 +379,13 @@ impl<'a> ExcerptSelector<'a> { } fn make_excerpt(&self, range: Range) -> EditPredictionExcerpt { - let parent_signature_ranges = self - .outline_items + let parent_declarations = self + .parent_declarations .iter() - .filter(|item| item.item_range.contains_inclusive(&range)) - .map(|item| item.signature_range.clone()) + .filter(|(_, declaration)| declaration.item_range.contains_inclusive(&range)) + .map(|(id, declaration)| (*id, declaration.signature_range.clone())) .collect(); - EditPredictionExcerpt::new(range, parent_signature_ranges) + EditPredictionExcerpt::new(range, parent_declarations) } /// Returns `true` if the `forward` excerpt is a better choice than the `backward` excerpt. @@ -493,8 +476,9 @@ mod tests { let buffer = create_buffer(&text, cx); let cursor_point = cursor.to_point(&buffer); - let excerpt = EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &options) - .expect("Should select an excerpt"); + let excerpt = + EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &options, None) + .expect("Should select an excerpt"); pretty_assertions::assert_eq!( generate_marked_text(&text, std::slice::from_ref(&excerpt.range), false), generate_marked_text(&text, &[expected_excerpt], false) @@ -517,7 +501,6 @@ fn main() { max_bytes: 20, min_bytes: 10, target_before_cursor_over_total_bytes: 0.5, - include_parent_signatures: false, }; check_example(options, text, cx); @@ -541,7 +524,6 @@ fn bar() {}"#; max_bytes: 65, min_bytes: 10, target_before_cursor_over_total_bytes: 0.5, - include_parent_signatures: false, }; check_example(options, text, cx); @@ -561,7 +543,6 @@ fn main() { max_bytes: 50, min_bytes: 10, target_before_cursor_over_total_bytes: 0.5, - include_parent_signatures: false, }; check_example(options, text, cx); @@ -583,7 +564,6 @@ fn main() { max_bytes: 60, min_bytes: 45, target_before_cursor_over_total_bytes: 0.5, - include_parent_signatures: false, }; check_example(options, text, cx); @@ -608,7 +588,6 @@ fn main() { max_bytes: 120, min_bytes: 10, target_before_cursor_over_total_bytes: 0.6, - include_parent_signatures: false, }; check_example(options, text, cx); diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs index abb7dd75dd569f7a14bea807bdc64441d0e64871..268f8c39ef84ba29593f502aff7e818e931cc873 100644 --- a/crates/edit_prediction_context/src/reference.rs +++ b/crates/edit_prediction_context/src/reference.rs @@ -33,8 +33,8 @@ pub fn references_in_excerpt( snapshot, ); - for (range, text) in excerpt - .parent_signature_ranges + for ((_, range), text) in excerpt + .parent_declarations .iter() .zip(excerpt_text.parent_signatures.iter()) { diff --git a/crates/edit_prediction_context/src/syntax_index.rs b/crates/edit_prediction_context/src/syntax_index.rs index 64982f5805f08a3ba791578e28778f0c8399fde8..d234e975d504c145d7bc2fc0680569c388ba0d1c 100644 --- a/crates/edit_prediction_context/src/syntax_index.rs +++ b/crates/edit_prediction_context/src/syntax_index.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use collections::{HashMap, HashSet}; use futures::lock::Mutex; use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity}; @@ -8,20 +6,17 @@ use project::buffer_store::{BufferStore, BufferStoreEvent}; use project::worktree_store::{WorktreeStore, WorktreeStoreEvent}; use project::{PathChange, Project, ProjectEntryId, ProjectPath}; use slotmap::SlotMap; +use std::iter; +use std::ops::Range; +use std::sync::Arc; use text::BufferId; -use util::{debug_panic, some_or_debug_panic}; +use util::{RangeExt as _, debug_panic, some_or_debug_panic}; use crate::declaration::{ BufferDeclaration, Declaration, DeclarationId, FileDeclaration, Identifier, }; use crate::outline::declarations_in_buffer; -// TODO: -// -// * Skip for remote projects -// -// * Consider making SyntaxIndex not an Entity. - // Potential future improvements: // // * Send multiple selected excerpt ranges. Challenge is that excerpt ranges influence which @@ -40,7 +35,6 @@ use crate::outline::declarations_in_buffer; // * Concurrent slotmap // // * Use queue for parsing -// pub struct SyntaxIndex { state: Arc>, @@ -432,7 +426,7 @@ impl SyntaxIndexState { pub fn declarations_for_identifier( &self, identifier: &Identifier, - ) -> Vec { + ) -> Vec<(DeclarationId, &Declaration)> { // make sure to not have a large stack allocation assert!(N < 32); @@ -454,7 +448,7 @@ impl SyntaxIndexState { project_entry_id, .. } => { included_buffer_entry_ids.push(*project_entry_id); - result.push(declaration.clone()); + result.push((*declaration_id, declaration)); if result.len() == N { return Vec::new(); } @@ -463,19 +457,19 @@ impl SyntaxIndexState { project_entry_id, .. } => { if !included_buffer_entry_ids.contains(&project_entry_id) { - file_declarations.push(declaration.clone()); + file_declarations.push((*declaration_id, declaration)); } } } } - for declaration in file_declarations { + for (declaration_id, declaration) in file_declarations { match declaration { Declaration::File { project_entry_id, .. } => { if !included_buffer_entry_ids.contains(&project_entry_id) { - result.push(declaration); + result.push((declaration_id, declaration)); if result.len() == N { return Vec::new(); @@ -489,6 +483,35 @@ impl SyntaxIndexState { result } + pub fn buffer_declarations_containing_range( + &self, + buffer_id: BufferId, + range: Range, + ) -> impl Iterator { + let Some(buffer_state) = self.buffers.get(&buffer_id) else { + return itertools::Either::Left(iter::empty()); + }; + + let iter = buffer_state + .declarations + .iter() + .filter_map(move |declaration_id| { + let Some(declaration) = self + .declarations + .get(*declaration_id) + .and_then(|d| d.as_buffer()) + else { + log::error!("bug: missing buffer outline declaration"); + return None; + }; + if declaration.item_range.contains_inclusive(&range) { + return Some((*declaration_id, declaration)); + } + return None; + }); + itertools::Either::Right(iter) + } + pub fn file_declaration_count(&self, declaration: &Declaration) -> usize { match declaration { Declaration::File { @@ -553,11 +576,11 @@ mod tests { let decls = index_state.declarations_for_identifier::<8>(&main); assert_eq!(decls.len(), 2); - let decl = expect_file_decl("c.rs", &decls[0], &project, cx); + let decl = expect_file_decl("c.rs", &decls[0].1, &project, cx); assert_eq!(decl.identifier, main.clone()); assert_eq!(decl.item_range_in_file, 32..280); - let decl = expect_file_decl("a.rs", &decls[1], &project, cx); + let decl = expect_file_decl("a.rs", &decls[1].1, &project, cx); assert_eq!(decl.identifier, main); assert_eq!(decl.item_range_in_file, 0..98); }); @@ -577,7 +600,7 @@ mod tests { let decls = index_state.declarations_for_identifier::<8>(&test_process_data); assert_eq!(decls.len(), 1); - let decl = expect_file_decl("c.rs", &decls[0], &project, cx); + let decl = expect_file_decl("c.rs", &decls[0].1, &project, cx); assert_eq!(decl.identifier, test_process_data); let parent_id = decl.parent.unwrap(); @@ -618,7 +641,7 @@ mod tests { let decls = index_state.declarations_for_identifier::<8>(&test_process_data); assert_eq!(decls.len(), 1); - let decl = expect_buffer_decl("c.rs", &decls[0], &project, cx); + let decl = expect_buffer_decl("c.rs", &decls[0].1, &project, cx); assert_eq!(decl.identifier, test_process_data); let parent_id = decl.parent.unwrap(); @@ -676,11 +699,11 @@ mod tests { cx.update(|cx| { let decls = index_state.declarations_for_identifier::<8>(&main); assert_eq!(decls.len(), 2); - let decl = expect_buffer_decl("c.rs", &decls[0], &project, cx); + let decl = expect_buffer_decl("c.rs", &decls[0].1, &project, cx); assert_eq!(decl.identifier, main); assert_eq!(decl.item_range.to_offset(&buffer.read(cx)), 32..280); - expect_file_decl("a.rs", &decls[1], &project, cx); + expect_file_decl("a.rs", &decls[1].1, &project, cx); }); } @@ -695,8 +718,8 @@ mod tests { cx.update(|cx| { let decls = index_state.declarations_for_identifier::<8>(&main); assert_eq!(decls.len(), 2); - expect_file_decl("c.rs", &decls[0], &project, cx); - expect_file_decl("a.rs", &decls[1], &project, cx); + expect_file_decl("c.rs", &decls[0].1, &project, cx); + expect_file_decl("a.rs", &decls[1].1, &project, cx); }); } diff --git a/crates/edit_prediction_context/src/text_similarity.rs b/crates/edit_prediction_context/src/text_similarity.rs index f7a9822ecca01f1b6ff1dc04bdc12fbcddc5159b..2ace7bf10cc6fd13b8a5636212211a3274d3c259 100644 --- a/crates/edit_prediction_context/src/text_similarity.rs +++ b/crates/edit_prediction_context/src/text_similarity.rs @@ -9,8 +9,12 @@ use crate::reference::Reference; // That implementation could actually be more efficient - no need to track words in the window that // are not in the query. +// TODO: Consider a flat sorted Vec<(String, usize)> representation. Intersection can just walk the +// two in parallel. + static IDENTIFIER_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"\b\w+\b").unwrap()); +// TODO: use &str or Cow keys? #[derive(Debug)] pub struct IdentifierOccurrences { identifier_to_count: HashMap, diff --git a/crates/edit_prediction_tools/src/edit_prediction_tools.rs b/crates/edit_prediction_tools/src/edit_prediction_tools.rs index f00a16e026704f1d1da318956f41128a9783a54c..7e18b468bc6aac5ab53a9ce55f195644e19e9367 100644 --- a/crates/edit_prediction_tools/src/edit_prediction_tools.rs +++ b/crates/edit_prediction_tools/src/edit_prediction_tools.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, str::FromStr, sync::Arc, - time::Duration, + time::{Duration, Instant}, }; use collections::HashMap; @@ -195,6 +195,8 @@ impl EditPredictionTools { .timer(Duration::from_millis(50)) .await; + let mut start_time = None; + let Ok(task) = this.update(cx, |this, cx| { fn number_input_value( input: &Entity, @@ -216,15 +218,16 @@ impl EditPredictionTools { &this.cursor_context_ratio_input, cx, ), - // TODO Display and add to options - include_parent_signatures: false, }; - EditPredictionContext::gather( + start_time = Some(Instant::now()); + + // TODO use global zeta instead + EditPredictionContext::gather_context_in_background( cursor_position, current_buffer_snapshot, options, - this.syntax_index.clone(), + Some(this.syntax_index.clone()), cx, ) }) else { @@ -243,6 +246,7 @@ impl EditPredictionTools { .ok(); return; }; + let retrieval_duration = start_time.unwrap().elapsed(); let mut languages = HashMap::default(); for snippet in context.snippets.iter() { @@ -320,7 +324,7 @@ impl EditPredictionTools { this.last_context = Some(ContextState { context_editor, - retrieval_duration: context.retrieval_duration, + retrieval_duration, }); cx.notify(); }) diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index 6052afee671edba49e05b56ddef147a01866e364..24a3de0c3b918e86488c42d9d12fedb8e16081c6 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -84,6 +84,17 @@ pub enum EditPredictionProvider { Zed, } +impl EditPredictionProvider { + pub fn is_zed(&self) -> bool { + match self { + EditPredictionProvider::Zed => true, + EditPredictionProvider::None + | EditPredictionProvider::Copilot + | EditPredictionProvider::Supermaven => false, + } + } +} + /// The contents of the edit prediction settings. #[skip_serializing_none] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 5b6cb3924610b89406a37230497fee8ffc511e34..3d7bbe8642c64d7cfff9bfb9169c3df17e18d854 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -163,6 +163,7 @@ workspace.workspace = true zed_actions.workspace = true zed_env_vars.workspace = true zeta.workspace = true +zeta2.workspace = true zlog.workspace = true zlog_settings.workspace = true diff --git a/crates/zed/src/zed/edit_prediction_registry.rs b/crates/zed/src/zed/edit_prediction_registry.rs index ae26427fc6547079b163235f5d1c3df26a489795..d0e8e26074296e5b54bccaa73de7e06e4aacf205 100644 --- a/crates/zed/src/zed/edit_prediction_registry.rs +++ b/crates/zed/src/zed/edit_prediction_registry.rs @@ -203,21 +203,43 @@ fn assign_edit_prediction_provider( } } - let zeta = zeta::Zeta::register(worktree, client.clone(), user_store, cx); - - if let Some(buffer) = &singleton_buffer - && buffer.read(cx).file().is_some() - && let Some(project) = editor.project() - { - zeta.update(cx, |zeta, cx| { - zeta.register_buffer(buffer, project, cx); + if std::env::var("ZED_ZETA2").is_ok() { + let zeta = zeta2::Zeta::global(client, &user_store, cx); + let provider = cx.new(|cx| { + zeta2::ZetaEditPredictionProvider::new( + editor.project(), + &client, + &user_store, + cx, + ) }); - } - let provider = - cx.new(|_| zeta::ZetaEditPredictionProvider::new(zeta, singleton_buffer)); + if let Some(buffer) = &singleton_buffer + && buffer.read(cx).file().is_some() + && let Some(project) = editor.project() + { + zeta.update(cx, |zeta, cx| { + zeta.register_buffer(buffer, project, cx); + }); + } - editor.set_edit_prediction_provider(Some(provider), window, cx); + editor.set_edit_prediction_provider(Some(provider), window, cx); + } else { + let zeta = zeta::Zeta::register(worktree, client.clone(), user_store, cx); + + if let Some(buffer) = &singleton_buffer + && buffer.read(cx).file().is_some() + && let Some(project) = editor.project() + { + zeta.update(cx, |zeta, cx| { + zeta.register_buffer(buffer, project, cx); + }); + } + + let provider = + cx.new(|_| zeta::ZetaEditPredictionProvider::new(zeta, singleton_buffer)); + editor.set_edit_prediction_provider(Some(provider), window, cx); + } } } } diff --git a/crates/zeta2/Cargo.toml b/crates/zeta2/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..61c560baab543baf9d57b034807fe60cb566b24f --- /dev/null +++ b/crates/zeta2/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "zeta2" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/zeta2.rs" + +[dependencies] +anyhow.workspace = true +arrayvec.workspace = true +client.workspace = true +cloud_llm_client.workspace = true +edit_prediction.workspace = true +edit_prediction_context.workspace = true +futures.workspace = true +gpui.workspace = true +language.workspace = true +language_model.workspace = true +log.workspace = true +project.workspace = true +release_channel.workspace = true +serde_json.workspace = true +thiserror.workspace = true +util.workspace = true +uuid.workspace = true +workspace.workspace = true +workspace-hack.workspace = true +worktree.workspace = true + +[dev-dependencies] +gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/zeta2/LICENSE-GPL b/crates/zeta2/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/zeta2/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs new file mode 100644 index 0000000000000000000000000000000000000000..791273a9242dd7aa50588fcfe90e9258ff3724ea --- /dev/null +++ b/crates/zeta2/src/zeta2.rs @@ -0,0 +1,1130 @@ +use anyhow::{Context as _, Result, anyhow}; +use arrayvec::ArrayVec; +use client::{Client, EditPredictionUsage, UserStore}; +use cloud_llm_client::predict_edits_v3::{self, Signature}; +use cloud_llm_client::{ + EXPIRED_LLM_TOKEN_HEADER_NAME, MINIMUM_REQUIRED_VERSION_HEADER_NAME, ZED_VERSION_HEADER_NAME, +}; +use edit_prediction::{DataCollectionState, Direction, EditPredictionProvider}; +use edit_prediction_context::{ + DeclarationId, EditPredictionContext, EditPredictionExcerptOptions, SyntaxIndex, + SyntaxIndexState, +}; +use futures::AsyncReadExt as _; +use gpui::http_client::Method; +use gpui::{ + App, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task, http_client, + prelude::*, +}; +use language::{Anchor, Buffer, OffsetRangeExt as _, ToPoint}; +use language::{BufferSnapshot, EditPreview}; +use language_model::{LlmApiToken, RefreshLlmTokenListener}; +use project::Project; +use release_channel::AppVersion; +use std::cmp; +use std::collections::{HashMap, VecDeque, hash_map}; +use std::path::PathBuf; +use std::str::FromStr as _; +use std::time::{Duration, Instant}; +use std::{ops::Range, sync::Arc}; +use thiserror::Error; +use util::ResultExt as _; +use uuid::Uuid; +use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification}; + +const BUFFER_CHANGE_GROUPING_INTERVAL: Duration = Duration::from_secs(1); + +/// Maximum number of events to track. +const MAX_EVENT_COUNT: usize = 16; + +#[derive(Clone)] +struct ZetaGlobal(Entity); + +impl Global for ZetaGlobal {} + +pub struct Zeta { + client: Arc, + user_store: Entity, + llm_token: LlmApiToken, + _llm_token_subscription: Subscription, + projects: HashMap, + excerpt_options: EditPredictionExcerptOptions, + update_required: bool, +} + +struct ZetaProject { + syntax_index: Entity, + events: VecDeque, + registered_buffers: HashMap, +} + +struct RegisteredBuffer { + snapshot: BufferSnapshot, + _subscriptions: [gpui::Subscription; 2], +} + +#[derive(Clone)] +pub enum Event { + BufferChange { + old_snapshot: BufferSnapshot, + new_snapshot: BufferSnapshot, + timestamp: Instant, + }, +} + +impl Zeta { + pub fn global( + client: &Arc, + user_store: &Entity, + cx: &mut App, + ) -> Entity { + cx.try_global::() + .map(|global| global.0.clone()) + .unwrap_or_else(|| { + let zeta = cx.new(|cx| Self::new(client.clone(), user_store.clone(), cx)); + cx.set_global(ZetaGlobal(zeta.clone())); + zeta + }) + } + + fn new(client: Arc, user_store: Entity, cx: &mut Context) -> Self { + let refresh_llm_token_listener = RefreshLlmTokenListener::global(cx); + + Self { + projects: HashMap::new(), + client, + user_store, + excerpt_options: EditPredictionExcerptOptions { + max_bytes: 512, + min_bytes: 128, + target_before_cursor_over_total_bytes: 0.5, + }, + llm_token: LlmApiToken::default(), + _llm_token_subscription: cx.subscribe( + &refresh_llm_token_listener, + |this, _listener, _event, cx| { + let client = this.client.clone(); + let llm_token = this.llm_token.clone(); + cx.spawn(async move |_this, _cx| { + llm_token.refresh(&client).await?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + }, + ), + update_required: false, + } + } + + pub fn usage(&self, cx: &App) -> Option { + self.user_store.read(cx).edit_prediction_usage() + } + + pub fn register_project(&mut self, project: &Entity, cx: &mut App) { + self.get_or_init_zeta_project(project, cx); + } + + pub fn register_buffer( + &mut self, + buffer: &Entity, + project: &Entity, + cx: &mut Context, + ) { + let zeta_project = self.get_or_init_zeta_project(project, cx); + Self::register_buffer_impl(zeta_project, buffer, project, cx); + } + + fn get_or_init_zeta_project( + &mut self, + project: &Entity, + cx: &mut App, + ) -> &mut ZetaProject { + self.projects + .entry(project.entity_id()) + .or_insert_with(|| ZetaProject { + syntax_index: cx.new(|cx| SyntaxIndex::new(project, cx)), + events: VecDeque::new(), + registered_buffers: HashMap::new(), + }) + } + + fn register_buffer_impl<'a>( + zeta_project: &'a mut ZetaProject, + buffer: &Entity, + project: &Entity, + cx: &mut Context, + ) -> &'a mut RegisteredBuffer { + let buffer_id = buffer.entity_id(); + match zeta_project.registered_buffers.entry(buffer_id) { + hash_map::Entry::Occupied(entry) => entry.into_mut(), + hash_map::Entry::Vacant(entry) => { + let snapshot = buffer.read(cx).snapshot(); + let project_entity_id = project.entity_id(); + entry.insert(RegisteredBuffer { + snapshot, + _subscriptions: [ + cx.subscribe(buffer, { + let project = project.downgrade(); + move |this, buffer, event, cx| { + if let language::BufferEvent::Edited = event + && let Some(project) = project.upgrade() + { + this.report_changes_for_buffer(&buffer, &project, cx); + } + } + }), + cx.observe_release(buffer, move |this, _buffer, _cx| { + let Some(zeta_project) = this.projects.get_mut(&project_entity_id) + else { + return; + }; + zeta_project.registered_buffers.remove(&buffer_id); + }), + ], + }) + } + } + } + + fn report_changes_for_buffer( + &mut self, + buffer: &Entity, + project: &Entity, + cx: &mut Context, + ) -> BufferSnapshot { + let zeta_project = self.get_or_init_zeta_project(project, cx); + let registered_buffer = Self::register_buffer_impl(zeta_project, buffer, project, cx); + + let new_snapshot = buffer.read(cx).snapshot(); + if new_snapshot.version != registered_buffer.snapshot.version { + let old_snapshot = + std::mem::replace(&mut registered_buffer.snapshot, new_snapshot.clone()); + Self::push_event( + zeta_project, + Event::BufferChange { + old_snapshot, + new_snapshot: new_snapshot.clone(), + timestamp: Instant::now(), + }, + ); + } + + new_snapshot + } + + fn push_event(zeta_project: &mut ZetaProject, event: Event) { + let events = &mut zeta_project.events; + + if let Some(Event::BufferChange { + new_snapshot: last_new_snapshot, + timestamp: last_timestamp, + .. + }) = events.back_mut() + { + // Coalesce edits for the same buffer when they happen one after the other. + let Event::BufferChange { + old_snapshot, + new_snapshot, + timestamp, + } = &event; + + if timestamp.duration_since(*last_timestamp) <= BUFFER_CHANGE_GROUPING_INTERVAL + && old_snapshot.remote_id() == last_new_snapshot.remote_id() + && old_snapshot.version == last_new_snapshot.version + { + *last_new_snapshot = new_snapshot.clone(); + *last_timestamp = *timestamp; + return; + } + } + + if events.len() >= MAX_EVENT_COUNT { + // These are halved instead of popping to improve prompt caching. + events.drain(..MAX_EVENT_COUNT / 2); + } + + events.push_back(event); + } + + pub fn request_prediction( + &mut self, + project: &Entity, + buffer: &Entity, + position: language::Anchor, + cx: &mut Context, + ) -> Task>> { + let project_state = self.projects.get(&project.entity_id()); + + let index_state = project_state.map(|state| { + state + .syntax_index + .read_with(cx, |index, _cx| index.state().clone()) + }); + let excerpt_options = self.excerpt_options.clone(); + let snapshot = buffer.read(cx).snapshot(); + let Some(excerpt_path) = snapshot.file().map(|path| path.full_path(cx)) else { + return Task::ready(Err(anyhow!("No file path for excerpt"))); + }; + let client = self.client.clone(); + let llm_token = self.llm_token.clone(); + let app_version = AppVersion::global(cx); + let worktree_snapshots = project + .read(cx) + .worktrees(cx) + .map(|worktree| worktree.read(cx).snapshot()) + .collect::>(); + + let request_task = cx.background_spawn({ + let snapshot = snapshot.clone(); + async move { + let index_state = if let Some(index_state) = index_state { + Some(index_state.lock_owned().await) + } else { + None + }; + + let cursor_point = position.to_point(&snapshot); + + // TODO: make this only true if debug view is open + let debug_info = true; + + let Some(request) = EditPredictionContext::gather_context( + cursor_point, + &snapshot, + &excerpt_options, + index_state.as_deref(), + ) + .map(|context| { + make_cloud_request( + excerpt_path.clone(), + context, + // TODO pass everything + Vec::new(), + false, + Vec::new(), + None, + debug_info, + &worktree_snapshots, + index_state.as_deref(), + ) + }) else { + return Ok(None); + }; + + anyhow::Ok(Some( + Self::perform_request(client, llm_token, app_version, request).await?, + )) + } + }); + + let buffer = buffer.clone(); + + cx.spawn(async move |this, cx| { + match request_task.await { + Ok(Some((response, usage))) => { + log::debug!("predicted edits: {:?}", &response.edits); + + if let Some(usage) = usage { + this.update(cx, |this, cx| { + this.user_store.update(cx, |user_store, cx| { + user_store.update_edit_prediction_usage(usage, cx); + }); + }) + .ok(); + } + + // TODO telemetry: duration, etc + + // TODO produce smaller edits by diffing against snapshot first + // + // Cloud returns entire snippets/excerpts ranges as they were included + // in the request, but we should display smaller edits to the user. + // + // We can do this by computing a diff of each one against the snapshot. + // Similar to zeta::Zeta::compute_edits, but per edit. + let edits = response + .edits + .into_iter() + .map(|edit| { + // TODO edits to different files + ( + snapshot.anchor_before(edit.range.start) + ..snapshot.anchor_before(edit.range.end), + edit.content, + ) + }) + .collect::>() + .into(); + + let Some((edits, snapshot, edit_preview_task)) = + buffer.read_with(cx, |buffer, cx| { + let new_snapshot = buffer.snapshot(); + let edits: Arc<[_]> = + interpolate(&snapshot, &new_snapshot, edits)?.into(); + Some((edits.clone(), new_snapshot, buffer.preview_edits(edits, cx))) + })? + else { + return Ok(None); + }; + + Ok(Some(EditPrediction { + id: EditPredictionId(response.request_id), + edits, + snapshot, + edit_preview: edit_preview_task.await, + })) + } + Ok(None) => Ok(None), + Err(err) => { + if err.is::() { + cx.update(|cx| { + this.update(cx, |this, _cx| { + this.update_required = true; + }) + .ok(); + + let error_message: SharedString = err.to_string().into(); + show_app_notification( + NotificationId::unique::(), + cx, + move |cx| { + cx.new(|cx| { + ErrorMessagePrompt::new(error_message.clone(), cx) + .with_link_button( + "Update Zed", + "https://zed.dev/releases", + ) + }) + }, + ); + }) + .ok(); + } + + Err(err) + } + } + }) + } + + async fn perform_request( + client: Arc, + llm_token: LlmApiToken, + app_version: SemanticVersion, + request: predict_edits_v3::PredictEditsRequest, + ) -> Result<( + predict_edits_v3::PredictEditsResponse, + Option, + )> { + let http_client = client.http_client(); + let mut token = llm_token.acquire(&client).await?; + let mut did_retry = false; + + loop { + let request_builder = http_client::Request::builder().method(Method::POST); + let request_builder = + if let Ok(predict_edits_url) = std::env::var("ZED_PREDICT_EDITS_URL") { + request_builder.uri(predict_edits_url) + } else { + request_builder.uri( + http_client + .build_zed_llm_url("/predict_edits/v3", &[])? + .as_ref(), + ) + }; + let request = request_builder + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {}", token)) + .header(ZED_VERSION_HEADER_NAME, app_version.to_string()) + .body(serde_json::to_string(&request)?.into())?; + + let mut response = http_client.send(request).await?; + + if let Some(minimum_required_version) = response + .headers() + .get(MINIMUM_REQUIRED_VERSION_HEADER_NAME) + .and_then(|version| SemanticVersion::from_str(version.to_str().ok()?).ok()) + { + anyhow::ensure!( + app_version >= minimum_required_version, + ZedUpdateRequiredError { + minimum_version: minimum_required_version + } + ); + } + + if response.status().is_success() { + let usage = EditPredictionUsage::from_headers(response.headers()).ok(); + + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await?; + return Ok((serde_json::from_slice(&body)?, usage)); + } else if !did_retry + && response + .headers() + .get(EXPIRED_LLM_TOKEN_HEADER_NAME) + .is_some() + { + did_retry = true; + token = llm_token.refresh(&client).await?; + } else { + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + anyhow::bail!( + "error predicting edits.\nStatus: {:?}\nBody: {}", + response.status(), + body + ); + } + } + } +} + +#[derive(Error, Debug)] +#[error( + "You must update to Zed version {minimum_version} or higher to continue using edit predictions." +)] +pub struct ZedUpdateRequiredError { + minimum_version: SemanticVersion, +} + +pub struct ZetaEditPredictionProvider { + zeta: Entity, + current_prediction: Option, + next_pending_prediction_id: usize, + pending_predictions: ArrayVec, + last_request_timestamp: Instant, +} + +impl ZetaEditPredictionProvider { + pub const THROTTLE_TIMEOUT: Duration = Duration::from_millis(300); + + pub fn new( + project: Option<&Entity>, + client: &Arc, + user_store: &Entity, + cx: &mut App, + ) -> Self { + let zeta = Zeta::global(client, user_store, cx); + if let Some(project) = project { + zeta.update(cx, |zeta, cx| { + zeta.register_project(project, cx); + }); + } + + Self { + zeta, + current_prediction: None, + next_pending_prediction_id: 0, + pending_predictions: ArrayVec::new(), + last_request_timestamp: Instant::now(), + } + } +} + +#[derive(Clone)] +struct CurrentEditPrediction { + buffer_id: EntityId, + prediction: EditPrediction, +} + +impl CurrentEditPrediction { + fn should_replace_prediction(&self, old_prediction: &Self, snapshot: &BufferSnapshot) -> bool { + if self.buffer_id != old_prediction.buffer_id { + return true; + } + + let Some(old_edits) = old_prediction.prediction.interpolate(snapshot) else { + return true; + }; + let Some(new_edits) = self.prediction.interpolate(snapshot) else { + return false; + }; + + if old_edits.len() == 1 && new_edits.len() == 1 { + let (old_range, old_text) = &old_edits[0]; + let (new_range, new_text) = &new_edits[0]; + new_range == old_range && new_text.starts_with(old_text) + } else { + true + } + } +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] +pub struct EditPredictionId(Uuid); + +impl From for gpui::ElementId { + fn from(value: EditPredictionId) -> Self { + gpui::ElementId::Uuid(value.0) + } +} + +impl std::fmt::Display for EditPredictionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Clone)] +pub struct EditPrediction { + id: EditPredictionId, + edits: Arc<[(Range, String)]>, + snapshot: BufferSnapshot, + edit_preview: EditPreview, +} + +impl EditPrediction { + fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option, String)>> { + interpolate(&self.snapshot, new_snapshot, self.edits.clone()) + } +} + +struct PendingPrediction { + id: usize, + _task: Task<()>, +} + +impl EditPredictionProvider for ZetaEditPredictionProvider { + fn name() -> &'static str { + "zed-predict2" + } + + fn display_name() -> &'static str { + "Zed's Edit Predictions 2" + } + + fn show_completions_in_menu() -> bool { + true + } + + fn show_tab_accept_marker() -> bool { + true + } + + fn data_collection_state(&self, _cx: &App) -> DataCollectionState { + // TODO [zeta2] + DataCollectionState::Unsupported + } + + fn toggle_data_collection(&mut self, _cx: &mut App) { + // TODO [zeta2] + } + + fn usage(&self, cx: &App) -> Option { + self.zeta.read(cx).usage(cx) + } + + fn is_enabled( + &self, + _buffer: &Entity, + _cursor_position: language::Anchor, + _cx: &App, + ) -> bool { + true + } + + fn is_refreshing(&self) -> bool { + !self.pending_predictions.is_empty() + } + + fn refresh( + &mut self, + project: Option>, + buffer: Entity, + cursor_position: language::Anchor, + _debounce: bool, + cx: &mut Context, + ) { + let Some(project) = project else { + return; + }; + + if self + .zeta + .read(cx) + .user_store + .read_with(cx, |user_store, _cx| { + user_store.account_too_young() || user_store.has_overdue_invoices() + }) + { + return; + } + + if let Some(current_prediction) = self.current_prediction.as_ref() { + let snapshot = buffer.read(cx).snapshot(); + if current_prediction + .prediction + .interpolate(&snapshot) + .is_some() + { + return; + } + } + + let pending_prediction_id = self.next_pending_prediction_id; + self.next_pending_prediction_id += 1; + let last_request_timestamp = self.last_request_timestamp; + + let task = cx.spawn(async move |this, cx| { + if let Some(timeout) = (last_request_timestamp + Self::THROTTLE_TIMEOUT) + .checked_duration_since(Instant::now()) + { + cx.background_executor().timer(timeout).await; + } + + let prediction_request = this.update(cx, |this, cx| { + this.last_request_timestamp = Instant::now(); + this.zeta.update(cx, |zeta, cx| { + zeta.request_prediction(&project, &buffer, cursor_position, cx) + }) + }); + + let prediction = match prediction_request { + Ok(prediction_request) => { + let prediction_request = prediction_request.await; + prediction_request.map(|c| { + c.map(|prediction| CurrentEditPrediction { + buffer_id: buffer.entity_id(), + prediction, + }) + }) + } + Err(error) => Err(error), + }; + + this.update(cx, |this, cx| { + if this.pending_predictions[0].id == pending_prediction_id { + this.pending_predictions.remove(0); + } else { + this.pending_predictions.clear(); + } + + let Some(new_prediction) = prediction + .context("edit prediction failed") + .log_err() + .flatten() + else { + cx.notify(); + return; + }; + + if let Some(old_prediction) = this.current_prediction.as_ref() { + let snapshot = buffer.read(cx).snapshot(); + if new_prediction.should_replace_prediction(old_prediction, &snapshot) { + this.current_prediction = Some(new_prediction); + } + } else { + this.current_prediction = Some(new_prediction); + } + + cx.notify(); + }) + .ok(); + }); + + // We always maintain at most two pending predictions. When we already + // have two, we replace the newest one. + if self.pending_predictions.len() <= 1 { + self.pending_predictions.push(PendingPrediction { + id: pending_prediction_id, + _task: task, + }); + } else if self.pending_predictions.len() == 2 { + self.pending_predictions.pop(); + self.pending_predictions.push(PendingPrediction { + id: pending_prediction_id, + _task: task, + }); + } + + cx.notify(); + } + + fn cycle( + &mut self, + _buffer: Entity, + _cursor_position: language::Anchor, + _direction: Direction, + _cx: &mut Context, + ) { + } + + fn accept(&mut self, _cx: &mut Context) { + // TODO [zeta2] report accept + self.current_prediction.take(); + self.pending_predictions.clear(); + } + + fn discard(&mut self, _cx: &mut Context) { + self.pending_predictions.clear(); + self.current_prediction.take(); + } + + fn suggest( + &mut self, + buffer: &Entity, + cursor_position: language::Anchor, + cx: &mut Context, + ) -> Option { + let CurrentEditPrediction { + buffer_id, + prediction, + .. + } = self.current_prediction.as_mut()?; + + // Invalidate previous prediction if it was generated for a different buffer. + if *buffer_id != buffer.entity_id() { + self.current_prediction.take(); + return None; + } + + let buffer = buffer.read(cx); + let Some(edits) = prediction.interpolate(&buffer.snapshot()) else { + self.current_prediction.take(); + return None; + }; + + let cursor_row = cursor_position.to_point(buffer).row; + let (closest_edit_ix, (closest_edit_range, _)) = + edits.iter().enumerate().min_by_key(|(_, (range, _))| { + let distance_from_start = cursor_row.abs_diff(range.start.to_point(buffer).row); + let distance_from_end = cursor_row.abs_diff(range.end.to_point(buffer).row); + cmp::min(distance_from_start, distance_from_end) + })?; + + let mut edit_start_ix = closest_edit_ix; + for (range, _) in edits[..edit_start_ix].iter().rev() { + let distance_from_closest_edit = + closest_edit_range.start.to_point(buffer).row - range.end.to_point(buffer).row; + if distance_from_closest_edit <= 1 { + edit_start_ix -= 1; + } else { + break; + } + } + + let mut edit_end_ix = closest_edit_ix + 1; + for (range, _) in &edits[edit_end_ix..] { + let distance_from_closest_edit = + range.start.to_point(buffer).row - closest_edit_range.end.to_point(buffer).row; + if distance_from_closest_edit <= 1 { + edit_end_ix += 1; + } else { + break; + } + } + + Some(edit_prediction::EditPrediction { + id: Some(prediction.id.to_string().into()), + edits: edits[edit_start_ix..edit_end_ix].to_vec(), + edit_preview: Some(prediction.edit_preview.clone()), + }) + } +} + +fn make_cloud_request( + excerpt_path: PathBuf, + context: EditPredictionContext, + events: Vec, + can_collect_data: bool, + diagnostic_groups: Vec, + git_info: Option, + debug_info: bool, + worktrees: &Vec, + index_state: Option<&SyntaxIndexState>, +) -> predict_edits_v3::PredictEditsRequest { + let mut signatures = Vec::new(); + let mut declaration_to_signature_index = HashMap::default(); + let mut referenced_declarations = Vec::new(); + + for snippet in context.snippets { + let project_entry_id = snippet.declaration.project_entry_id(); + // TODO: Use full paths (worktree rooted) - need to move full_path method to the snapshot. + // Note that currently full_path is currently being used for excerpt_path. + let Some(path) = worktrees.iter().find_map(|worktree| { + let abs_path = worktree.abs_path(); + worktree + .entry_for_id(project_entry_id) + .map(|e| abs_path.join(&e.path)) + }) else { + continue; + }; + + let parent_index = index_state.and_then(|index_state| { + snippet.declaration.parent().and_then(|parent| { + add_signature( + parent, + &mut declaration_to_signature_index, + &mut signatures, + index_state, + ) + }) + }); + + let (text, text_is_truncated) = snippet.declaration.item_text(); + referenced_declarations.push(predict_edits_v3::ReferencedDeclaration { + path, + text: text.into(), + range: snippet.declaration.item_range(), + text_is_truncated, + signature_range: snippet.declaration.signature_range_in_item_text(), + parent_index, + score_components: snippet.score_components, + signature_score: snippet.scores.signature, + declaration_score: snippet.scores.declaration, + }); + } + + let excerpt_parent = index_state.and_then(|index_state| { + context + .excerpt + .parent_declarations + .last() + .and_then(|(parent, _)| { + add_signature( + *parent, + &mut declaration_to_signature_index, + &mut signatures, + index_state, + ) + }) + }); + + predict_edits_v3::PredictEditsRequest { + excerpt_path, + excerpt: context.excerpt_text.body, + excerpt_range: context.excerpt.range, + cursor_offset: context.cursor_offset_in_excerpt, + referenced_declarations, + signatures, + excerpt_parent, + events, + can_collect_data, + diagnostic_groups, + git_info, + debug_info, + } +} + +fn add_signature( + declaration_id: DeclarationId, + declaration_to_signature_index: &mut HashMap, + signatures: &mut Vec, + index: &SyntaxIndexState, +) -> Option { + if let Some(signature_index) = declaration_to_signature_index.get(&declaration_id) { + return Some(*signature_index); + } + let Some(parent_declaration) = index.declaration(declaration_id) else { + log::error!("bug: missing parent declaration"); + return None; + }; + let parent_index = parent_declaration.parent().and_then(|parent| { + add_signature(parent, declaration_to_signature_index, signatures, index) + }); + let (text, text_is_truncated) = parent_declaration.signature_text(); + let signature_index = signatures.len(); + signatures.push(Signature { + text: text.into(), + text_is_truncated, + parent_index, + }); + declaration_to_signature_index.insert(declaration_id, signature_index); + Some(signature_index) +} + +fn interpolate( + old_snapshot: &BufferSnapshot, + new_snapshot: &BufferSnapshot, + current_edits: Arc<[(Range, String)]>, +) -> Option, String)>> { + let mut edits = Vec::new(); + + let mut model_edits = current_edits.iter().peekable(); + for user_edit in new_snapshot.edits_since::(&old_snapshot.version) { + while let Some((model_old_range, _)) = model_edits.peek() { + let model_old_range = model_old_range.to_offset(old_snapshot); + if model_old_range.end < user_edit.old.start { + let (model_old_range, model_new_text) = model_edits.next().unwrap(); + edits.push((model_old_range.clone(), model_new_text.clone())); + } else { + break; + } + } + + if let Some((model_old_range, model_new_text)) = model_edits.peek() { + let model_old_offset_range = model_old_range.to_offset(old_snapshot); + if user_edit.old == model_old_offset_range { + let user_new_text = new_snapshot + .text_for_range(user_edit.new.clone()) + .collect::(); + + if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) { + if !model_suffix.is_empty() { + let anchor = old_snapshot.anchor_after(user_edit.old.end); + edits.push((anchor..anchor, model_suffix.to_string())); + } + + model_edits.next(); + continue; + } + } + } + + return None; + } + + edits.extend(model_edits.cloned()); + + if edits.is_empty() { None } else { Some(edits) } +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::TestAppContext; + use language::ToOffset as _; + + #[gpui::test] + async fn test_edit_prediction_basic_interpolation(cx: &mut TestAppContext) { + let buffer = cx.new(|cx| Buffer::local("Lorem ipsum dolor", cx)); + let edits: Arc<[(Range, String)]> = cx.update(|cx| { + to_prediction_edits( + [(2..5, "REM".to_string()), (9..11, "".to_string())], + &buffer, + cx, + ) + .into() + }); + + let edit_preview = cx + .read(|cx| buffer.read(cx).preview_edits(edits.clone(), cx)) + .await; + + let prediction = EditPrediction { + id: EditPredictionId(Uuid::new_v4()), + edits, + snapshot: cx.read(|cx| buffer.read(cx).snapshot()), + edit_preview, + }; + + cx.update(|cx| { + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(2..5, "REM".to_string()), (9..11, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(2..2, "REM".to_string()), (6..8, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.undo(cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(2..5, "REM".to_string()), (9..11, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "R")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(3..3, "EM".to_string()), (7..9, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "E")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(4..4, "M".to_string()), (8..10, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "M")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(9..11, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(4..4, "M".to_string()), (8..10, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(8..10, "")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(4..4, "M".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(4..6, "")], None, cx)); + assert_eq!(prediction.interpolate(&buffer.read(cx).snapshot()), None); + }) + } + + fn to_prediction_edits( + iterator: impl IntoIterator, String)>, + buffer: &Entity, + cx: &App, + ) -> Vec<(Range, String)> { + let buffer = buffer.read(cx); + iterator + .into_iter() + .map(|(range, text)| { + ( + buffer.anchor_after(range.start)..buffer.anchor_before(range.end), + text, + ) + }) + .collect() + } + + fn from_prediction_edits( + editor_edits: &[(Range, String)], + buffer: &Entity, + cx: &App, + ) -> Vec<(Range, String)> { + let buffer = buffer.read(cx); + editor_edits + .iter() + .map(|(range, text)| { + ( + range.start.to_offset(buffer)..range.end.to_offset(buffer), + text.clone(), + ) + }) + .collect() + } +} From e9fbcf5abf6d7b72c583cb09816a2975954290ba Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 22 Sep 2025 16:26:47 -0600 Subject: [PATCH 194/721] Allow `zed filename.rs:` (#38677) iTerm's editor configuration dialog allows you to set your editor to `zed \1:\2`, but not (as far as I know) to leave off the : when there's no line number This fixes clicking on bare filenames in iTerm for me. Release Notes: - Fixed line number parsing so that `zed filename.rs:` will now act as though you did `zed filename.rs` --- crates/util/src/paths.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 72753b026e2194e0b083acb1f9d6d69864286c6b..7d16ee0b5ec88362fe45ab8ff594eb13c446417f 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -379,6 +379,8 @@ const ROW_COL_CAPTURE_REGEX: &str = r"(?xs) \:+(\d+)\:(\d+)\:*$ # filename:row:column | \:+(\d+)\:*()$ # filename:row + | + \:+()()$ )"; /// A representation of a path-like string with optional row and column numbers. @@ -455,8 +457,8 @@ impl PathWithPosition { /// row: None, /// column: None, /// }); - /// assert_eq!(PathWithPosition::parse_str("test_file.rs::"), PathWithPosition { - /// path: PathBuf::from("test_file.rs::"), + /// assert_eq!(PathWithPosition::parse_str("test_file.rs"), PathWithPosition { + /// path: PathBuf::from("test_file.rs"), /// row: None, /// column: None, /// }); @@ -998,7 +1000,7 @@ mod tests { assert_eq!( PathWithPosition::parse_str("test_file.rs:"), PathWithPosition { - path: PathBuf::from("test_file.rs:"), + path: PathBuf::from("test_file.rs"), row: None, column: None } From 5e502a32fbc5aa5b3bb63183513e7e6d699bccc0 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Mon, 22 Sep 2025 17:30:27 -0500 Subject: [PATCH 195/721] Fix remote server crash with JSON files (#38678) Closes #38594 Release Notes: - N/A --- crates/languages/src/json.rs | 16 ++++++++++------ crates/theme/src/registry.rs | 5 +++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 7e698cbf095b5679aefda4230b2b5b08c24b0825..482c709d453c520486990409a37c27a50132dd77 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -157,12 +157,16 @@ impl JsonLspAdapter { ) -> Value { let keymap_schema = KeymapFile::generate_json_schema_for_registered_actions(cx); let font_names = &cx.text_system().all_font_names(); - let theme_names = &ThemeRegistry::global(cx).list_names(); - let icon_theme_names = &ThemeRegistry::global(cx) - .list_icon_themes() - .into_iter() - .map(|icon_theme| icon_theme.name) - .collect::>(); + let themes = ThemeRegistry::try_global(cx); + let theme_names = &themes.clone().map(|t| t.list_names()).unwrap_or_default(); + let icon_theme_names = &themes + .map(|t| { + t.list_icon_themes() + .into_iter() + .map(|icon_theme| icon_theme.name) + .collect::>() + }) + .unwrap_or_default(); let settings_schema = cx .global::() .json_schema(&SettingsJsonSchemaParams { diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 8bf8481c84c9c48cbbd03c472b785a776aa07dac..c362b62704257fefde125e81ca1c056490263b0b 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -73,6 +73,11 @@ impl ThemeRegistry { cx.default_global::().0.clone() } + /// Returns the global [`ThemeRegistry`] if it exists. + pub fn try_global(cx: &mut App) -> Option> { + cx.try_global::().map(|t| t.0.clone()) + } + /// Sets the global [`ThemeRegistry`]. pub(crate) fn set_global(assets: Box, cx: &mut App) { cx.set_global(GlobalThemeRegistry(Arc::new(ThemeRegistry::new(assets)))); From 681a4adc42df0c93b95fd1a36a3ad47eaca086c0 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Mon, 22 Sep 2025 16:57:41 -0600 Subject: [PATCH 196/721] Remove `OutlineItem::signature_range` as it is no longer used (#38680) Use in edit predictions was removed in #38676 Release Notes: - N/A --- crates/language/src/buffer.rs | 19 ------------------- crates/language/src/outline.rs | 9 --------- crates/multi_buffer/src/multi_buffer.rs | 12 ------------ crates/outline_panel/src/outline_panel.rs | 1 - 4 files changed, 41 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 311ef4d55b947888cd7fbc6706a9bd581f2dd27d..99eae4c2028a9bd3aa82a4facb9e2099e8b5dea4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -3894,9 +3894,6 @@ impl BufferSnapshot { text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, - signature_range: item - .signature_range - .map(|r| self.anchor_after(r.start)..self.anchor_before(r.end)), body_range: item .body_range .map(|r| self.anchor_after(r.start)..self.anchor_before(r.end)), @@ -3940,15 +3937,6 @@ impl BufferSnapshot { let mut open_point = None; let mut close_point = None; - let mut signature_start = None; - let mut signature_end = None; - let mut extend_signature_range = |node: tree_sitter::Node| { - if signature_start.is_none() { - signature_start = Some(Point::from_ts_point(node.start_position())); - } - signature_end = Some(Point::from_ts_point(node.end_position())); - }; - let mut buffer_ranges = Vec::new(); let mut add_to_buffer_ranges = |node: tree_sitter::Node, node_is_name| { let mut range = node.start_byte()..node.end_byte(); @@ -3965,12 +3953,10 @@ impl BufferSnapshot { for capture in mat.captures { if capture.index == config.name_capture_ix { add_to_buffer_ranges(capture.node, true); - extend_signature_range(capture.node); } else if Some(capture.index) == config.context_capture_ix || (Some(capture.index) == config.extra_context_capture_ix && include_extra_context) { add_to_buffer_ranges(capture.node, false); - extend_signature_range(capture.node); } else { if Some(capture.index) == config.open_capture_ix { open_point = Some(Point::from_ts_point(capture.node.end_position())); @@ -4033,17 +4019,12 @@ impl BufferSnapshot { last_buffer_range_end = buffer_range.end; } - let signature_range = signature_start - .zip(signature_end) - .map(|(start, end)| start..end); - Some(OutlineItem { depth: 0, // We'll calculate the depth later range: item_point_range, text, highlight_ranges, name_ranges, - signature_range, body_range: open_point.zip(close_point).map(|(start, end)| start..end), annotation_range: None, }) diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index 09c556cf98f58ea26925e1df8bde9d43ec72e6c7..d96cd90e03142c6498ae17bc63e1787d99e8557a 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -19,7 +19,6 @@ pub struct OutlineItem { pub text: String, pub highlight_ranges: Vec<(Range, HighlightStyle)>, pub name_ranges: Vec>, - pub signature_range: Option>, pub body_range: Option>, pub annotation_range: Option>, } @@ -36,10 +35,6 @@ impl OutlineItem { text: self.text.clone(), highlight_ranges: self.highlight_ranges.clone(), name_ranges: self.name_ranges.clone(), - signature_range: self - .signature_range - .as_ref() - .map(|r| r.start.to_point(buffer)..r.end.to_point(buffer)), body_range: self .body_range .as_ref() @@ -213,7 +208,6 @@ mod tests { text: "class Foo".to_string(), highlight_ranges: vec![], name_ranges: vec![6..9], - signature_range: None, body_range: None, annotation_range: None, }, @@ -223,7 +217,6 @@ mod tests { text: "private".to_string(), highlight_ranges: vec![], name_ranges: vec![], - signature_range: None, body_range: None, annotation_range: None, }, @@ -248,7 +241,6 @@ mod tests { text: "fn process".to_string(), highlight_ranges: vec![], name_ranges: vec![3..10], - signature_range: None, body_range: None, annotation_range: None, }, @@ -258,7 +250,6 @@ mod tests { text: "struct DataProcessor".to_string(), highlight_ranges: vec![], name_ranges: vec![7..20], - signature_range: None, body_range: None, annotation_range: None, }, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 8d72e919a4802c1875c956fc3e6d72fc0e9a2ade..8dd75a4c8b02c5e09f9c8df4221944755c4ed672 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -6130,12 +6130,6 @@ impl MultiBufferSnapshot { text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, - signature_range: item.signature_range.and_then(|signature_range| { - Some( - self.anchor_in_excerpt(*excerpt_id, signature_range.start)? - ..self.anchor_in_excerpt(*excerpt_id, signature_range.end)?, - ) - }), body_range: item.body_range.and_then(|body_range| { Some( self.anchor_in_excerpt(*excerpt_id, body_range.start)? @@ -6176,12 +6170,6 @@ impl MultiBufferSnapshot { text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, - signature_range: item.signature_range.and_then(|signature_range| { - Some( - self.anchor_in_excerpt(excerpt_id, signature_range.start)? - ..self.anchor_in_excerpt(excerpt_id, signature_range.end)?, - ) - }), body_range: item.body_range.and_then(|body_range| { Some( self.anchor_in_excerpt(excerpt_id, body_range.start)? diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 5fd3103dff842bfbacc612536218ac7367b27ec4..863725ad851d5b2e2e44895d97d59fdb2f062f1a 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -2481,7 +2481,6 @@ impl OutlinePanel { &OutlineItem { depth, annotation_range: None, - signature_range: None, range: search_data.context_range.clone(), text: search_data.context_text.clone(), highlight_ranges: search_data From 98865a3ff248ab90f93fb0deb3b8bba9ae2fc3ae Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 22 Sep 2025 20:38:12 -0600 Subject: [PATCH 197/721] Fix invalid anchors in breadcrumbs (#38687) Release Notes: - (nightly only) Fix panic when your cursor abuts a multibyte character --- crates/language/src/buffer.rs | 8 +++----- crates/language/src/buffer_tests.rs | 15 +++++++++++++++ crates/multi_buffer/src/multi_buffer.rs | 20 +++++++------------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 99eae4c2028a9bd3aa82a4facb9e2099e8b5dea4..2610447c92107c34bca52fa004600a9fe10ab6d4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -3756,11 +3756,9 @@ impl BufferSnapshot { theme: Option<&SyntaxTheme>, ) -> Vec> { let position = position.to_offset(self); - let mut items = self.outline_items_containing( - position.saturating_sub(1)..self.len().min(position + 1), - false, - theme, - ); + let start = self.clip_offset(position.saturating_sub(1), Bias::Left); + let end = self.clip_offset(position + 1, Bias::Right); + let mut items = self.outline_items_containing(start..end, false, theme); let mut prev_depth = None; items.retain(|item| { let result = prev_depth.is_none_or(|prev_depth| item.depth > prev_depth); diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index a60399538e840d18d42d3ed2b6d6f62927d4e046..b416099e3cd6f887e16f688594c41273be5cd582 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1052,6 +1052,21 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { }) .collect() } + + let (text, offsets) = marked_text_offsets( + &" + // ˇ😅 // + fn test() { + } + " + .unindent(), + ); + let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + + // note, it would be nice to actually return the method test in this + // case, but primarily asserting we don't crash because of the multibyte character. + assert_eq!(snapshot.symbols_containing(offsets[0], None), vec![]); } #[gpui::test] diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 8dd75a4c8b02c5e09f9c8df4221944755c4ed672..d5f22019235f74e32d839123cf5f8e8a3cae87d1 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -6156,8 +6156,9 @@ impl MultiBufferSnapshot { let anchor = self.anchor_before(offset); let excerpt_id = anchor.excerpt_id; let excerpt = self.excerpt(excerpt_id)?; + let buffer_id = excerpt.buffer_id; Some(( - excerpt.buffer_id, + buffer_id, excerpt .buffer .symbols_containing(anchor.text_anchor, theme) @@ -6165,22 +6166,15 @@ impl MultiBufferSnapshot { .flat_map(|item| { Some(OutlineItem { depth: item.depth, - range: self.anchor_in_excerpt(excerpt_id, item.range.start)? - ..self.anchor_in_excerpt(excerpt_id, item.range.end)?, + range: Anchor::range_in_buffer(excerpt_id, buffer_id, item.range), text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, - body_range: item.body_range.and_then(|body_range| { - Some( - self.anchor_in_excerpt(excerpt_id, body_range.start)? - ..self.anchor_in_excerpt(excerpt_id, body_range.end)?, - ) + body_range: item.body_range.map(|body_range| { + Anchor::range_in_buffer(excerpt_id, buffer_id, body_range) }), - annotation_range: item.annotation_range.and_then(|body_range| { - Some( - self.anchor_in_excerpt(excerpt_id, body_range.start)? - ..self.anchor_in_excerpt(excerpt_id, body_range.end)?, - ) + annotation_range: item.annotation_range.map(|body_range| { + Anchor::range_in_buffer(excerpt_id, buffer_id, body_range) }), }) }) From 25a182745673822a72fbff21d5b27999fe7e679a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 22 Sep 2025 21:51:03 -0600 Subject: [PATCH 198/721] Ensure we have the targets needed for bundling (#38688) Closes #ISSUE Release Notes: - N/A --- script/bundle-mac | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/bundle-mac b/script/bundle-mac index f2a5bf313d53d5dfb084f5ae5035e55a68eff739..0bac0f75ee5ade49eba842257e854420f9bca82f 100755 --- a/script/bundle-mac +++ b/script/bundle-mac @@ -90,6 +90,9 @@ if [ "$local_arch" = true ]; then echo "Building for local target only." cargo build ${build_flag} --package zed --package cli --package remote_server else + rustup target add aarch64-apple-darwin + rustup target add x86_64-apple-darwin + echo "Compiling zed binaries" cargo build ${build_flag} --package zed --package cli --target aarch64-apple-darwin --target x86_64-apple-darwin # Build remote_server in separate invocation to prevent feature unification from other crates From 4532765ae845b8c98c73c88cc916f7d771b429d5 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 23 Sep 2025 00:20:26 -0600 Subject: [PATCH 199/721] zeta2: Add prompt planner and provide access via zeta_cli (#38691) Release Notes: - N/A --- Cargo.lock | 15 + Cargo.toml | 2 + .../cloud_llm_client/src/predict_edits_v3.rs | 5 +- crates/cloud_zeta2_prompt/Cargo.toml | 20 + crates/cloud_zeta2_prompt/LICENSE-GPL | 1 + .../src/cloud_zeta2_prompt.rs | 396 ++++++++++++++++++ .../src/declaration.rs | 58 +-- .../src/declaration_scoring.rs | 5 +- .../src/syntax_index.rs | 4 +- .../src/wip_requests.rs | 35 -- crates/zeta2/src/zeta2.rs | 77 +++- crates/zeta_cli/Cargo.toml | 5 +- crates/zeta_cli/src/main.rs | 143 +++++-- 13 files changed, 667 insertions(+), 99 deletions(-) create mode 100644 crates/cloud_zeta2_prompt/Cargo.toml create mode 120000 crates/cloud_zeta2_prompt/LICENSE-GPL create mode 100644 crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs delete mode 100644 crates/edit_prediction_context/src/wip_requests.rs diff --git a/Cargo.lock b/Cargo.lock index 5e704d56697b1460c9a3a705f852e3287185bdf2..1819b62c3434ad8bdea6dd526b7f68122378e290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3225,6 +3225,18 @@ dependencies = [ "workspace-hack", ] +[[package]] +name = "cloud_zeta2_prompt" +version = "0.1.0" +dependencies = [ + "anyhow", + "cloud_llm_client", + "ordered-float 2.10.1", + "rustc-hash 2.1.1", + "strum 0.27.1", + "workspace-hack", +] + [[package]] name = "clru" version = "0.6.2" @@ -21683,7 +21695,9 @@ dependencies = [ "anyhow", "clap", "client", + "cloud_zeta2_prompt", "debug_adapter_extension", + "edit_prediction_context", "extension", "fs", "futures 0.3.31", @@ -21710,6 +21724,7 @@ dependencies = [ "watch", "workspace-hack", "zeta", + "zeta2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6e1950aaeea715dd85c98a443b6116a619b0e3f7..3c431a29eb53420dadde38a1c1ad30a1f61d44c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "crates/cloud_api_client", "crates/cloud_api_types", "crates/cloud_llm_client", + "crates/cloud_zeta2_prompt", "crates/collab", "crates/collab_ui", "crates/collections", @@ -271,6 +272,7 @@ clock = { path = "crates/clock" } cloud_api_client = { path = "crates/cloud_api_client" } cloud_api_types = { path = "crates/cloud_api_types" } cloud_llm_client = { path = "crates/cloud_llm_client" } +cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" } collab = { path = "crates/collab" } collab_ui = { path = "crates/collab_ui" } collections = { path = "crates/collections" } diff --git a/crates/cloud_llm_client/src/predict_edits_v3.rs b/crates/cloud_llm_client/src/predict_edits_v3.rs index 60621b1f14714439b0527078c07e2865799172f3..35d05ff81f9eb72c4b8261dc3e3340b18c79ebfa 100644 --- a/crates/cloud_llm_client/src/predict_edits_v3.rs +++ b/crates/cloud_llm_client/src/predict_edits_v3.rs @@ -48,6 +48,9 @@ pub struct Signature { pub text_is_truncated: bool, #[serde(skip_serializing_if = "Option::is_none", default)] pub parent_index: Option, + /// Range of `text` within the file, possibly truncated according to `text_is_truncated`. The + /// file is implicitly the file that contains the descendant declaration or excerpt. + pub range: Range, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -55,7 +58,7 @@ pub struct ReferencedDeclaration { pub path: PathBuf, pub text: String, pub text_is_truncated: bool, - /// Range of `text` within file, potentially truncated according to `text_is_truncated` + /// Range of `text` within file, possibly truncated according to `text_is_truncated` pub range: Range, /// Range within `text` pub signature_range: Range, diff --git a/crates/cloud_zeta2_prompt/Cargo.toml b/crates/cloud_zeta2_prompt/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a1194f13615964fd3013eb8dbdf3057984946e32 --- /dev/null +++ b/crates/cloud_zeta2_prompt/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cloud_zeta2_prompt" +version = "0.1.0" +publish.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/cloud_zeta2_prompt.rs" + +[dependencies] +anyhow.workspace = true +cloud_llm_client.workspace = true +ordered-float.workspace = true +rustc-hash.workspace = true +strum.workspace = true +workspace-hack.workspace = true diff --git a/crates/cloud_zeta2_prompt/LICENSE-GPL b/crates/cloud_zeta2_prompt/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/cloud_zeta2_prompt/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs new file mode 100644 index 0000000000000000000000000000000000000000..6690380c74b0d4880210b683f34eea1d98a7946b --- /dev/null +++ b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs @@ -0,0 +1,396 @@ +//! Zeta2 prompt planning and generation code shared with cloud. + +use anyhow::{Result, anyhow}; +use cloud_llm_client::predict_edits_v3::{self, ReferencedDeclaration}; +use ordered_float::OrderedFloat; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::{cmp::Reverse, collections::BinaryHeap, ops::Range, path::Path}; +use strum::{EnumIter, IntoEnumIterator}; + +pub const CURSOR_MARKER: &str = "<|user_cursor_is_here|>"; +/// NOTE: Differs from zed version of constant - includes a newline +pub const EDITABLE_REGION_START_MARKER: &str = "<|editable_region_start|>\n"; +/// NOTE: Differs from zed version of constant - includes a newline +pub const EDITABLE_REGION_END_MARKER: &str = "<|editable_region_end|>\n"; + +pub struct PlannedPrompt<'a> { + request: &'a predict_edits_v3::PredictEditsRequest, + /// Snippets to include in the prompt. These may overlap - they are merged / deduplicated in + /// `to_prompt_string`. + snippets: Vec>, + budget_used: usize, +} + +pub struct PlanOptions { + pub max_bytes: usize, +} + +#[derive(Clone, Debug)] +pub struct PlannedSnippet<'a> { + path: &'a Path, + range: Range, + text: &'a str, + // TODO: Indicate this in the output + #[allow(dead_code)] + text_is_truncated: bool, +} + +#[derive(EnumIter, Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] +pub enum SnippetStyle { + Signature, + Declaration, +} + +impl<'a> PlannedPrompt<'a> { + /// Greedy one-pass knapsack algorithm to populate the prompt plan. Does the following: + /// + /// Initializes a priority queue by populating it with each snippet, finding the SnippetStyle + /// that minimizes `score_density = score / snippet.range(style).len()`. When a "signature" + /// snippet is popped, insert an entry for the "declaration" variant that reflects the cost of + /// upgrade. + /// + /// TODO: Implement an early halting condition. One option might be to have another priority + /// queue where the score is the size, and update it accordingly. Another option might be to + /// have some simpler heuristic like bailing after N failed insertions, or based on how much + /// budget is left. + /// + /// TODO: Has the current known sources of imprecision: + /// + /// * Does not consider snippet overlap when ranking. For example, it might add a field to the + /// plan even though the containing struct is already included. + /// + /// * Does not consider cost of signatures when ranking snippets - this is tricky since + /// signatures may be shared by multiple snippets. + /// + /// * Does not include file paths / other text when considering max_bytes. + pub fn populate( + request: &'a predict_edits_v3::PredictEditsRequest, + options: &PlanOptions, + ) -> Result { + let mut this = PlannedPrompt { + request, + snippets: Vec::new(), + budget_used: request.excerpt.len(), + }; + let mut included_parents = FxHashSet::default(); + let additional_parents = this.additional_parent_signatures( + &request.excerpt_path, + request.excerpt_parent, + &included_parents, + )?; + this.add_parents(&mut included_parents, additional_parents); + + if this.budget_used > options.max_bytes { + return Err(anyhow!( + "Excerpt + signatures size of {} already exceeds budget of {}", + this.budget_used, + options.max_bytes + )); + } + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + struct QueueEntry { + score_density: OrderedFloat, + declaration_index: usize, + style: SnippetStyle, + } + + // Initialize priority queue with the best score for each snippet. + let mut queue: BinaryHeap = BinaryHeap::new(); + for (declaration_index, declaration) in request.referenced_declarations.iter().enumerate() { + let (style, score_density) = SnippetStyle::iter() + .map(|style| { + ( + style, + OrderedFloat(declaration_score_density(&declaration, style)), + ) + }) + .max_by_key(|(_, score_density)| *score_density) + .unwrap(); + queue.push(QueueEntry { + score_density, + declaration_index, + style, + }); + } + + // Knapsack selection loop + while let Some(queue_entry) = queue.pop() { + let Some(declaration) = request + .referenced_declarations + .get(queue_entry.declaration_index) + else { + return Err(anyhow!( + "Invalid declaration index {}", + queue_entry.declaration_index + )); + }; + + let mut additional_bytes = declaration_size(declaration, queue_entry.style); + if this.budget_used + additional_bytes > options.max_bytes { + continue; + } + + let additional_parents = this.additional_parent_signatures( + &declaration.path, + declaration.parent_index, + &mut included_parents, + )?; + additional_bytes += additional_parents + .iter() + .map(|(_, snippet)| snippet.text.len()) + .sum::(); + if this.budget_used + additional_bytes > options.max_bytes { + continue; + } + + this.budget_used += additional_bytes; + this.add_parents(&mut included_parents, additional_parents); + let planned_snippet = match queue_entry.style { + SnippetStyle::Signature => { + let Some(text) = declaration.text.get(declaration.signature_range.clone()) + else { + return Err(anyhow!( + "Invalid declaration signature_range {:?} with text.len() = {}", + declaration.signature_range, + declaration.text.len() + )); + }; + PlannedSnippet { + path: &declaration.path, + range: (declaration.signature_range.start + declaration.range.start) + ..(declaration.signature_range.end + declaration.range.start), + text, + text_is_truncated: declaration.text_is_truncated, + } + } + SnippetStyle::Declaration => PlannedSnippet { + path: &declaration.path, + range: declaration.range.clone(), + text: &declaration.text, + text_is_truncated: declaration.text_is_truncated, + }, + }; + this.snippets.push(planned_snippet); + + // When a Signature is consumed, insert an entry for Definition style. + if queue_entry.style == SnippetStyle::Signature { + let signature_size = declaration_size(&declaration, SnippetStyle::Signature); + let declaration_size = declaration_size(&declaration, SnippetStyle::Declaration); + let signature_score = declaration_score(&declaration, SnippetStyle::Signature); + let declaration_score = declaration_score(&declaration, SnippetStyle::Declaration); + + let score_diff = declaration_score - signature_score; + let size_diff = declaration_size.saturating_sub(signature_size); + if score_diff > 0.0001 && size_diff > 0 { + queue.push(QueueEntry { + declaration_index: queue_entry.declaration_index, + score_density: OrderedFloat(score_diff / (size_diff as f32)), + style: SnippetStyle::Declaration, + }); + } + } + } + + anyhow::Ok(this) + } + + fn add_parents( + &mut self, + included_parents: &mut FxHashSet, + snippets: Vec<(usize, PlannedSnippet<'a>)>, + ) { + for (parent_index, snippet) in snippets { + included_parents.insert(parent_index); + self.budget_used += snippet.text.len(); + self.snippets.push(snippet); + } + } + + fn additional_parent_signatures( + &self, + path: &'a Path, + parent_index: Option, + included_parents: &FxHashSet, + ) -> Result)>> { + let mut results = Vec::new(); + self.additional_parent_signatures_impl(path, parent_index, included_parents, &mut results)?; + Ok(results) + } + + fn additional_parent_signatures_impl( + &self, + path: &'a Path, + parent_index: Option, + included_parents: &FxHashSet, + results: &mut Vec<(usize, PlannedSnippet<'a>)>, + ) -> Result<()> { + let Some(parent_index) = parent_index else { + return Ok(()); + }; + if included_parents.contains(&parent_index) { + return Ok(()); + } + let Some(parent_signature) = self.request.signatures.get(parent_index) else { + return Err(anyhow!("Invalid parent index {}", parent_index)); + }; + results.push(( + parent_index, + PlannedSnippet { + path, + range: parent_signature.range.clone(), + text: &parent_signature.text, + text_is_truncated: parent_signature.text_is_truncated, + }, + )); + self.additional_parent_signatures_impl( + path, + parent_signature.parent_index, + included_parents, + results, + ) + } + + /// Renders the planned context. Each file starts with "```FILE_PATH\n` and ends with triple + /// backticks, with a newline after each file. Outputs a line with "..." between nonconsecutive + /// chunks. + pub fn to_prompt_string(&self) -> String { + let mut file_to_snippets: FxHashMap<&'a std::path::Path, Vec<&PlannedSnippet<'a>>> = + FxHashMap::default(); + for snippet in &self.snippets { + file_to_snippets + .entry(&snippet.path) + .or_default() + .push(snippet); + } + + // Reorder so that file with cursor comes last + let mut file_snippets = Vec::new(); + let mut excerpt_file_snippets = Vec::new(); + for (file_path, snippets) in file_to_snippets { + if file_path == &self.request.excerpt_path { + excerpt_file_snippets = snippets; + } else { + file_snippets.push((file_path, snippets, false)); + } + } + let excerpt_snippet = PlannedSnippet { + path: &self.request.excerpt_path, + range: self.request.excerpt_range.clone(), + text: &self.request.excerpt, + text_is_truncated: false, + }; + excerpt_file_snippets.push(&excerpt_snippet); + file_snippets.push((&self.request.excerpt_path, excerpt_file_snippets, true)); + + let mut excerpt_file_insertions = vec![ + ( + self.request.excerpt_range.start, + EDITABLE_REGION_START_MARKER, + ), + ( + self.request.excerpt_range.start + self.request.cursor_offset, + CURSOR_MARKER, + ), + ( + self.request + .excerpt_range + .end + .saturating_sub(0) + .max(self.request.excerpt_range.start), + EDITABLE_REGION_END_MARKER, + ), + ]; + + fn push_excerpt_file_range( + range: Range, + text: &str, + excerpt_file_insertions: &mut Vec<(usize, &'static str)>, + output: &mut String, + ) { + let mut last_offset = range.start; + let mut i = 0; + while i < excerpt_file_insertions.len() { + let (offset, insertion) = &excerpt_file_insertions[i]; + let found = *offset >= range.start && *offset <= range.end; + if found { + output.push_str(&text[last_offset - range.start..offset - range.start]); + output.push_str(insertion); + last_offset = *offset; + excerpt_file_insertions.remove(i); + continue; + } + i += 1; + } + output.push_str(&text[last_offset - range.start..]); + } + + let mut output = String::new(); + for (file_path, mut snippets, is_excerpt_file) in file_snippets { + output.push_str(&format!("```{}\n", file_path.display())); + + let mut last_included_range: Option> = None; + snippets.sort_by_key(|s| (s.range.start, Reverse(s.range.end))); + for snippet in snippets { + if let Some(last_range) = &last_included_range + && snippet.range.start < last_range.end + { + if snippet.range.end <= last_range.end { + continue; + } + // TODO: Should probably also handle case where there is just one char (newline) + // between snippets - assume it's a newline. + let text = &snippet.text[last_range.end - snippet.range.start..]; + if is_excerpt_file { + push_excerpt_file_range( + last_range.end..snippet.range.end, + text, + &mut excerpt_file_insertions, + &mut output, + ); + } else { + output.push_str(text); + } + last_included_range = Some(last_range.start..snippet.range.end); + continue; + } + if last_included_range.is_some() { + output.push_str("…\n"); + } + if is_excerpt_file { + push_excerpt_file_range( + snippet.range.clone(), + snippet.text, + &mut excerpt_file_insertions, + &mut output, + ); + } else { + output.push_str(snippet.text); + } + last_included_range = Some(snippet.range.clone()); + } + + output.push_str("```\n\n"); + } + + output + } +} + +fn declaration_score_density(declaration: &ReferencedDeclaration, style: SnippetStyle) -> f32 { + declaration_score(declaration, style) / declaration_size(declaration, style) as f32 +} + +fn declaration_score(declaration: &ReferencedDeclaration, style: SnippetStyle) -> f32 { + match style { + SnippetStyle::Signature => declaration.signature_score, + SnippetStyle::Declaration => declaration.declaration_score, + } +} + +fn declaration_size(declaration: &ReferencedDeclaration, style: SnippetStyle) -> usize { + match style { + SnippetStyle::Signature => declaration.signature_range.len(), + SnippetStyle::Declaration => declaration.text.len(), + } +} diff --git a/crates/edit_prediction_context/src/declaration.rs b/crates/edit_prediction_context/src/declaration.rs index 653f810d439395a8825c99f4b007e05d881540ab..910835534af80ba97b99b8fc560c27bf13c4acda 100644 --- a/crates/edit_prediction_context/src/declaration.rs +++ b/crates/edit_prediction_context/src/declaration.rs @@ -68,7 +68,7 @@ impl Declaration { pub fn item_range(&self) -> Range { match self { - Declaration::File { declaration, .. } => declaration.item_range_in_file.clone(), + Declaration::File { declaration, .. } => declaration.item_range.clone(), Declaration::Buffer { declaration, .. } => declaration.item_range.clone(), } } @@ -92,7 +92,7 @@ impl Declaration { pub fn signature_text(&self) -> (Cow<'_, str>, bool) { match self { Declaration::File { declaration, .. } => ( - declaration.text[declaration.signature_range_in_text.clone()].into(), + declaration.text[self.signature_range_in_item_text()].into(), declaration.signature_is_truncated, ), Declaration::Buffer { @@ -105,15 +105,19 @@ impl Declaration { } } - pub fn signature_range_in_item_text(&self) -> Range { + pub fn signature_range(&self) -> Range { match self { - Declaration::File { declaration, .. } => declaration.signature_range_in_text.clone(), - Declaration::Buffer { declaration, .. } => { - declaration.signature_range.start - declaration.item_range.start - ..declaration.signature_range.end - declaration.item_range.start - } + Declaration::File { declaration, .. } => declaration.signature_range.clone(), + Declaration::Buffer { declaration, .. } => declaration.signature_range.clone(), } } + + pub fn signature_range_in_item_text(&self) -> Range { + let signature_range = self.signature_range(); + let item_range = self.item_range(); + signature_range.start.saturating_sub(item_range.start) + ..(signature_range.end.saturating_sub(item_range.start)).min(item_range.len()) + } } fn expand_range_to_line_boundaries_and_truncate( @@ -141,13 +145,13 @@ pub struct FileDeclaration { pub parent: Option, pub identifier: Identifier, /// offset range of the declaration in the file, expanded to line boundaries and truncated - pub item_range_in_file: Range, - /// text of `item_range_in_file` + pub item_range: Range, + /// text of `item_range` pub text: Arc, /// whether `text` was truncated pub text_is_truncated: bool, - /// offset range of the signature within `text` - pub signature_range_in_text: Range, + /// offset range of the signature in the file, expanded to line boundaries and truncated + pub signature_range: Range, /// whether `signature` was truncated pub signature_is_truncated: bool, } @@ -160,31 +164,33 @@ impl FileDeclaration { rope, ); - // TODO: consider logging if unexpected - let signature_start = declaration - .signature_range - .start - .saturating_sub(item_range_in_file.start); - let mut signature_end = declaration - .signature_range - .end - .saturating_sub(item_range_in_file.start); - let signature_is_truncated = signature_end > item_range_in_file.len(); - if signature_is_truncated { - signature_end = item_range_in_file.len(); + let (mut signature_range_in_file, mut signature_is_truncated) = + expand_range_to_line_boundaries_and_truncate( + &declaration.signature_range, + ITEM_TEXT_TRUNCATION_LENGTH, + rope, + ); + + if signature_range_in_file.start < item_range_in_file.start { + signature_range_in_file.start = item_range_in_file.start; + signature_is_truncated = true; + } + if signature_range_in_file.end > item_range_in_file.end { + signature_range_in_file.end = item_range_in_file.end; + signature_is_truncated = true; } FileDeclaration { parent: None, identifier: declaration.identifier, - signature_range_in_text: signature_start..signature_end, + signature_range: signature_range_in_file, signature_is_truncated, text: rope .chunks_in_range(item_range_in_file.clone()) .collect::() .into(), text_is_truncated, - item_range_in_file, + item_range: item_range_in_file, } } } diff --git a/crates/edit_prediction_context/src/declaration_scoring.rs b/crates/edit_prediction_context/src/declaration_scoring.rs index 1638723857d225d05efce512bb9025fa89fb38f3..fee7498a696c0608704dab6e8ab9f012c95660b5 100644 --- a/crates/edit_prediction_context/src/declaration_scoring.rs +++ b/crates/edit_prediction_context/src/declaration_scoring.rs @@ -40,10 +40,9 @@ impl ScoredSnippet { } pub fn size(&self, style: SnippetStyle) -> usize { - // TODO: how to handle truncation? match &self.declaration { Declaration::File { declaration, .. } => match style { - SnippetStyle::Signature => declaration.signature_range_in_text.len(), + SnippetStyle::Signature => declaration.signature_range.len(), SnippetStyle::Declaration => declaration.text.len(), }, Declaration::Buffer { declaration, .. } => match style { @@ -276,6 +275,8 @@ pub struct Scores { impl Scores { fn score(components: &ScoreComponents) -> Scores { + // TODO: handle truncation + // Score related to how likely this is the correct declaration, range 0 to 1 let accuracy_score = if components.is_same_file { // TODO: use declaration_line_distance_rank diff --git a/crates/edit_prediction_context/src/syntax_index.rs b/crates/edit_prediction_context/src/syntax_index.rs index d234e975d504c145d7bc2fc0680569c388ba0d1c..1b5e4268ccec74b9eea52c1001c7854dd746c5cf 100644 --- a/crates/edit_prediction_context/src/syntax_index.rs +++ b/crates/edit_prediction_context/src/syntax_index.rs @@ -578,11 +578,11 @@ mod tests { let decl = expect_file_decl("c.rs", &decls[0].1, &project, cx); assert_eq!(decl.identifier, main.clone()); - assert_eq!(decl.item_range_in_file, 32..280); + assert_eq!(decl.item_range, 32..280); let decl = expect_file_decl("a.rs", &decls[1].1, &project, cx); assert_eq!(decl.identifier, main); - assert_eq!(decl.item_range_in_file, 0..98); + assert_eq!(decl.item_range, 0..98); }); } diff --git a/crates/edit_prediction_context/src/wip_requests.rs b/crates/edit_prediction_context/src/wip_requests.rs deleted file mode 100644 index 9189587929725c8e1e4369fe5bd24cc641d6afab..0000000000000000000000000000000000000000 --- a/crates/edit_prediction_context/src/wip_requests.rs +++ /dev/null @@ -1,35 +0,0 @@ -// To discuss: What to send to the new endpoint? Thinking it'd make sense to put `prompt.rs` from -// `zeta_context.rs` in cloud. -// -// * Run excerpt selection at several different sizes, send the largest size with offsets within for -// the smaller sizes. -// -// * Longer event history. -// -// * Many more snippets than could fit in model context - allows ranking experimentation. - -pub struct Zeta2Request { - pub event_history: Vec, - pub excerpt: String, - pub excerpt_subsets: Vec, - /// Within `excerpt` - pub cursor_position: usize, - pub signatures: Vec, - pub retrieved_declarations: Vec, -} - -pub struct Zeta2ExcerptSubset { - /// Within `excerpt` text. - pub excerpt_range: Range, - /// Within `signatures`. - pub parent_signatures: Vec, -} - -pub struct ReferencedDeclaration { - pub text: Arc, - /// Range within `text` - pub signature_range: Range, - /// Indices within `signatures`. - pub parent_signatures: Vec, - // A bunch of score metrics -} diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 791273a9242dd7aa50588fcfe90e9258ff3724ea..240d44fae44d9a430e1ed64816e11428a5bdb3d0 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -48,7 +48,7 @@ pub struct Zeta { llm_token: LlmApiToken, _llm_token_subscription: Subscription, projects: HashMap, - excerpt_options: EditPredictionExcerptOptions, + pub excerpt_options: EditPredictionExcerptOptions, update_required: bool, } @@ -87,7 +87,7 @@ impl Zeta { }) } - fn new(client: Arc, user_store: Entity, cx: &mut Context) -> Self { + pub fn new(client: Arc, user_store: Entity, cx: &mut Context) -> Self { let refresh_llm_token_listener = RefreshLlmTokenListener::global(cx); Self { @@ -478,6 +478,66 @@ impl Zeta { } } } + + // TODO: Dedupe with similar code in request_prediction? + pub fn cloud_request_for_zeta_cli( + &mut self, + project: &Entity, + buffer: &Entity, + position: language::Anchor, + cx: &mut Context, + ) -> Task> { + let project_state = self.projects.get(&project.entity_id()); + + let index_state = project_state.map(|state| { + state + .syntax_index + .read_with(cx, |index, _cx| index.state().clone()) + }); + let excerpt_options = self.excerpt_options.clone(); + let snapshot = buffer.read(cx).snapshot(); + let Some(excerpt_path) = snapshot.file().map(|path| path.full_path(cx)) else { + return Task::ready(Err(anyhow!("No file path for excerpt"))); + }; + let worktree_snapshots = project + .read(cx) + .worktrees(cx) + .map(|worktree| worktree.read(cx).snapshot()) + .collect::>(); + + cx.background_spawn(async move { + let index_state = if let Some(index_state) = index_state { + Some(index_state.lock_owned().await) + } else { + None + }; + + let cursor_point = position.to_point(&snapshot); + + let debug_info = true; + EditPredictionContext::gather_context( + cursor_point, + &snapshot, + &excerpt_options, + index_state.as_deref(), + ) + .context("Failed to select excerpt") + .map(|context| { + make_cloud_request( + excerpt_path.clone(), + context, + // TODO pass everything + Vec::new(), + false, + Vec::new(), + None, + debug_info, + &worktree_snapshots, + index_state.as_deref(), + ) + }) + }) + } } #[derive(Error, Debug)] @@ -840,13 +900,13 @@ fn make_cloud_request( for snippet in context.snippets { let project_entry_id = snippet.declaration.project_entry_id(); - // TODO: Use full paths (worktree rooted) - need to move full_path method to the snapshot. - // Note that currently full_path is currently being used for excerpt_path. let Some(path) = worktrees.iter().find_map(|worktree| { - let abs_path = worktree.abs_path(); - worktree - .entry_for_id(project_entry_id) - .map(|e| abs_path.join(&e.path)) + worktree.entry_for_id(project_entry_id).map(|entry| { + let mut full_path = PathBuf::new(); + full_path.push(worktree.root_name()); + full_path.push(&entry.path); + full_path + }) }) else { continue; }; @@ -929,6 +989,7 @@ fn add_signature( text: text.into(), text_is_truncated, parent_index, + range: parent_declaration.signature_range(), }); declaration_to_signature_index.insert(declaration_id, signature_index); Some(signature_index) diff --git a/crates/zeta_cli/Cargo.toml b/crates/zeta_cli/Cargo.toml index e77351c219bac4425136e2a3f1752d73e76adbbf..38b85d7c3ac583b25f72240bfde6109a04e30c10 100644 --- a/crates/zeta_cli/Cargo.toml +++ b/crates/zeta_cli/Cargo.toml @@ -16,7 +16,9 @@ path = "src/main.rs" anyhow.workspace = true clap.workspace = true client.workspace = true +cloud_zeta2_prompt.workspace= true debug_adapter_extension.workspace = true +edit_prediction_context.workspace = true extension.workspace = true fs.workspace = true futures.workspace = true @@ -37,9 +39,10 @@ serde.workspace = true serde_json.workspace = true settings.workspace = true shellexpand.workspace = true +smol.workspace = true terminal_view.workspace = true util.workspace = true watch.workspace = true workspace-hack.workspace = true zeta.workspace = true -smol.workspace = true +zeta2.workspace = true diff --git a/crates/zeta_cli/src/main.rs b/crates/zeta_cli/src/main.rs index e7cec26b19358056cee4c8e253c54c0b2c794b33..d2cde6be589a8138ef1e88e872a1e18294f4cb30 100644 --- a/crates/zeta_cli/src/main.rs +++ b/crates/zeta_cli/src/main.rs @@ -2,6 +2,7 @@ mod headless; use anyhow::{Result, anyhow}; use clap::{Args, Parser, Subcommand}; +use edit_prediction_context::EditPredictionExcerptOptions; use futures::channel::mpsc; use futures::{FutureExt as _, StreamExt as _}; use gpui::{AppContext, Application, AsyncApp}; @@ -18,7 +19,7 @@ use std::process::exit; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use zeta::{GatherContextOutput, PerformPredictEditsParams, Zeta, gather_context}; +use zeta::{PerformPredictEditsParams, Zeta}; use crate::headless::ZetaCliAppState; @@ -32,6 +33,12 @@ struct ZetaCliArgs { #[derive(Subcommand, Debug)] enum Commands { Context(ContextArgs), + Zeta2Context { + #[clap(flatten)] + zeta2_args: Zeta2Args, + #[clap(flatten)] + context_args: ContextArgs, + }, Predict { #[arg(long)] predict_edits_body: Option, @@ -53,6 +60,18 @@ struct ContextArgs { events: Option, } +#[derive(Debug, Args)] +struct Zeta2Args { + #[arg(long, default_value_t = 8192)] + prompt_max_bytes: usize, + #[arg(long, default_value_t = 2048)] + excerpt_max_bytes: usize, + #[arg(long, default_value_t = 1024)] + excerpt_min_bytes: usize, + #[arg(long, default_value_t = 0.66)] + target_before_cursor_over_total_bytes: f32, +} + #[derive(Debug, Clone)] enum FileOrStdin { File(PathBuf), @@ -112,11 +131,17 @@ impl FromStr for CursorPosition { } } +enum GetContextOutput { + Zeta1(zeta::GatherContextOutput), + Zeta2(String), +} + async fn get_context( + zeta2_args: Option, args: ContextArgs, app_state: &Arc, cx: &mut AsyncApp, -) -> Result { +) -> Result { let ContextArgs { worktree: worktree_path, cursor, @@ -152,9 +177,7 @@ async fn get_context( open_buffer_with_language_server(&project, &worktree, &cursor.path, cx).await?; (Some(lsp_open_handle), buffer) } else { - let abs_path = worktree_path.join(&cursor.path); - let content = smol::fs::read_to_string(&abs_path).await?; - let buffer = cx.new(|cx| Buffer::local(content, cx))?; + let buffer = open_buffer(&project, &worktree, &cursor.path, cx).await?; (None, buffer) }; @@ -189,33 +212,83 @@ async fn get_context( Some(events) => events.read_to_string().await?, None => String::new(), }; - let prompt_for_events = move || (events, 0); - cx.update(|cx| { - gather_context( - full_path_str, - &snapshot, - clipped_cursor, - prompt_for_events, - cx, - ) - })? - .await + + if let Some(zeta2_args) = zeta2_args { + Ok(GetContextOutput::Zeta2( + cx.update(|cx| { + let zeta = cx.new(|cx| { + zeta2::Zeta::new(app_state.client.clone(), app_state.user_store.clone(), cx) + }); + zeta.update(cx, |zeta, cx| { + zeta.register_buffer(&buffer, &project, cx); + zeta.excerpt_options = EditPredictionExcerptOptions { + max_bytes: zeta2_args.excerpt_max_bytes, + min_bytes: zeta2_args.excerpt_min_bytes, + target_before_cursor_over_total_bytes: zeta2_args + .target_before_cursor_over_total_bytes, + } + }); + // TODO: Actually wait for indexing. + let timer = cx.background_executor().timer(Duration::from_secs(5)); + cx.spawn(async move |cx| { + timer.await; + let request = zeta + .update(cx, |zeta, cx| { + let cursor = buffer.read(cx).snapshot().anchor_before(clipped_cursor); + zeta.cloud_request_for_zeta_cli(&project, &buffer, cursor, cx) + })? + .await?; + let planned_prompt = cloud_zeta2_prompt::PlannedPrompt::populate( + &request, + &cloud_zeta2_prompt::PlanOptions { + max_bytes: zeta2_args.prompt_max_bytes, + }, + )?; + anyhow::Ok(planned_prompt.to_prompt_string()) + }) + })? + .await?, + )) + } else { + let prompt_for_events = move || (events, 0); + Ok(GetContextOutput::Zeta1( + cx.update(|cx| { + zeta::gather_context( + full_path_str, + &snapshot, + clipped_cursor, + prompt_for_events, + cx, + ) + })? + .await?, + )) + } } -pub async fn open_buffer_with_language_server( +pub async fn open_buffer( project: &Entity, worktree: &Entity, path: &Path, cx: &mut AsyncApp, -) -> Result<(Entity>, Entity)> { +) -> Result> { let project_path = worktree.read_with(cx, |worktree, _cx| ProjectPath { worktree_id: worktree.id(), path: path.to_path_buf().into(), })?; - let buffer = project + project .update(cx, |project, cx| project.open_buffer(project_path, cx))? - .await?; + .await +} + +pub async fn open_buffer_with_language_server( + project: &Entity, + worktree: &Entity, + path: &Path, + cx: &mut AsyncApp, +) -> Result<(Entity>, Entity)> { + let buffer = open_buffer(project, worktree, path, cx).await?; let lsp_open_handle = project.update(cx, |project, cx| { project.register_buffer_with_language_servers(&buffer, cx) @@ -319,11 +392,26 @@ fn main() { app.run(move |cx| { let app_state = Arc::new(headless::init(cx)); + let is_zeta2_context_command = matches!(args.command, Commands::Zeta2Context { .. }); cx.spawn(async move |cx| { let result = match args.command { - Commands::Context(context_args) => get_context(context_args, &app_state, cx) - .await - .map(|output| serde_json::to_string_pretty(&output.body).unwrap()), + Commands::Zeta2Context { + zeta2_args, + context_args, + } => match get_context(Some(zeta2_args), context_args, &app_state, cx).await { + Ok(GetContextOutput::Zeta1 { .. }) => unreachable!(), + Ok(GetContextOutput::Zeta2(output)) => Ok(output), + Err(err) => Err(err), + }, + Commands::Context(context_args) => { + match get_context(None, context_args, &app_state, cx).await { + Ok(GetContextOutput::Zeta1(output)) => { + Ok(serde_json::to_string_pretty(&output.body).unwrap()) + } + Ok(GetContextOutput::Zeta2 { .. }) => unreachable!(), + Err(err) => Err(err), + } + } Commands::Predict { predict_edits_body, context_args, @@ -338,7 +426,10 @@ fn main() { if let Some(predict_edits_body) = predict_edits_body { serde_json::from_str(&predict_edits_body.read_to_string().await?)? } else if let Some(context_args) = context_args { - get_context(context_args, &app_state, cx).await?.body + match get_context(None, context_args, &app_state, cx).await? { + GetContextOutput::Zeta1(output) => output.body, + GetContextOutput::Zeta2 { .. } => unreachable!(), + } } else { return Err(anyhow!( "Expected either --predict-edits-body-file \ @@ -363,6 +454,10 @@ fn main() { match result { Ok(output) => { println!("{}", output); + // TODO: Remove this once the 5 second delay is properly replaced. + if is_zeta2_context_command { + eprintln!("Note that zeta2-context doesn't yet wait for indexing, instead waits 5 seconds."); + } let _ = cx.update(|cx| cx.quit()); } Err(e) => { From f07bc12aed224159016007cbe47ef60c74381a74 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Sep 2025 09:44:50 +0200 Subject: [PATCH 200/721] helix: Further cleanups to helix paste in line mode (#38694) I noticed that after we paste in line mode, the cursor position is positioned at the beginning of the next logical line which is somewhat undesirable since then inserting/appending will position the cursor after the selection. This does not match helix behaviour which we should further investigate. Follow-up to https://github.com/zed-industries/zed/pull/38663 Release Notes: - N/A --- crates/vim/src/helix/paste.rs | 61 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/crates/vim/src/helix/paste.rs b/crates/vim/src/helix/paste.rs index 957d459dac50892e8173f4f1ac12459277b6d6ae..feb6b494b5ccaf514c37a591eb1fd4b5510df7cd 100644 --- a/crates/vim/src/helix/paste.rs +++ b/crates/vim/src/helix/paste.rs @@ -84,21 +84,14 @@ impl Vim { let display_point = if line_mode { if action.before { movement::line_beginning(&display_map, sel.start, false) - } else if sel.start.column() > 0 - && sel.end.column() == 0 - && sel.start != sel.end - { - sel.end } else { - let point = movement::line_end(&display_map, sel.end, false); - if sel.end.column() == 0 && point.column() > 0 { - // If the selection ends at the beginning of the next line, and the current line - // under the cursor is not empty, we paste at the selection's end. - sel.end + if sel.start == sel.end { + movement::right( + &display_map, + movement::line_end(&display_map, sel.end, false), + ) } else { - // If however the current line under the cursor is empty, we need to move - // to the beginning of the next line to avoid pasting above the end of current selection. - movement::right(&display_map, point) + sel.end } } } else if action.before { @@ -132,12 +125,6 @@ impl Vim { let offset = anchor.to_offset(&snapshot); if action.before { offset.saturating_sub(len)..offset - } else if line_mode { - // In line mode, we always move the cursor to the end of the inserted text. - // Otherwise, while it looks fine visually, inserting/appending ends up - // in the next logical line which is not desirable. - debug_assert!(len > 0); - offset..(offset + len - 1) } else { offset..(offset + len) } @@ -401,8 +388,8 @@ mod test { indoc! {" The quick brown fox jumps over - «nˇ» - the lazy dog."}, + «n + ˇ»the lazy dog."}, Mode::HelixNormal, ); @@ -411,8 +398,8 @@ mod test { cx.set_state( indoc! {" The quick brown - fox jumps over - ˇthe lazy dog."}, + fox jumps overˇ + the lazy dog."}, Mode::HelixNormal, ); cx.simulate_keystrokes("p"); @@ -420,8 +407,8 @@ mod test { indoc! {" The quick brown fox jumps over - «nˇ» - the lazy dog."}, + «n + ˇ»the lazy dog."}, Mode::HelixNormal, ); @@ -437,11 +424,31 @@ mod test { cx.assert_state( indoc! {" - «fox jumps overˇ» - The quick brown + «fox jumps over + ˇ»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("y p p"); + cx.assert_state( + indoc! {" + The quick brown + fox jumps over + The quick brown + fox jumps over + «The quick brown + fox jumps over + ˇ»the lazy dog."}, + Mode::HelixNormal, + ); } } From 1d5da685604442376b1baac8df49f35dabbd25ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Tue, 23 Sep 2025 16:13:29 +0800 Subject: [PATCH 201/721] windows: Show `alt-=` for `pane::GoForward` (#38696) Reorder the shortcuts for `pane::GoForward` so the menu now shows `Alt-=` instead of `forward` Release Notes: - N/A --- assets/keymaps/default-windows.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 5608d01379de75e612270da243c846d4da8d775f..10b7a85d7c56f733228c75bf5e9cde028349f829 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -465,8 +465,8 @@ "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", "back": "pane::GoBack", "alt--": "pane::GoBack", - "alt-=": "pane::GoForward", "forward": "pane::GoForward", + "alt-=": "pane::GoForward", "f3": "search::SelectNextMatch", "shift-f3": "search::SelectPreviousMatch", "ctrl-shift-f": "project_search::ToggleFocus", From 691bfe71db354c95fbe8ee65909778c60b572774 Mon Sep 17 00:00:00 2001 From: tidely <43219534+tidely@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:59:37 +0300 Subject: [PATCH 202/721] search: Remove noisy buffer search logs (#38679) Buffer search initiates a new search every time a key is pressed in the buffer search bar. This would cancel the task associated with any pending searches. Whenever one of these searches was canceled Zed would log `[search]: oneshot canceled`. This log would trigger almost on every keypress when typing moderately fast. This PR silences these logs by not treating canceled searches as errors. Release Notes: - N/A --- crates/search/src/buffer_search.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 925d390cb3eb5489025818e4826aba691ac1bfa8..126215a0a75ee5057c560462f40958ba71d8cf74 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -794,10 +794,13 @@ impl BufferSearchBar { if let Some(search) = search { cx.spawn_in(window, async move |this, cx| { - search.await?; - this.update_in(cx, |this, window, cx| { - this.activate_current_match(window, cx) - }) + if search.await.is_ok() { + this.update_in(cx, |this, window, cx| { + this.activate_current_match(window, cx) + }) + } else { + Ok(()) + } }) .detach_and_log_err(cx); } @@ -1057,10 +1060,13 @@ impl BufferSearchBar { cx.notify(); cx.spawn_in(window, async move |this, cx| { - search.await?; - this.update_in(cx, |this, window, cx| { - this.activate_current_match(window, cx) - }) + if search.await.is_ok() { + this.update_in(cx, |this, window, cx| { + this.activate_current_match(window, cx) + }) + } else { + Ok(()) + } }) .detach_and_log_err(cx); } From edb804de5a43dffef87aecde31cececab319b565 Mon Sep 17 00:00:00 2001 From: Kaikai <98205900+Kaikaikaifang@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:47:18 +0800 Subject: [PATCH 203/721] go: Stop running ghost tests, fix broken `go test -run` for suites (#38167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closed #33759 Closed #38166 ### Summary This PR fixes the way `go test` commands are generated for **testify suite test methods**. Previously, only the method name was included in the `-run` flag, which caused Go’s test runner to fail to find suite test cases. --- ### Problem https://github.com/user-attachments/assets/e6f80a77-bcf3-457c-8bfb-a7286d44ff71 1. **Incorrect command** was generated for suite tests: ```bash go test -run TestSomething_Success ``` This results in: ``` testing: warning: no tests to run ``` 2. The correct format requires the **suite name + method name**: ```bash go test -run ^TestFooSuite$/TestSomething_Success$ ``` Without the suite prefix (`TestFooSuite`), Go cannot locate test methods defined on a suite struct. --- ### Changes Made * **Updated `runnables.scm`**: * Added a new query rule for suite methods (`.*Suite` receiver types). * Ensures only methods on suite structs (e.g., `FooSuite`) are matched. * Tagged these with `go-testify-suite` in addition to `go-test`. * **Extended task template generation**: * Introduced `GO_SUITE_NAME_TASK_VARIABLE` to capture the suite name. * Create a `TaskTemplate` for the testify suite. * **Improved labeling**: * Labels now show the full path (`go test ./pkg -v -run TestFooSuite/TestSomething_Success`) for clarity. * **Added a test** `test_testify_suite_detection`: * Covered testify suite cases to ensure correct detection and command generation. --- ### Impact https://github.com/user-attachments/assets/ef509183-534a-4aa4-9dc7-01402ac32260 * **Before**: Running a suite test method produced “no tests to run.” * **After**: Suite test methods are runnable individually with the correct `-run` command, and full suites can still be executed as before. ### Release Notes * Fixed generation of `go test` commands for **testify suite test methods**. Suite methods now include both the suite name and the method name in the `-run` flag (e.g., `^TestFooSuite$/TestSomething_Success$`), ensuring they are properly detected and runnable individually. --- crates/languages/src/go.rs | 88 ++++++++++++++++++++++++++- crates/languages/src/go/runnables.scm | 26 +++++--- 2 files changed, 102 insertions(+), 12 deletions(-) diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 60c6d98d21f9b3273f3afdaeb2cbe544af15efe9..52bd93523c2a9e0257249bd2c4464a589d41f286 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -479,6 +479,8 @@ const GO_SUBTEST_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_SUBTEST_NAME")); const GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_TABLE_TEST_CASE_NAME")); +const GO_SUITE_NAME_TASK_VARIABLE: VariableName = + VariableName::Custom(Cow::Borrowed("GO_SUITE_NAME")); impl ContextProvider for GoContextProvider { fn build_context( @@ -537,19 +539,26 @@ impl ContextProvider for GoContextProvider { let go_subtest_variable = extract_subtest_name(_subtest_name.unwrap_or("")) .map(|subtest_name| (GO_SUBTEST_NAME_TASK_VARIABLE.clone(), subtest_name)); - let table_test_case_name = variables.get(&VariableName::Custom(Cow::Borrowed( + let _table_test_case_name = variables.get(&VariableName::Custom(Cow::Borrowed( "_table_test_case_name", ))); - let go_table_test_case_variable = table_test_case_name + let go_table_test_case_variable = _table_test_case_name .and_then(extract_subtest_name) .map(|case_name| (GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.clone(), case_name)); + let _suite_name = variables.get(&VariableName::Custom(Cow::Borrowed("_suite_name"))); + + let go_suite_variable = _suite_name + .and_then(extract_subtest_name) + .map(|suite_name| (GO_SUITE_NAME_TASK_VARIABLE.clone(), suite_name)); + Task::ready(Ok(TaskVariables::from_iter( [ go_package_variable, go_subtest_variable, go_table_test_case_variable, + go_suite_variable, go_module_root_variable, ] .into_iter() @@ -566,6 +575,28 @@ impl ContextProvider for GoContextProvider { let module_cwd = Some(GO_MODULE_ROOT_TASK_VARIABLE.template_value()); Task::ready(Some(TaskTemplates(vec![ + TaskTemplate { + label: format!( + "go test {} -v -run Test{}/{}", + GO_PACKAGE_TASK_VARIABLE.template_value(), + GO_SUITE_NAME_TASK_VARIABLE.template_value(), + VariableName::Symbol.template_value(), + ), + command: "go".into(), + args: vec![ + "test".into(), + "-v".into(), + "-run".into(), + format!( + "\\^Test{}\\$/\\^{}\\$", + GO_SUITE_NAME_TASK_VARIABLE.template_value(), + VariableName::Symbol.template_value(), + ), + ], + cwd: package_cwd.clone(), + tags: vec!["go-testify-suite".to_owned()], + ..TaskTemplate::default() + }, TaskTemplate { label: format!( "go test {} -v -run {}/{}", @@ -819,6 +850,59 @@ mod tests { ); } + #[gpui::test] + fn test_testify_suite_detection(cx: &mut TestAppContext) { + let language = language("go", tree_sitter_go::LANGUAGE.into()); + + let testify_suite = r#" + package main + + import ( + "testing" + + "github.com/stretchr/testify/suite" + ) + + type ExampleSuite struct { + suite.Suite + } + + func TestExampleSuite(t *testing.T) { + suite.Run(t, new(ExampleSuite)) + } + + func (s *ExampleSuite) TestSomething_Success() { + // test code + } + "#; + + let buffer = cx + .new(|cx| crate::Buffer::local(testify_suite, cx).with_language(language.clone(), cx)); + cx.executor().run_until_parked(); + + let runnables: Vec<_> = buffer.update(cx, |buffer, _| { + let snapshot = buffer.snapshot(); + snapshot.runnable_ranges(0..testify_suite.len()).collect() + }); + + let tag_strings: Vec = runnables + .iter() + .flat_map(|r| &r.runnable.tags) + .map(|tag| tag.0.to_string()) + .collect(); + + assert!( + tag_strings.contains(&"go-test".to_string()), + "Should find go-test tag, found: {:?}", + tag_strings + ); + assert!( + tag_strings.contains(&"go-testify-suite".to_string()), + "Should find go-testify-suite tag, found: {:?}", + tag_strings + ); + } + #[gpui::test] fn test_go_runnable_detection(cx: &mut TestAppContext) { let language = language("go", tree_sitter_go::LANGUAGE.into()); diff --git a/crates/languages/src/go/runnables.scm b/crates/languages/src/go/runnables.scm index f56262f799c3d73c8eaadd03665565f75add27ba..90b29f7cbca2f46786c092f3450edc65e04939f1 100644 --- a/crates/languages/src/go/runnables.scm +++ b/crates/languages/src/go/runnables.scm @@ -1,22 +1,28 @@ ; Functions names start with `Test` ( - [ + ( (function_declaration name: (_) @run (#match? @run "^Test.*")) + ) @_ + (#set! tag go-test) +) + +; Suite test methods (testify/suite) +( (method_declaration receiver: (parameter_list (parameter_declaration - name: (identifier) @_receiver_name - type: [ - (pointer_type (type_identifier) @_receiver_type) - (type_identifier) @_receiver_type - ] + type: [ + (pointer_type (type_identifier) @_suite_name) + (type_identifier) @_suite_name + ] ) ) - name: (field_identifier) @run @_method_name - (#match? @_method_name "^Test.*")) - ] @_ - (#set! tag go-test) + name: (field_identifier) @run @_subtest_name + (#match? @_subtest_name "^Test.*") + (#match? @_suite_name ".*Suite") + ) @_ + (#set! tag go-testify-suite) ) ; `go:generate` comments From d8048f46ee9ed0ce3aa6ad1b9e080009817503ec Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 23 Sep 2025 14:12:39 +0300 Subject: [PATCH 204/721] Test task shell commands (#38706) Add tests on task commands, to ensure things like https://github.com/zed-industries/zed/issues/38343 do not come so easily unnoticed and to provide a base to create more tests in the future, if needed. Release Notes: - N/A --------- Co-authored-by: Lukas Wirth --- Cargo.lock | 1 + crates/debugger_ui/src/session/running.rs | 2 +- crates/project/src/terminals.rs | 9 +- crates/task/src/shell_builder.rs | 32 ++--- crates/terminal/src/terminal.rs | 34 ++--- crates/terminal_view/Cargo.toml | 1 + crates/terminal_view/src/terminal_panel.rs | 140 ++++++++++++++++++++- crates/terminal_view/src/terminal_view.rs | 6 +- 8 files changed, 181 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1819b62c3434ad8bdea6dd526b7f68122378e290..39146342f2973691e00a9c5ab9a8d29e50539a47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17156,6 +17156,7 @@ dependencies = [ "itertools 0.14.0", "language", "log", + "pretty_assertions", "project", "rand 0.9.1", "regex", diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index a18a186469a0aaaf5f3d061830446f5ba27dec72..1bef21ed8b19f01fc2371a785544a45cc88bfe9c 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -1026,7 +1026,7 @@ impl RunningState { }; let builder = ShellBuilder::new(remote_shell.as_deref(), &task.resolved.shell); - let command_label = builder.command_label(&task.resolved.command_label); + let command_label = builder.command_label(task.resolved.command.as_deref().unwrap_or("")); let (command, args) = builder.build(task.resolved.command.clone(), &task.resolved.args); diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 94e9999e1344efbc391476e22d107f10052d7694..1143bdebfd46d6c4f44b06cd62002557c0d46936 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -88,15 +88,8 @@ impl Project { let local_path = if is_via_remote { None } else { path.clone() }; let task_state = Some(TaskState { - id: spawn_task.id, - full_label: spawn_task.full_label, - label: spawn_task.label, - command_label: spawn_task.command_label, - hide: spawn_task.hide, + spawned_task: spawn_task.clone(), status: TaskStatus::Running, - show_summary: spawn_task.show_summary, - show_command: spawn_task.show_command, - show_rerun: spawn_task.show_rerun, completion_rx, }); let remote_client = self.remote_client.clone(); diff --git a/crates/task/src/shell_builder.rs b/crates/task/src/shell_builder.rs index c3f0646c02cc427a07505c2ff30157e84d2ca0fe..5ebf483bc3925542212ff125586016dd477c37e2 100644 --- a/crates/task/src/shell_builder.rs +++ b/crates/task/src/shell_builder.rs @@ -241,20 +241,24 @@ impl ShellBuilder { } /// Returns the label to show in the terminal tab - pub fn command_label(&self, command_label: &str) -> String { - match self.kind { - ShellKind::PowerShell => { - format!("{} -C '{}'", self.program, command_label) - } - ShellKind::Cmd => { - format!("{} /C '{}'", self.program, command_label) - } - ShellKind::Posix | ShellKind::Nushell | ShellKind::Fish | ShellKind::Csh => { - let interactivity = self.interactive.then_some("-i ").unwrap_or_default(); - format!( - "{} {interactivity}-c '$\"{}\"'", - self.program, command_label - ) + pub fn command_label(&self, command_to_use_in_label: &str) -> String { + if command_to_use_in_label.trim().is_empty() { + self.program.clone() + } else { + match self.kind { + ShellKind::PowerShell => { + format!("{} -C '{}'", self.program, command_to_use_in_label) + } + ShellKind::Cmd => { + format!("{} /C '{}'", self.program, command_to_use_in_label) + } + ShellKind::Posix | ShellKind::Nushell | ShellKind::Fish | ShellKind::Csh => { + let interactivity = self.interactive.then_some("-i ").unwrap_or_default(); + format!( + "{PROGRAM} {interactivity}-c '{command_to_use_in_label}'", + PROGRAM = self.program + ) + } } } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 987e3272602763f93d350c07b10246707b0ea2ec..cb43d548211b1d154baacf6c1c07db6892cd6dc4 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -44,7 +44,7 @@ use pty_info::PtyProcessInfo; use serde::{Deserialize, Serialize}; use settings::Settings; use smol::channel::{Receiver, Sender}; -use task::{HideStrategy, Shell, TaskId}; +use task::{HideStrategy, Shell, SpawnInTerminal}; use terminal_hyperlinks::RegexSearches; use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings}; use theme::{ActiveTheme, Theme}; @@ -739,17 +739,11 @@ struct CopyTemplate { window_id: u64, } +#[derive(Debug)] pub struct TaskState { - pub id: TaskId, - pub full_label: String, - pub label: String, - pub command_label: String, pub status: TaskStatus, pub completion_rx: Receiver>, - pub hide: HideStrategy, - pub show_summary: bool, - pub show_command: bool, - pub show_rerun: bool, + pub spawned_task: SpawnInTerminal, } /// A status of the current terminal tab's task. @@ -1839,9 +1833,9 @@ impl Terminal { match &self.task { Some(task_state) => { if truncate { - truncate_and_trailoff(&task_state.label, MAX_CHARS) + truncate_and_trailoff(&task_state.spawned_task.label, MAX_CHARS) } else { - task_state.full_label.clone() + task_state.spawned_task.full_label.clone() } } None => self @@ -1949,10 +1943,10 @@ impl Terminal { let (finished_successfully, task_line, command_line) = task_summary(task, error_code); let mut lines_to_show = Vec::new(); - if task.show_summary { + if task.spawned_task.show_summary { lines_to_show.push(task_line.as_str()); } - if task.show_command { + if task.spawned_task.show_command { lines_to_show.push(command_line.as_str()); } @@ -1964,7 +1958,7 @@ impl Terminal { unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) }; } - match task.hide { + match task.spawned_task.hide { HideStrategy::Never => {} HideStrategy::Always => { cx.emit(Event::CloseTerminal); @@ -2014,7 +2008,11 @@ pub fn row_to_string(row: &Row) -> String { const TASK_DELIMITER: &str = "⏵ "; fn task_summary(task: &TaskState, error_code: Option) -> (bool, String, String) { - let escaped_full_label = task.full_label.replace("\r\n", "\r").replace('\n', "\r"); + let escaped_full_label = task + .spawned_task + .full_label + .replace("\r\n", "\r") + .replace('\n', "\r"); let (success, task_line) = match error_code { Some(0) => ( true, @@ -2031,7 +2029,11 @@ fn task_summary(task: &TaskState, error_code: Option) -> (bool, String, Str format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished"), ), }; - let escaped_command_label = task.command_label.replace("\r\n", "\r").replace('\n', "\r"); + let escaped_command_label = task + .spawned_task + .command_label + .replace("\r\n", "\r") + .replace('\n', "\r"); let command_line = format!("{TASK_DELIMITER}Command: {escaped_command_label}"); (success, task_line, command_line) } diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index e424d89917bc02b3398f1457a676ab03cdd1b179..85ee506d69444fa7d58b536acac3a00088e3f047 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -29,6 +29,7 @@ gpui.workspace = true itertools.workspace = true language.workspace = true log.workspace = true +pretty_assertions.workspace = true project.workspace = true regex.workspace = true task.workspace = true diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index faea73849ba83b3e2899f3efd995d4557c6b1e3c..25b3671ccad69d02df383108e9d853ce9231ed70 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -544,7 +544,7 @@ impl TerminalPanel { .and_then(|remote_client| remote_client.read(cx).shell()); let builder = ShellBuilder::new(remote_shell.as_deref(), &task.shell); - let command_label = builder.command_label(&task.command_label); + let command_label = builder.command_label(task.command.as_deref().unwrap_or("")); let (command, args) = builder.build(task.command.clone(), &task.args); let task = SpawnInTerminal { @@ -664,7 +664,7 @@ impl TerminalPanel { .filter_map(|(index, item)| Some((index, item.act_as::(cx)?))) .filter_map(|(index, terminal_view)| { let task_state = terminal_view.read(cx).terminal().read(cx).task()?; - if &task_state.full_label == label { + if &task_state.spawned_task.full_label == label { Some((index, terminal_view)) } else { None @@ -1624,3 +1624,139 @@ impl Render for InlineAssistTabBarButton { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use gpui::TestAppContext; + use pretty_assertions::assert_eq; + use project::FakeFs; + use settings::SettingsStore; + + #[gpui::test] + async fn test_spawn_an_empty_task(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx)); + + let (window_handle, terminal_panel) = workspace + .update(cx, |workspace, window, cx| { + let window_handle = window.window_handle(); + let terminal_panel = cx.new(|cx| TerminalPanel::new(workspace, window, cx)); + (window_handle, terminal_panel) + }) + .unwrap(); + + let task = window_handle + .update(cx, |_, window, cx| { + terminal_panel.update(cx, |terminal_panel, cx| { + terminal_panel.spawn_task(&SpawnInTerminal::default(), window, cx) + }) + }) + .unwrap(); + + let terminal = task.await.unwrap(); + let expected_shell = util::get_system_shell(); + terminal + .update(cx, |terminal, _| { + let task_metadata = terminal + .task() + .expect("When spawning a task, should have the task metadata") + .spawned_task + .clone(); + assert_eq!(task_metadata.env, HashMap::default()); + assert_eq!(task_metadata.cwd, None); + assert_eq!(task_metadata.shell, task::Shell::System); + assert_eq!( + task_metadata.command, + Some(expected_shell.clone()), + "Empty tasks should spawn a -i shell" + ); + assert_eq!(task_metadata.args, Vec::::new()); + assert_eq!( + task_metadata.command_label, expected_shell, + "We show the shell launch for empty commands" + ); + }) + .unwrap(); + } + + // A complex Unix command won't be properly parsed by the Windows terminal hence omit the test there. + #[cfg(unix)] + #[gpui::test] + async fn test_spawn_script_like_task(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx)); + + let (window_handle, terminal_panel) = workspace + .update(cx, |workspace, window, cx| { + let window_handle = window.window_handle(); + let terminal_panel = cx.new(|cx| TerminalPanel::new(workspace, window, cx)); + (window_handle, terminal_panel) + }) + .unwrap(); + + let user_command = r#"REPO_URL=$(git remote get-url origin | sed -e \"s/^git@\\(.*\\):\\(.*\\)\\.git$/https:\\/\\/\\1\\/\\2/\"); COMMIT_SHA=$(git log -1 --format=\"%H\" -- \"${ZED_RELATIVE_FILE}\"); echo \"${REPO_URL}/blob/${COMMIT_SHA}/${ZED_RELATIVE_FILE}#L${ZED_ROW}-$(echo $(($(wc -l <<< \"$ZED_SELECTED_TEXT\") + $ZED_ROW - 1)))\" | xclip -selection clipboard"#.to_string(); + + let expected_cwd = PathBuf::from("/some/work"); + let task = window_handle + .update(cx, |_, window, cx| { + terminal_panel.update(cx, |terminal_panel, cx| { + terminal_panel.spawn_task( + &SpawnInTerminal { + command: Some(user_command.clone()), + cwd: Some(expected_cwd.clone()), + ..SpawnInTerminal::default() + }, + window, + cx, + ) + }) + }) + .unwrap(); + + let terminal = task.await.unwrap(); + let shell = util::get_system_shell(); + terminal + .update(cx, |terminal, _| { + let task_metadata = terminal + .task() + .expect("When spawning a task, should have the task metadata") + .spawned_task + .clone(); + assert_eq!(task_metadata.env, HashMap::default()); + assert_eq!(task_metadata.cwd, Some(expected_cwd)); + assert_eq!(task_metadata.shell, task::Shell::System); + assert_eq!(task_metadata.command, Some(shell.clone())); + assert_eq!( + task_metadata.args, + vec!["-i".to_string(), "-c".to_string(), user_command.clone(),], + "Use command should have been moved into the arguments, as we're spawning a new -i shell", + ); + assert_eq!( + task_metadata.command_label, + format!("{shell} {interactive}-c '{user_command}'", interactive = if cfg!(windows) {""} else {"-i "}), + "We want to show to the user the entire command spawned"); + }) + .unwrap(); + } + + pub fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + let store = SettingsStore::test(cx); + cx.set_global(store); + theme::init(theme::LoadThemes::JustBase, cx); + client::init_settings(cx); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + editor::init(cx); + crate::init(cx); + }); + } +} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d7adf74acb37e848ccb2d8670f970054d46ea0ae..1f1e48562fc35575813cd81921aace9188f8a0c0 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -476,7 +476,7 @@ impl TerminalView { .terminal .read(cx) .task() - .map(|task| terminal_rerun_override(&task.id)) + .map(|task| terminal_rerun_override(&task.spawned_task.id)) .unwrap_or_default(); window.dispatch_action(Box::new(task), cx); } @@ -831,11 +831,11 @@ impl TerminalView { } fn rerun_button(task: &TaskState) -> Option { - if !task.show_rerun { + if !task.spawned_task.show_rerun { return None; } - let task_id = task.id.clone(); + let task_id = task.spawned_task.id.clone(); Some( IconButton::new("rerun-icon", IconName::Rerun) .icon_size(IconSize::Small) From 1bf83323337d123a1e9c91a34597b7fbde3f13ad Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 23 Sep 2025 13:39:48 +0200 Subject: [PATCH 205/721] editor: Deduplicate locations in `navigate_to_hover_links` (#38707) Closes https://github.com/zed-industries/zed/issues/6730#issuecomment-3320933701 That way if multiple servers are running while reporting the same results we prevent opening multi buffers for single entries. Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/editor.rs | 114 ++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 38ebdb4909b051d96700447617b392a7741714a7..1d6ac4bcca2c1dfb9e34b461965adc97e4a6b6eb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -177,7 +177,7 @@ use std::{ borrow::Cow, cell::{OnceCell, RefCell}, cmp::{self, Ordering, Reverse}, - iter::Peekable, + iter::{self, Peekable}, mem, num::NonZeroU32, ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive}, @@ -16401,15 +16401,30 @@ impl Editor { let workspace = self.workspace(); - cx.spawn_in(window, async move |editor, acx| { - let mut locations: Vec = future::join_all(definitions) + cx.spawn_in(window, async move |editor, cx| { + let locations: Vec = future::join_all(definitions) .await .into_iter() .filter_map(|location| location.transpose()) .collect::>() .context("location tasks")?; + let mut locations = cx.update(|_, cx| { + locations + .into_iter() + .map(|location| { + let buffer = location.buffer.read(cx); + (location.buffer, location.range.to_point(buffer)) + }) + .into_group_map() + })?; + let mut num_locations = 0; + for ranges in locations.values_mut() { + ranges.sort_by_key(|range| (range.start, Reverse(range.end))); + ranges.dedup(); + num_locations += ranges.len(); + } - if locations.len() > 1 { + if num_locations > 1 { let Some(workspace) = workspace else { return Ok(Navigated::No); }; @@ -16421,14 +16436,14 @@ impl Editor { Some(GotoDefinitionKind::Type) => "Types", }; let title = editor - .update_in(acx, |_, _, cx| { + .update_in(cx, |_, _, cx| { let target = locations .iter() - .map(|location| { - location - .buffer + .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v)) + .map(|(buffer, location)| { + buffer .read(cx) - .text_for_range(location.range.clone()) + .text_for_range(location.clone()) .collect::() }) .filter(|text| !text.contains('\n')) @@ -16444,7 +16459,7 @@ impl Editor { .context("buffer title")?; let opened = workspace - .update_in(acx, |workspace, window, cx| { + .update_in(cx, |workspace, window, cx| { Self::open_locations_in_multibuffer( workspace, locations, @@ -16458,11 +16473,11 @@ impl Editor { .is_ok(); anyhow::Ok(Navigated::from_bool(opened)) - } else if locations.is_empty() { + } else if num_locations == 0 { // If there is one url or file, open it directly match first_url_or_file { Some(Either::Left(url)) => { - acx.update(|_, cx| cx.open_url(&url))?; + cx.update(|_, cx| cx.open_url(&url))?; Ok(Navigated::Yes) } Some(Either::Right(path)) => { @@ -16471,7 +16486,7 @@ impl Editor { }; workspace - .update_in(acx, |workspace, window, cx| { + .update_in(cx, |workspace, window, cx| { workspace.open_resolved_path(path, window, cx) })? .await?; @@ -16484,14 +16499,16 @@ impl Editor { return Ok(Navigated::No); }; - let target = locations.pop().unwrap(); - editor.update_in(acx, |editor, window, cx| { - let range = target.range.to_point(target.buffer.read(cx)); + let (target_buffer, target_ranges) = locations.into_iter().next().unwrap(); + let target_range = target_ranges.first().unwrap().clone(); + + editor.update_in(cx, |editor, window, cx| { + let range = target_range.to_point(target_buffer.read(cx)); let range = editor.range_for_match(&range); let range = collapse_multiline_range(range); if !split - && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() + && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() { editor.go_to_singleton_buffer_range(range, window, cx); } else { @@ -16507,7 +16524,7 @@ impl Editor { workspace.open_project_item( pane, - target.buffer.clone(), + target_buffer.clone(), true, true, window, @@ -16617,18 +16634,31 @@ impl Editor { let Some(locations) = references.await? else { return anyhow::Ok(Navigated::No); }; + let mut locations = cx.update(|_, cx| { + locations + .into_iter() + .map(|location| { + let buffer = location.buffer.read(cx); + (location.buffer, location.range.to_point(buffer)) + }) + .into_group_map() + })?; if locations.is_empty() { return anyhow::Ok(Navigated::No); } + for ranges in locations.values_mut() { + ranges.sort_by_key(|range| (range.start, Reverse(range.end))); + ranges.dedup(); + } workspace.update_in(cx, |workspace, window, cx| { let target = locations .iter() - .map(|location| { - location - .buffer + .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v)) + .map(|(buffer, location)| { + buffer .read(cx) - .text_for_range(location.range.clone()) + .text_for_range(location.clone()) .collect::() }) .filter(|text| !text.contains('\n')) @@ -16657,7 +16687,7 @@ impl Editor { /// Opens a multibuffer with the given project locations in it pub fn open_locations_in_multibuffer( workspace: &mut Workspace, - mut locations: Vec, + locations: std::collections::HashMap, Vec>>, title: String, split: bool, multibuffer_selection_mode: MultibufferSelectionMode, @@ -16669,11 +16699,8 @@ impl Editor { return; } - locations.sort_by_key(|location| location.buffer.read(cx).remote_id()); - - let mut locations = locations.into_iter().peekable(); - let mut ranges: Vec> = Vec::new(); let capability = workspace.project().read(cx).capability(); + let mut ranges = >>::new(); // a key to find existing multibuffer editors with the same set of locations // to prevent us from opening more and more multibuffer tabs for searches and the like @@ -16681,26 +16708,12 @@ impl Editor { let excerpt_buffer = cx.new(|cx| { let key = &mut key.1; let mut multibuffer = MultiBuffer::new(capability); - while let Some(location) = locations.next() { - let buffer = location.buffer.read(cx); - let mut ranges_for_buffer = Vec::new(); - let range = location.range.to_point(buffer); - ranges_for_buffer.push(range.clone()); - - while let Some(next_location) = - locations.next_if(|next_location| next_location.buffer == location.buffer) - { - ranges_for_buffer.push(next_location.range.to_point(buffer)); - } - + for (buffer, mut ranges_for_buffer) in locations { ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); - key.push(( - location.buffer.read(cx).remote_id(), - ranges_for_buffer.clone(), - )); + key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone())); let (new_ranges, _) = multibuffer.set_excerpts_for_path( - PathKey::for_buffer(&location.buffer, cx), - location.buffer.clone(), + PathKey::for_buffer(&buffer, cx), + buffer.clone(), ranges_for_buffer, multibuffer_context_lines(cx), cx, @@ -19782,11 +19795,14 @@ impl Editor { .selections .all_anchors(cx) .iter() - .map(|selection| Location { - buffer: buffer.clone(), - range: selection.start.text_anchor..selection.end.text_anchor, + .map(|selection| { + ( + buffer.clone(), + (selection.start.text_anchor..selection.end.text_anchor) + .to_point(buffer.read(cx)), + ) }) - .collect::>(); + .into_group_map(); cx.spawn_in(window, async move |_, cx| { workspace.update_in(cx, |workspace, window, cx| { From 9e7302520ec93a96a9b275a817f083b96620148e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=BB=E4=BA=8C=E6=B0=AE=E6=9D=82=E8=8F=B2?= <40173605+Cupnfish@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:03:29 +0800 Subject: [PATCH 206/721] Fix UTF-8 character boundary panic in DirectWrite text layout (#37767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Zed was crashing with a UTF-8 character boundary error when rendering text containing multi-byte characters (like emojis or CJK characters): ``` Thread "main" panicked with "byte index 49 is not a char boundary; it is inside '…' (bytes 48..51)" ``` ## Root Cause Analysis The PR reviewer correctly identified that the issue was not in the DirectWrite boundary handling, but rather in the text run length calculation in the text system. When text runs are split across lines in `text_system.rs:426`, the calculation: ```rust let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; ``` This could result in `run_len_within_line` values that don't respect UTF-8 character boundaries, especially when multi-byte characters (like '…' which is 3 bytes) get split across lines. The resulting `FontRun` objects would have lengths that don't align with character boundaries, causing the panic when DirectWrite tries to slice the string. ## Solution Fixed the issue by adding UTF-8 character boundary validation in the text system where run lengths are calculated. The fix ensures that when text runs are split across lines, the split always occurs at valid UTF-8 character boundaries: ```rust // Ensure the run length respects UTF-8 character boundaries if run_len_within_line > 0 { let text_slice = &line_text[run_start - line_start..]; if run_len_within_line < text_slice.len() && !text_slice.is_char_boundary(run_len_within_line) { // Find the previous character boundary using efficient bit-level checking // UTF-8 characters are at most 4 bytes, so we only need to check up to 3 bytes back let lower_bound = run_len_within_line.saturating_sub(3); let search_range = &text_slice.as_bytes()[lower_bound..=run_len_within_line]; // SAFETY: A valid character boundary must exist in this range because: // 1. run_len_within_line is a valid position in the string slice // 2. UTF-8 characters are at most 4 bytes, so some boundary exists in [run_len_within_line-3..=run_len_within_line] let pos_from_lower = unsafe { search_range .iter() .rposition(|&b| (b as i8) >= -0x40) .unwrap_unchecked() }; run_len_within_line = lower_bound + pos_from_lower; } } ``` ## Testing - ✅ Builds successfully on all platforms - ✅ Eliminates UTF-8 character boundary panics - ✅ Maintains existing functionality for all text types - ✅ Handles edge cases like very long multi-byte characters ## Benefits 1. **Root cause fix**: Addresses the issue at the source rather than treating symptoms 2. **Performance optimal**: Uses the same efficient algorithm as the standard library 3. **Minimal changes**: Only modifies the specific problematic code path 4. **Future compatible**: Can be easily replaced with `str::floor_char_boundary()` when stabilized ## Alternative Approaches Considered 1. **DirectWrite boundary fixing**: Initially tried to fix in DirectWrite, but this was treating symptoms rather than the root cause 2. **Helper function approach**: Considered extracting to a helper function, but inlined implementation is more appropriate for this specific use case 3. **Standard library methods**: `floor_char_boundary()` is not yet stable, so implemented equivalent logic The chosen approach provides the best balance of performance, safety, and code maintainability. --- Release Notes: - N/A --- crates/gpui/src/text_system.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index efa4ad032a66ce92a71cbd82be6ed4a63d527858..b0776051d6049e70397912c28435ebe4eb2b8d7a 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -429,7 +429,33 @@ impl WindowTextSystem { break; }; - let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; + let mut run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; + + // Ensure the run length respects UTF-8 character boundaries + if run_len_within_line > 0 { + let text_slice = &line_text[run_start - line_start..]; + if run_len_within_line < text_slice.len() + && !text_slice.is_char_boundary(run_len_within_line) + { + // Find the previous character boundary using efficient bit-level checking + // UTF-8 characters are at most 4 bytes, so we only need to check up to 3 bytes back + let lower_bound = run_len_within_line.saturating_sub(3); + let search_range = + &text_slice.as_bytes()[lower_bound..=run_len_within_line]; + + // SAFETY: A valid character boundary must exist in this range because: + // 1. run_len_within_line is a valid position in the string slice + // 2. UTF-8 characters are at most 4 bytes, so some boundary exists in [run_len_within_line-3..=run_len_within_line] + let pos_from_lower = unsafe { + search_range + .iter() + .rposition(|&b| (b as i8) >= -0x40) + .unwrap_unchecked() + }; + + run_len_within_line = lower_bound + pos_from_lower; + } + } if last_font == Some(run.font.clone()) { font_runs.last_mut().unwrap().len += run_len_within_line; From 3ac14e15bb5f9cabdb57f9c7ccc1ac7255cec85f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 23 Sep 2025 14:14:03 +0200 Subject: [PATCH 207/721] agent: Fix Gemini refusing all requests with file-based tool calls (#38705) Solves an issue where Google APIs refuse all requests with file-based tool calls attached. This seems to get triggered in the case where: - copy_path + another file-based tool call is enabled - default terminal is `/bin/bash` or something similar It is unclear why this is happening, but removing the terminal commands in those tool calls seems to have solved the issue. Closes #37180 and #37414 Release Notes: - agent: Fix Gemini refusing requests with certain profiles/systems. --- crates/agent2/src/templates/system_prompt.hbs | 18 +++++++++++------- crates/agent2/src/tools/copy_path_tool.rs | 4 ++-- .../agent2/src/tools/create_directory_tool.rs | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/agent2/src/templates/system_prompt.hbs b/crates/agent2/src/templates/system_prompt.hbs index a9f67460d81e79f03d0a0a9b60cd4d6c32fc3b20..ca324fad7acccb3e50f1140c8f99d52319d159d4 100644 --- a/crates/agent2/src/templates/system_prompt.hbs +++ b/crates/agent2/src/templates/system_prompt.hbs @@ -48,16 +48,15 @@ The one exception to this is if the user references something you don't know abo ## Code Block Formatting Whenever you mention a code block, you MUST use ONLY use the following format: + ```path/to/Something.blah#L123-456 (code goes here) ``` -The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah -is a path in the project. (If there is no valid path in the project, then you can use -/dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser -does not understand the more common ```language syntax, or bare ``` blocks. It only -understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again. + +The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah is a path in the project. (If there is no valid path in the project, then you can use /dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser does not understand the more common ```language syntax, or bare ``` blocks. It only understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again. Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP! You have made a mistake. You can only ever put paths after triple backticks! + Based on all the information I've gathered, here's a summary of how this system works: 1. The README file is loaded into the system. @@ -74,6 +73,7 @@ This is the last header in the README. ``` 4. Finally, it passes this information on to the next process. + In Markdown, hash marks signify headings. For example: ```/dev/null/example.md#L1-3 @@ -82,6 +82,7 @@ In Markdown, hash marks signify headings. For example: ### Level 3 heading ``` + Here are examples of ways you must never render code blocks: In Markdown, hash marks signify headings. For example: @@ -91,7 +92,9 @@ In Markdown, hash marks signify headings. For example: ### Level 3 heading ``` + This example is unacceptable because it does not include the path. + In Markdown, hash marks signify headings. For example: ```markdown @@ -101,14 +104,15 @@ In Markdown, hash marks signify headings. For example: ``` This example is unacceptable because it has the language instead of the path. + In Markdown, hash marks signify headings. For example: # Level 1 heading ## Level 2 heading ### Level 3 heading -This example is unacceptable because it uses indentation to mark the code block -instead of backticks with a path. +This example is unacceptable because it uses indentation to mark the code block instead of backticks with a path. + In Markdown, hash marks signify headings. For example: ```markdown diff --git a/crates/agent2/src/tools/copy_path_tool.rs b/crates/agent2/src/tools/copy_path_tool.rs index 8fcd80391f828c7503701a86e9e1b400115763d6..905c090883192064782be62905c991393249f911 100644 --- a/crates/agent2/src/tools/copy_path_tool.rs +++ b/crates/agent2/src/tools/copy_path_tool.rs @@ -9,14 +9,14 @@ use std::sync::Arc; use util::markdown::MarkdownInlineCode; /// Copies a file or directory in the project, and returns confirmation that the copy succeeded. -/// Directory contents will be copied recursively (like `cp -r`). +/// Directory contents will be copied recursively. /// /// This tool should be used when it's desirable to create a copy of a file or directory without modifying the original. /// It's much more efficient than doing this by separately reading and then writing the file or directory's contents, so this tool should be preferred over that approach whenever copying is the goal. #[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct CopyPathToolInput { /// The source path of the file or directory to copy. - /// If a directory is specified, its contents will be copied recursively (like `cp -r`). + /// If a directory is specified, its contents will be copied recursively. /// /// /// If the project has the following files: diff --git a/crates/agent2/src/tools/create_directory_tool.rs b/crates/agent2/src/tools/create_directory_tool.rs index 30bd6418db35182358ed6139a9078e40a29dfac5..b6240e99cf4dd6698bf9f46edd8d4681247d8f64 100644 --- a/crates/agent2/src/tools/create_directory_tool.rs +++ b/crates/agent2/src/tools/create_directory_tool.rs @@ -11,7 +11,7 @@ use crate::{AgentTool, ToolCallEventStream}; /// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created. /// -/// This tool creates a directory and all necessary parent directories (similar to `mkdir -p`). It should be used whenever you need to create new directories within the project. +/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project. #[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct CreateDirectoryToolInput { /// The path of the new directory. From 15e75bdf04bfb2c4c22e2e42a354df38ec564b06 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Tue, 23 Sep 2025 08:32:14 -0400 Subject: [PATCH 208/721] Add show_summary & show_command to the initial_tasks.json (#38660) Release Notes: - Added "show_summary" & "show_command" settings to the initial tasks.json file. This makes the initial task template match the docs here: https://zed.dev/docs/tasks --- assets/settings/initial_tasks.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/settings/initial_tasks.json b/assets/settings/initial_tasks.json index 5cead67b6d5bb89e878e3bfb8d250dcbbd2ce447..a79e98063237ca297a89b0d151bd48149061b7bb 100644 --- a/assets/settings/initial_tasks.json +++ b/assets/settings/initial_tasks.json @@ -43,7 +43,11 @@ // "args": ["--login"] // } // } - "shell": "system" + "shell": "system", + // Whether to show the task line in the output of the spawned task, defaults to `true`. + "show_summary": true, + // Whether to show the command line in the output of the spawned task, defaults to `true`. + "show_command": true // Represents the tags for inline runnable indicators, or spawning multiple tasks at once. // "tags": [] } From 2e87387e53b14b7a5e9368b876e110afb7f7cadf Mon Sep 17 00:00:00 2001 From: localcc Date: Tue, 23 Sep 2025 15:57:26 +0200 Subject: [PATCH 209/721] Change emulated GPU message on Windows (#38710) Release Notes: - N/A --- crates/zed/src/zed.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6029ce6530a729ccd0b299bdcab2acbd620bbbdd..5e9ebb3433ee10451102b6066bda73f4cb4d1a51 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -512,17 +512,30 @@ fn show_software_emulation_warning_if_needed( cx: &mut Context, ) { if specs.is_software_emulated && std::env::var("ZED_ALLOW_EMULATED_GPU").is_err() { + let (graphics_api, docs_url, open_url) = if cfg!(target_os = "windows") { + ( + "DirectX", + "https://zed.dev/docs/windows", + "https://zed.dev/docs/windows", + ) + } else { + ( + "Vulkan", + "https://zed.dev/docs/linux", + "https://zed.dev/docs/linux#zed-fails-to-open-windows", + ) + }; let message = format!( db::indoc! {r#" - Zed uses Vulkan for rendering and requires a compatible GPU. + Zed uses {} for rendering and requires a compatible GPU. Currently you are using a software emulated GPU ({}) which will result in awful performance. - For troubleshooting see: https://zed.dev/docs/linux + For troubleshooting see: {} Set ZED_ALLOW_EMULATED_GPU=1 env var to permanently override. "#}, - specs.device_name + graphics_api, specs.device_name, docs_url ); let prompt = window.prompt( PromptLevel::Critical, @@ -534,7 +547,7 @@ fn show_software_emulation_warning_if_needed( cx.spawn(async move |_, cx| { if prompt.await == Ok(1) { cx.update(|cx| { - cx.open_url("https://zed.dev/docs/linux#zed-fails-to-open-windows"); + cx.open_url(open_url); cx.quit(); }) .ok(); From 271d67f7adde107a728fe2e9d4cb964ac91cd15b Mon Sep 17 00:00:00 2001 From: Alvaro Parker <64918109+AlvaroParker@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:20:49 -0300 Subject: [PATCH 210/721] git: Fix git amend on panel (#38681) Closes #38651 `git_panel.set_amend_pending(false, cx);` was being called before `git_panel.commit_changes(...)` which was causing the commit buffer to be cleared/reset before actually sending the commit request to git. Introduced by #35268 which added clear buffer functionality to the `set_amend_pending` function. Release Notes: - Fix git amend on panel sending "Update ..." instead of the original commit message - FIx git amend button not working --- crates/git_ui/src/git_panel.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index f8df51357da99909b28e871a8aa6202328d2412d..b2c855f0fa341c19e7f44446e37240ee5a70e5e6 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -1425,7 +1425,6 @@ impl GitPanel { self.load_last_commit_message_if_empty(cx); } else { telemetry::event!("Git Amended", source = "Git Panel"); - self.set_amend_pending(false, cx); self.commit_changes( CommitOptions { amend: true, @@ -1599,6 +1598,9 @@ impl GitPanel { }); self.pending_commit = Some(task); + if options.amend { + self.set_amend_pending(false, cx); + } } pub(crate) fn uncommit(&mut self, window: &mut Window, cx: &mut Context) { @@ -3445,7 +3447,6 @@ impl GitPanel { telemetry::event!("Git Committed", source = "Git Panel"); git_panel .update(cx, |git_panel, cx| { - git_panel.set_amend_pending(false, cx); git_panel.commit_changes( CommitOptions { amend, signoff }, window, From 0aad47493e184422b758350ea6c6330aca9b3633 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Tue, 23 Sep 2025 12:32:36 -0300 Subject: [PATCH 211/721] zeta2: Use global zeta in Inspector (#38718) The edit prediction debug tools has been renamed to zeta2 inspector because it's now zeta specific. It will now always display the last prediction request context, prompt, and model response. Release Notes: - N/A --------- Co-authored-by: Bennet Co-authored-by: Bennet Bo Fenner --- Cargo.lock | 60 +- Cargo.toml | 4 +- .../src/declaration_scoring.rs | 11 +- .../src/edit_prediction_context.rs | 2 +- crates/edit_prediction_context/src/excerpt.rs | 8 +- crates/zed/Cargo.toml | 2 +- crates/zed/src/main.rs | 2 +- crates/zeta2/Cargo.toml | 3 +- crates/zeta2/src/zeta2.rs | 116 ++- .../Cargo.toml | 16 +- .../LICENSE-GPL | 0 crates/zeta2_tools/src/zeta2_tools.rs | 663 ++++++++++++++++++ 12 files changed, 810 insertions(+), 77 deletions(-) rename crates/{edit_prediction_tools => zeta2_tools}/Cargo.toml (77%) rename crates/{edit_prediction_tools => zeta2_tools}/LICENSE-GPL (100%) create mode 100644 crates/zeta2_tools/src/zeta2_tools.rs diff --git a/Cargo.lock b/Cargo.lock index 39146342f2973691e00a9c5ab9a8d29e50539a47..c57d06ef884b78d22042324d02a78d23ec485608 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5214,33 +5214,6 @@ dependencies = [ "zlog", ] -[[package]] -name = "edit_prediction_tools" -version = "0.1.0" -dependencies = [ - "clap", - "collections", - "edit_prediction_context", - "editor", - "futures 0.3.31", - "gpui", - "indoc", - "language", - "log", - "pretty_assertions", - "project", - "serde", - "serde_json", - "settings", - "text", - "ui", - "ui_input", - "util", - "workspace", - "workspace-hack", - "zlog", -] - [[package]] name = "editor" version = "0.1.0" @@ -21274,7 +21247,6 @@ dependencies = [ "debugger_ui", "diagnostics", "edit_prediction_button", - "edit_prediction_tools", "editor", "env_logger 0.11.8", "extension", @@ -21386,6 +21358,7 @@ dependencies = [ "zed_env_vars", "zeta", "zeta2", + "zeta2_tools", "zlog", "zlog_settings", ] @@ -21669,6 +21642,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", + "chrono", "client", "cloud_llm_client", "edit_prediction", @@ -21689,6 +21663,36 @@ dependencies = [ "worktree", ] +[[package]] +name = "zeta2_tools" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "client", + "collections", + "edit_prediction_context", + "editor", + "futures 0.3.31", + "gpui", + "indoc", + "language", + "log", + "pretty_assertions", + "project", + "serde", + "serde_json", + "settings", + "text", + "ui", + "ui_input", + "util", + "workspace", + "workspace-hack", + "zeta2", + "zlog", +] + [[package]] name = "zeta_cli" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3c431a29eb53420dadde38a1c1ad30a1f61d44c1..8aabe6ad40c9d6b854d3453b08972dd8a4364e09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ members = [ "crates/edit_prediction", "crates/edit_prediction_button", "crates/edit_prediction_context", - "crates/edit_prediction_tools", + "crates/zeta2_tools", "crates/editor", "crates/eval", "crates/explorer_command_injector", @@ -319,7 +319,7 @@ image_viewer = { path = "crates/image_viewer" } edit_prediction = { path = "crates/edit_prediction" } edit_prediction_button = { path = "crates/edit_prediction_button" } edit_prediction_context = { path = "crates/edit_prediction_context" } -edit_prediction_tools = { path = "crates/edit_prediction_tools" } +zeta2_tools = { path = "crates/zeta2_tools" } inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } jj = { path = "crates/jj" } diff --git a/crates/edit_prediction_context/src/declaration_scoring.rs b/crates/edit_prediction_context/src/declaration_scoring.rs index fee7498a696c0608704dab6e8ab9f012c95660b5..f655387f680a53413161383f0678b21456c271f6 100644 --- a/crates/edit_prediction_context/src/declaration_scoring.rs +++ b/crates/edit_prediction_context/src/declaration_scoring.rs @@ -3,7 +3,7 @@ use itertools::Itertools as _; use language::BufferSnapshot; use ordered_float::OrderedFloat; use serde::Serialize; -use std::{collections::HashMap, ops::Range}; +use std::{cmp::Reverse, collections::HashMap, ops::Range}; use strum::EnumIter; use text::{Point, ToPoint}; @@ -159,11 +159,10 @@ pub fn scored_snippets( .collect::>(); snippets.sort_unstable_by_key(|snippet| { - OrderedFloat( - snippet - .score_density(SnippetStyle::Declaration) - .max(snippet.score_density(SnippetStyle::Signature)), - ) + let score_density = snippet + .score_density(SnippetStyle::Declaration) + .max(snippet.score_density(SnippetStyle::Signature)); + Reverse(OrderedFloat(score_density)) }); snippets diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs index aeda74811296b70fc48198b1c3f72a50cfd7c31e..f3752abab991493660d197fe871838a71f6c8ad1 100644 --- a/crates/edit_prediction_context/src/edit_prediction_context.rs +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -16,7 +16,7 @@ pub use excerpt::*; pub use reference::*; pub use syntax_index::*; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct EditPredictionContext { pub excerpt: EditPredictionExcerpt, pub excerpt_text: EditPredictionExcerptText, diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index 764c9040f247561ba6058dfd2954f42085297a4c..58549d579dca2b589fb8da01e4963782845933e9 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -1,4 +1,4 @@ -use language::BufferSnapshot; +use language::{BufferSnapshot, LanguageId}; use std::ops::Range; use text::{Point, ToOffset as _, ToPoint as _}; use tree_sitter::{Node, TreeCursor}; @@ -20,7 +20,7 @@ use crate::{BufferDeclaration, declaration::DeclarationId, syntax_index::SyntaxI // // - Filter outer syntax layers that don't support edit prediction. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct EditPredictionExcerptOptions { /// Limit for the number of bytes in the window around the cursor. pub max_bytes: usize, @@ -31,6 +31,7 @@ pub struct EditPredictionExcerptOptions { pub target_before_cursor_over_total_bytes: f32, } +// TODO: consider merging these #[derive(Debug, Clone)] pub struct EditPredictionExcerpt { pub range: Range, @@ -42,6 +43,7 @@ pub struct EditPredictionExcerpt { pub struct EditPredictionExcerptText { pub body: String, pub parent_signatures: Vec, + pub language_id: Option, } impl EditPredictionExcerpt { @@ -54,9 +56,11 @@ impl EditPredictionExcerpt { .iter() .map(|(_, range)| buffer.text_for_range(range.clone()).collect::()) .collect(); + let language_id = buffer.language().map(|l| l.id()); EditPredictionExcerptText { body, parent_signatures, + language_id, } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3d7bbe8642c64d7cfff9bfb9169c3df17e18d854..c2d8733b8ae1f98faa7e1a5ba0d1851ba7c9ccc4 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -52,7 +52,7 @@ debugger_tools.workspace = true debugger_ui.workspace = true diagnostics.workspace = true editor.workspace = true -edit_prediction_tools.workspace = true +zeta2_tools.workspace = true env_logger.workspace = true extension.workspace = true extension_host.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3dbbed0ce50ad84a1717f81afdf95c432b09259d..af882e76231458f160fac9354d2c57150560c00b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -549,7 +549,7 @@ pub fn main() { language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx); agent_settings::init(cx); acp_tools::init(cx); - edit_prediction_tools::init(cx); + zeta2_tools::init(cx); web_search::init(cx); web_search_providers::init(app_state.client.clone(), cx); snippet_provider::init(cx); diff --git a/crates/zeta2/Cargo.toml b/crates/zeta2/Cargo.toml index 61c560baab543baf9d57b034807fe60cb566b24f..d362441cdd522e012d75bec9bde6daeff50e3e89 100644 --- a/crates/zeta2/Cargo.toml +++ b/crates/zeta2/Cargo.toml @@ -14,6 +14,7 @@ path = "src/zeta2.rs" [dependencies] anyhow.workspace = true arrayvec.workspace = true +chrono.workspace = true client.workspace = true cloud_llm_client.workspace = true edit_prediction.workspace = true @@ -29,8 +30,8 @@ serde_json.workspace = true thiserror.workspace = true util.workspace = true uuid.workspace = true -workspace.workspace = true workspace-hack.workspace = true +workspace.workspace = true worktree.workspace = true [dev-dependencies] diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 240d44fae44d9a430e1ed64816e11428a5bdb3d0..4ecc0fa3acf9bc04935d3e7e443e0e610b907589 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -1,5 +1,6 @@ use anyhow::{Context as _, Result, anyhow}; use arrayvec::ArrayVec; +use chrono::TimeDelta; use client::{Client, EditPredictionUsage, UserStore}; use cloud_llm_client::predict_edits_v3::{self, Signature}; use cloud_llm_client::{ @@ -11,10 +12,11 @@ use edit_prediction_context::{ SyntaxIndexState, }; use futures::AsyncReadExt as _; +use futures::channel::mpsc; use gpui::http_client::Method; use gpui::{ - App, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task, http_client, - prelude::*, + App, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task, WeakEntity, + http_client, prelude::*, }; use language::{Anchor, Buffer, OffsetRangeExt as _, ToPoint}; use language::{BufferSnapshot, EditPreview}; @@ -28,7 +30,7 @@ use std::str::FromStr as _; use std::time::{Duration, Instant}; use std::{ops::Range, sync::Arc}; use thiserror::Error; -use util::ResultExt as _; +use util::{ResultExt as _, some_or_debug_panic}; use uuid::Uuid; use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification}; @@ -37,6 +39,12 @@ const BUFFER_CHANGE_GROUPING_INTERVAL: Duration = Duration::from_secs(1); /// Maximum number of events to track. const MAX_EVENT_COUNT: usize = 16; +pub const DEFAULT_EXCERPT_OPTIONS: EditPredictionExcerptOptions = EditPredictionExcerptOptions { + max_bytes: 512, + min_bytes: 128, + target_before_cursor_over_total_bytes: 0.5, +}; + #[derive(Clone)] struct ZetaGlobal(Entity); @@ -50,8 +58,19 @@ pub struct Zeta { projects: HashMap, pub excerpt_options: EditPredictionExcerptOptions, update_required: bool, + debug_tx: Option>>, +} + +pub struct PredictionDebugInfo { + pub context: EditPredictionContext, + pub retrieval_time: TimeDelta, + pub request: RequestDebugInfo, + pub buffer: WeakEntity, + pub position: language::Anchor, } +pub type RequestDebugInfo = predict_edits_v3::DebugInfo; + struct ZetaProject { syntax_index: Entity, events: VecDeque, @@ -94,11 +113,7 @@ impl Zeta { projects: HashMap::new(), client, user_store, - excerpt_options: EditPredictionExcerptOptions { - max_bytes: 512, - min_bytes: 128, - target_before_cursor_over_total_bytes: 0.5, - }, + excerpt_options: DEFAULT_EXCERPT_OPTIONS, llm_token: LlmApiToken::default(), _llm_token_subscription: cx.subscribe( &refresh_llm_token_listener, @@ -113,9 +128,24 @@ impl Zeta { }, ), update_required: false, + debug_tx: None, } } + pub fn debug_info(&mut self) -> mpsc::UnboundedReceiver> { + let (debug_watch_tx, debug_watch_rx) = mpsc::unbounded(); + self.debug_tx = Some(debug_watch_tx); + debug_watch_rx + } + + pub fn excerpt_options(&self) -> &EditPredictionExcerptOptions { + &self.excerpt_options + } + + pub fn set_excerpt_options(&mut self, options: EditPredictionExcerptOptions) { + self.excerpt_options = options; + } + pub fn usage(&self, cx: &App) -> Option { self.user_store.read(cx).edit_prediction_usage() } @@ -273,9 +303,11 @@ impl Zeta { .worktrees(cx) .map(|worktree| worktree.read(cx).snapshot()) .collect::>(); + let debug_tx = self.debug_tx.clone(); let request_task = cx.background_spawn({ let snapshot = snapshot.clone(); + let buffer = buffer.clone(); async move { let index_state = if let Some(index_state) = index_state { Some(index_state.lock_owned().await) @@ -285,35 +317,61 @@ impl Zeta { let cursor_point = position.to_point(&snapshot); - // TODO: make this only true if debug view is open - let debug_info = true; + let before_retrieval = chrono::Utc::now(); - let Some(request) = EditPredictionContext::gather_context( + let Some(context) = EditPredictionContext::gather_context( cursor_point, &snapshot, &excerpt_options, index_state.as_deref(), - ) - .map(|context| { - make_cloud_request( - excerpt_path.clone(), - context, - // TODO pass everything - Vec::new(), - false, - Vec::new(), - None, - debug_info, - &worktree_snapshots, - index_state.as_deref(), - ) - }) else { + ) else { return Ok(None); }; - anyhow::Ok(Some( - Self::perform_request(client, llm_token, app_version, request).await?, - )) + let debug_context = if let Some(debug_tx) = debug_tx { + Some((debug_tx, context.clone())) + } else { + None + }; + + let request = make_cloud_request( + excerpt_path.clone(), + context, + // TODO pass everything + Vec::new(), + false, + Vec::new(), + None, + debug_context.is_some(), + &worktree_snapshots, + index_state.as_deref(), + ); + + let retrieval_time = chrono::Utc::now() - before_retrieval; + let response = Self::perform_request(client, llm_token, app_version, request).await; + + if let Some((debug_tx, context)) = debug_context { + debug_tx + .unbounded_send(response.as_ref().map_err(|err| err.to_string()).and_then( + |response| { + let Some(request) = + some_or_debug_panic(response.0.debug_info.clone()) + else { + return Err("Missing debug info".to_string()); + }; + Ok(PredictionDebugInfo { + context, + request, + retrieval_time, + buffer: buffer.downgrade(), + position, + }) + }, + )) + .ok(); + } + + anyhow::Ok(Some(response?)) } }); diff --git a/crates/edit_prediction_tools/Cargo.toml b/crates/zeta2_tools/Cargo.toml similarity index 77% rename from crates/edit_prediction_tools/Cargo.toml rename to crates/zeta2_tools/Cargo.toml index ffd34abb2537006dd914d9bf9d30b735de91c5ba..fe651c14193db41f24af00d71835ffd4c6deb6eb 100644 --- a/crates/edit_prediction_tools/Cargo.toml +++ b/crates/zeta2_tools/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "edit_prediction_tools" +name = "zeta2_tools" version = "0.1.0" edition.workspace = true publish.workspace = true @@ -9,12 +9,15 @@ license = "GPL-3.0-or-later" workspace = true [lib] -path = "src/edit_prediction_tools.rs" +path = "src/zeta2_tools.rs" [dependencies] -edit_prediction_context.workspace = true +chrono.workspace = true +client.workspace = true collections.workspace = true +edit_prediction_context.workspace = true editor.workspace = true +futures.workspace = true gpui.workspace = true language.workspace = true log.workspace = true @@ -23,19 +26,20 @@ serde.workspace = true text.workspace = true ui.workspace = true ui_input.workspace = true +util.workspace = true workspace-hack.workspace = true workspace.workspace = true +zeta2.workspace = true [dev-dependencies] clap.workspace = true -futures.workspace = true gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true language = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true -project = {workspace= true, features = ["test-support"]} +project = { workspace = true, features = ["test-support"] } serde_json.workspace = true -settings = {workspace= true, features = ["test-support"]} +settings = { workspace = true, features = ["test-support"] } text = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] } zlog.workspace = true diff --git a/crates/edit_prediction_tools/LICENSE-GPL b/crates/zeta2_tools/LICENSE-GPL similarity index 100% rename from crates/edit_prediction_tools/LICENSE-GPL rename to crates/zeta2_tools/LICENSE-GPL diff --git a/crates/zeta2_tools/src/zeta2_tools.rs b/crates/zeta2_tools/src/zeta2_tools.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d743e388095f9c4658d102c69faec2eb3ad0275 --- /dev/null +++ b/crates/zeta2_tools/src/zeta2_tools.rs @@ -0,0 +1,663 @@ +use std::{ + collections::hash_map::Entry, + ffi::OsStr, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, + time::Duration, +}; + +use chrono::TimeDelta; +use client::{Client, UserStore}; +use collections::HashMap; +use editor::{Editor, EditorEvent, EditorMode, ExcerptRange, MultiBuffer}; +use futures::StreamExt as _; +use gpui::{ + Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, actions, + prelude::*, +}; +use language::{Buffer, DiskState}; +use project::{Project, WorktreeId}; +use ui::prelude::*; +use ui_input::SingleLineInput; +use util::ResultExt; +use workspace::{Item, SplitDirection, Workspace}; +use zeta2::Zeta; + +use edit_prediction_context::{EditPredictionExcerptOptions, SnippetStyle}; + +actions!( + dev, + [ + /// Opens the language server protocol logs viewer. + OpenZeta2Inspector + ] +); + +pub fn init(cx: &mut App) { + cx.observe_new(move |workspace: &mut Workspace, _, _cx| { + workspace.register_action(move |workspace, _: &OpenZeta2Inspector, window, cx| { + let project = workspace.project(); + workspace.split_item( + SplitDirection::Right, + Box::new(cx.new(|cx| { + Zeta2Inspector::new( + &project, + workspace.client(), + workspace.user_store(), + window, + cx, + ) + })), + window, + cx, + ); + }); + }) + .detach(); +} + +pub struct Zeta2Inspector { + focus_handle: FocusHandle, + project: Entity, + last_prediction: Option, + max_bytes_input: Entity, + min_bytes_input: Entity, + cursor_context_ratio_input: Entity, + active_view: ActiveView, + zeta: Entity, + _active_editor_subscription: Option, + _update_state_task: Task<()>, + _receive_task: Task<()>, +} + +#[derive(PartialEq)] +enum ActiveView { + Context, + Inference, +} + +enum LastPredictionState { + Failed(SharedString), + Success(LastPrediction), + Replaying { + prediction: LastPrediction, + _task: Task<()>, + }, +} + +struct LastPrediction { + context_editor: Entity, + retrieval_time: TimeDelta, + prompt_planning_time: TimeDelta, + inference_time: TimeDelta, + parsing_time: TimeDelta, + prompt_editor: Entity, + model_response_editor: Entity, + buffer: WeakEntity, + position: language::Anchor, +} + +impl Zeta2Inspector { + pub fn new( + project: &Entity, + client: &Arc, + user_store: &Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let zeta = Zeta::global(client, user_store, cx); + let mut request_rx = zeta.update(cx, |zeta, _cx| zeta.debug_info()); + + let receive_task = cx.spawn_in(window, async move |this, cx| { + while let Some(prediction_result) = request_rx.next().await { + this.update_in(cx, |this, window, cx| match prediction_result { + Ok(prediction) => { + this.update_last_prediction(prediction, window, cx); + } + Err(err) => { + this.last_prediction = Some(LastPredictionState::Failed(err.into())); + cx.notify(); + } + }) + .ok(); + } + }); + + let mut this = Self { + focus_handle: cx.focus_handle(), + project: project.clone(), + last_prediction: None, + active_view: ActiveView::Context, + max_bytes_input: Self::number_input("Max Bytes", window, cx), + min_bytes_input: Self::number_input("Min Bytes", window, cx), + cursor_context_ratio_input: Self::number_input("Cursor Context Ratio", window, cx), + zeta: zeta.clone(), + _active_editor_subscription: None, + _update_state_task: Task::ready(()), + _receive_task: receive_task, + }; + this.set_input_options(&zeta.read(cx).excerpt_options().clone(), window, cx); + this + } + + fn set_input_options( + &mut self, + options: &EditPredictionExcerptOptions, + window: &mut Window, + cx: &mut Context, + ) { + self.max_bytes_input.update(cx, |input, cx| { + input.set_text(options.max_bytes.to_string(), window, cx); + }); + self.min_bytes_input.update(cx, |input, cx| { + input.set_text(options.min_bytes.to_string(), window, cx); + }); + self.cursor_context_ratio_input.update(cx, |input, cx| { + input.set_text( + format!("{:.2}", options.target_before_cursor_over_total_bytes), + window, + cx, + ); + }); + cx.notify(); + } + + fn set_options(&mut self, options: EditPredictionExcerptOptions, cx: &mut Context) { + self.zeta + .update(cx, |this, _cx| this.set_excerpt_options(options)); + + const THROTTLE_TIME: Duration = Duration::from_millis(100); + + if let Some( + LastPredictionState::Success(prediction) + | LastPredictionState::Replaying { prediction, .. }, + ) = self.last_prediction.take() + { + if let Some(buffer) = prediction.buffer.upgrade() { + let position = prediction.position; + let zeta = self.zeta.clone(); + let project = self.project.clone(); + let task = cx.spawn(async move |_this, cx| { + cx.background_executor().timer(THROTTLE_TIME).await; + if let Some(task) = zeta + .update(cx, |zeta, cx| { + zeta.request_prediction(&project, &buffer, position, cx) + }) + .ok() + { + task.await.log_err(); + } + }); + self.last_prediction = Some(LastPredictionState::Replaying { + prediction, + _task: task, + }); + } else { + self.last_prediction = Some(LastPredictionState::Failed("Buffer dropped".into())); + } + } + + cx.notify(); + } + + fn number_input( + label: &'static str, + window: &mut Window, + cx: &mut Context, + ) -> Entity { + let input = cx.new(|cx| { + SingleLineInput::new(window, cx, "") + .label(label) + .label_min_width(px(64.)) + }); + + cx.subscribe_in( + &input.read(cx).editor().clone(), + window, + |this, _, event, _window, cx| { + let EditorEvent::BufferEdited = event else { + return; + }; + + fn number_input_value( + input: &Entity, + cx: &App, + ) -> T { + input + .read(cx) + .editor() + .read(cx) + .text(cx) + .parse::() + .unwrap_or_default() + } + + let options = EditPredictionExcerptOptions { + max_bytes: number_input_value(&this.max_bytes_input, cx), + min_bytes: number_input_value(&this.min_bytes_input, cx), + target_before_cursor_over_total_bytes: number_input_value( + &this.cursor_context_ratio_input, + cx, + ), + }; + + this.set_options(options, cx); + }, + ) + .detach(); + input + } + + fn update_last_prediction( + &mut self, + prediction: zeta2::PredictionDebugInfo, + window: &mut Window, + cx: &mut Context, + ) { + let Some(worktree_id) = self + .project + .read(cx) + .worktrees(cx) + .next() + .map(|worktree| worktree.read(cx).id()) + else { + log::error!("Open a worktree to use edit prediction debug view"); + self.last_prediction.take(); + return; + }; + + self._update_state_task = cx.spawn_in(window, { + let language_registry = self.project.read(cx).languages().clone(); + async move |this, cx| { + let mut languages = HashMap::default(); + for lang_id in prediction + .context + .snippets + .iter() + .map(|snippet| snippet.declaration.identifier().language_id) + .chain(prediction.context.excerpt_text.language_id) + { + if let Entry::Vacant(entry) = languages.entry(lang_id) { + // Most snippets are gonna be the same language, + // so we think it's fine to do this sequentially for now + entry.insert(language_registry.language_for_id(lang_id).await.ok()); + } + } + + let markdown_language = language_registry + .language_for_name("Markdown") + .await + .log_err(); + + this.update_in(cx, |this, window, cx| { + let context_editor = cx.new(|cx| { + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(language::Capability::ReadOnly); + let excerpt_file = Arc::new(ExcerptMetadataFile { + title: PathBuf::from("Cursor Excerpt").into(), + worktree_id, + }); + + let excerpt_buffer = cx.new(|cx| { + let mut buffer = + Buffer::local(prediction.context.excerpt_text.body, cx); + if let Some(language) = prediction + .context + .excerpt_text + .language_id + .as_ref() + .and_then(|id| languages.get(id)) + { + buffer.set_language(language.clone(), cx); + } + buffer.file_updated(excerpt_file, cx); + buffer + }); + + multibuffer.push_excerpts( + excerpt_buffer, + [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], + cx, + ); + + for snippet in &prediction.context.snippets { + let path = this + .project + .read(cx) + .path_for_entry(snippet.declaration.project_entry_id(), cx); + + let snippet_file = Arc::new(ExcerptMetadataFile { + title: PathBuf::from(format!( + "{} (Score density: {})", + path.map(|p| p.path.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()), + snippet.score_density(SnippetStyle::Declaration) + )) + .into(), + worktree_id, + }); + + let excerpt_buffer = cx.new(|cx| { + let mut buffer = + Buffer::local(snippet.declaration.item_text().0, cx); + buffer.file_updated(snippet_file, cx); + if let Some(language) = + languages.get(&snippet.declaration.identifier().language_id) + { + buffer.set_language(language.clone(), cx); + } + buffer + }); + + multibuffer.push_excerpts( + excerpt_buffer, + [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], + cx, + ); + } + + multibuffer + }); + + Editor::new(EditorMode::full(), multibuffer, None, window, cx) + }); + + let last_prediction = LastPrediction { + context_editor, + prompt_editor: cx.new(|cx| { + let buffer = cx.new(|cx| { + let mut buffer = Buffer::local(prediction.request.prompt, cx); + buffer.set_language(markdown_language.clone(), cx); + buffer + }); + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); + let mut editor = + Editor::new(EditorMode::full(), buffer, None, window, cx); + editor.set_read_only(true); + editor.set_show_line_numbers(false, cx); + editor.set_show_gutter(false, cx); + editor.set_show_scrollbars(false, cx); + editor + }), + model_response_editor: cx.new(|cx| { + let buffer = cx.new(|cx| { + let mut buffer = + Buffer::local(prediction.request.model_response, cx); + buffer.set_language(markdown_language, cx); + buffer + }); + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); + let mut editor = + Editor::new(EditorMode::full(), buffer, None, window, cx); + editor.set_read_only(true); + editor.set_show_line_numbers(false, cx); + editor.set_show_gutter(false, cx); + editor.set_show_scrollbars(false, cx); + editor + }), + retrieval_time: prediction.retrieval_time, + prompt_planning_time: prediction.request.prompt_planning_time, + inference_time: prediction.request.inference_time, + parsing_time: prediction.request.parsing_time, + buffer: prediction.buffer, + position: prediction.position, + }; + this.last_prediction = Some(LastPredictionState::Success(last_prediction)); + cx.notify(); + }) + .ok(); + } + }); + } + + fn render_duration(name: &'static str, time: chrono::TimeDelta) -> Div { + h_flex() + .gap_1() + .child(Label::new(name).color(Color::Muted).size(LabelSize::Small)) + .child( + Label::new(if time.num_microseconds().unwrap_or(0) > 1000 { + format!("{} ms", time.num_milliseconds()) + } else { + format!("{} µs", time.num_microseconds().unwrap_or(0)) + }) + .size(LabelSize::Small), + ) + } + + fn render_last_prediction(&self, prediction: &LastPrediction, cx: &mut Context) -> Div { + match &self.active_view { + ActiveView::Context => div().size_full().child(prediction.context_editor.clone()), + ActiveView::Inference => h_flex() + .items_start() + .w_full() + .flex_1() + .border_t_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().editor_background) + .child( + v_flex() + .flex_1() + .gap_2() + .p_4() + .h_full() + .child(ui::Headline::new("Prompt").size(ui::HeadlineSize::XSmall)) + .child(prediction.prompt_editor.clone()), + ) + .child(ui::vertical_divider()) + .child( + v_flex() + .flex_1() + .gap_2() + .h_full() + .p_4() + .child(ui::Headline::new("Model Response").size(ui::HeadlineSize::XSmall)) + .child(prediction.model_response_editor.clone()), + ), + } + } +} + +impl Focusable for Zeta2Inspector { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Item for Zeta2Inspector { + type Event = (); + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Zeta2 Inspector".into() + } +} + +impl EventEmitter<()> for Zeta2Inspector {} + +impl Render for Zeta2Inspector { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let content = match self.last_prediction.as_ref() { + None => v_flex() + .size_full() + .justify_center() + .items_center() + .child(Label::new("No prediction").size(LabelSize::Large)) + .into_any(), + Some(LastPredictionState::Success(prediction)) => { + self.render_last_prediction(prediction, cx).into_any() + } + Some(LastPredictionState::Replaying { prediction, _task }) => self + .render_last_prediction(prediction, cx) + .opacity(0.6) + .into_any(), + Some(LastPredictionState::Failed(err)) => v_flex() + .p_4() + .gap_2() + .child(Label::new(err.clone()).buffer_font(cx)) + .into_any(), + }; + + v_flex() + .size_full() + .bg(cx.theme().colors().editor_background) + .child( + h_flex() + .w_full() + .child( + v_flex() + .flex_1() + .p_4() + .h_full() + .justify_between() + .child( + v_flex() + .gap_2() + .child( + Headline::new("Excerpt Options").size(HeadlineSize::Small), + ) + .child( + h_flex() + .gap_2() + .items_end() + .child(self.max_bytes_input.clone()) + .child(self.min_bytes_input.clone()) + .child(self.cursor_context_ratio_input.clone()) + .child( + ui::Button::new("reset-options", "Reset") + .disabled( + self.zeta.read(cx).excerpt_options() + == &zeta2::DEFAULT_EXCERPT_OPTIONS, + ) + .style(ButtonStyle::Outlined) + .size(ButtonSize::Large) + .on_click(cx.listener( + |this, _, window, cx| { + this.set_input_options( + &zeta2::DEFAULT_EXCERPT_OPTIONS, + window, + cx, + ); + }, + )), + ), + ), + ) + .map(|this| { + if let Some( + LastPredictionState::Success { .. } + | LastPredictionState::Replaying { .. }, + ) = self.last_prediction.as_ref() + { + this.child( + ui::ToggleButtonGroup::single_row( + "prediction", + [ + ui::ToggleButtonSimple::new( + "Context", + cx.listener(|this, _, _, cx| { + this.active_view = ActiveView::Context; + cx.notify(); + }), + ), + ui::ToggleButtonSimple::new( + "Inference", + cx.listener(|this, _, _, cx| { + this.active_view = ActiveView::Inference; + cx.notify(); + }), + ), + ], + ) + .style(ui::ToggleButtonGroupStyle::Outlined) + .selected_index( + if self.active_view == ActiveView::Context { + 0 + } else { + 1 + }, + ), + ) + } else { + this + } + }), + ) + .child(ui::vertical_divider()) + .map(|this| { + if let Some( + LastPredictionState::Success(prediction) + | LastPredictionState::Replaying { prediction, .. }, + ) = self.last_prediction.as_ref() + { + this.child( + v_flex() + .p_4() + .gap_2() + .min_w(px(160.)) + .child(Headline::new("Stats").size(HeadlineSize::Small)) + .child(Self::render_duration( + "Context retrieval", + prediction.retrieval_time, + )) + .child(Self::render_duration( + "Prompt planning", + prediction.prompt_planning_time, + )) + .child(Self::render_duration( + "Inference", + prediction.inference_time, + )) + .child(Self::render_duration( + "Parsing", + prediction.parsing_time, + )), + ) + } else { + this + } + }), + ) + .child(content) + } +} + +// Using same approach as commit view + +struct ExcerptMetadataFile { + title: Arc, + worktree_id: WorktreeId, +} + +impl language::File for ExcerptMetadataFile { + fn as_local(&self) -> Option<&dyn language::LocalFile> { + None + } + + fn disk_state(&self) -> DiskState { + DiskState::New + } + + fn path(&self) -> &Arc { + &self.title + } + + fn full_path(&self, _: &App) -> PathBuf { + self.title.as_ref().into() + } + + fn file_name<'a>(&'a self, _: &'a App) -> &'a OsStr { + self.title.file_name().unwrap() + } + + fn worktree_id(&self, _: &App) -> WorktreeId { + self.worktree_id + } + + fn to_proto(&self, _: &App) -> language::proto::File { + unimplemented!() + } + + fn is_private(&self) -> bool { + false + } +} From 809d3bfe0079716713ce7100138b721f75f42105 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Tue, 23 Sep 2025 12:33:31 -0300 Subject: [PATCH 212/721] acp: Include only path to @mentioned directory in user message (#37942) Nowadays, people don't expect @-mentioning a directory to include the contents of all files within it. Doing so makes it very likely to consume an undesirable amount of tokens. By default, we'll now only include the path of the directory and let the model decide how much to read via tools. We'll still include the contents if no tools are available (e.g. "Minimal" profile is selected). Release Notes: - Agent Panel: Do not include the content of @-mentioned directories when tools are available --- crates/agent_ui/src/acp/message_editor.rs | 277 ++++++++++++---------- crates/agent_ui/src/acp/thread_view.rs | 30 ++- 2 files changed, 170 insertions(+), 137 deletions(-) diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 2734726ddbe1608356bcc34af5c42f479d5a8e8a..1be8cb53872582cc26af65c87189195fa32b643a 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -364,7 +364,7 @@ impl MessageEditor { let task = match mention_uri.clone() { MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, cx), - MentionUri::Directory { abs_path } => self.confirm_mention_for_directory(abs_path, cx), + MentionUri::Directory { .. } => Task::ready(Ok(Mention::UriOnly)), MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx), MentionUri::TextThread { path, .. } => self.confirm_mention_for_text_thread(path, cx), MentionUri::File { abs_path } => self.confirm_mention_for_file(abs_path, cx), @@ -468,97 +468,6 @@ impl MessageEditor { }) } - fn confirm_mention_for_directory( - &mut self, - abs_path: PathBuf, - cx: &mut Context, - ) -> Task> { - fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc, PathBuf)> { - let mut files = Vec::new(); - - 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 Some(project_path) = self - .project - .read(cx) - .project_path_for_absolute_path(&abs_path, cx) - else { - return Task::ready(Err(anyhow!("project path not found"))); - }; - let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else { - return Task::ready(Err(anyhow!("project entry not found"))); - }; - let directory_path = entry.path.clone(); - let worktree_id = project_path.worktree_id; - let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) else { - return Task::ready(Err(anyhow!("worktree not found"))); - }; - let project = self.project.clone(); - cx.spawn(async move |_, cx| { - 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) - }) - }); - - cx.spawn(async move |cx| { - let buffer = open_task.await.log_err()?; - let buffer_content = outline::get_buffer_content_or_outline( - buffer.clone(), - Some(&full_path), - &cx, - ) - .await - .ok()?; - - Some((rel_path, full_path, buffer_content.text, buffer)) - }) - })) - })?; - - let contents = cx - .background_spawn(async move { - let (contents, tracked_buffers) = descendants_future - .await - .into_iter() - .flatten() - .map(|(rel_path, full_path, rope, buffer)| { - ((rel_path, full_path, rope), buffer) - }) - .unzip(); - Mention::Text { - content: render_directory_contents(contents), - tracked_buffers, - } - }) - .await; - anyhow::Ok(contents) - }) - } - fn confirm_mention_for_fetch( &mut self, url: url::Url, @@ -776,6 +685,7 @@ impl MessageEditor { pub fn contents( &self, + full_mention_content: bool, cx: &mut Context, ) -> Task, Vec>)>> { // Check for unsupported slash commands before spawning async task @@ -787,9 +697,12 @@ impl MessageEditor { return Task::ready(Err(err)); } - let contents = self - .mention_set - .contents(&self.prompt_capabilities.borrow(), cx); + let contents = self.mention_set.contents( + &self.prompt_capabilities.borrow(), + full_mention_content, + self.project.clone(), + cx, + ); let editor = self.editor.clone(); cx.spawn(async move |_, cx| { @@ -1263,6 +1176,96 @@ impl MessageEditor { } } +fn full_mention_for_directory( + project: &Entity, + abs_path: &Path, + cx: &mut App, +) -> Task> { + fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc, PathBuf)> { + let mut files = Vec::new(); + + 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 Some(project_path) = project + .read(cx) + .project_path_for_absolute_path(&abs_path, cx) + else { + return Task::ready(Err(anyhow!("project path not found"))); + }; + let Some(entry) = project.read(cx).entry_for_path(&project_path, cx) else { + return Task::ready(Err(anyhow!("project entry not found"))); + }; + let directory_path = entry.path.clone(); + let worktree_id = project_path.worktree_id; + let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else { + return Task::ready(Err(anyhow!("worktree not found"))); + }; + let project = project.clone(); + cx.spawn(async move |cx| { + 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) + }) + }); + + cx.spawn(async move |cx| { + let buffer = open_task.await.log_err()?; + let buffer_content = outline::get_buffer_content_or_outline( + buffer.clone(), + Some(&full_path), + &cx, + ) + .await + .ok()?; + + Some((rel_path, full_path, buffer_content.text, buffer)) + }) + })) + })?; + + let contents = cx + .background_spawn(async move { + let (contents, tracked_buffers) = descendants_future + .await + .into_iter() + .flatten() + .map(|(rel_path, full_path, rope, buffer)| { + ((rel_path, full_path, rope), buffer) + }) + .unzip(); + Mention::Text { + content: render_directory_contents(contents), + tracked_buffers, + } + }) + .await; + anyhow::Ok(contents) + }) +} + fn render_directory_contents(entries: Vec<(Arc, PathBuf, String)>) -> String { let mut output = String::new(); for (_relative_path, full_path, content) in entries { @@ -1514,6 +1517,8 @@ impl MentionSet { fn contents( &self, prompt_capabilities: &acp::PromptCapabilities, + full_mention_content: bool, + project: Entity, cx: &mut App, ) -> Task>> { if !prompt_capabilities.embedded_context { @@ -1527,13 +1532,19 @@ impl MentionSet { } let mentions = self.mentions.clone(); - cx.spawn(async move |_cx| { + cx.spawn(async move |cx| { let mut contents = HashMap::default(); for (crease_id, (mention_uri, task)) in mentions { - contents.insert( - crease_id, - (mention_uri, task.await.map_err(|e| anyhow!("{e}"))?), - ); + let content = if full_mention_content + && let MentionUri::Directory { abs_path } = &mention_uri + { + cx.update(|cx| full_mention_for_directory(&project, abs_path, cx))? + .await? + } else { + task.await.map_err(|e| anyhow!("{e}"))? + }; + + contents.insert(crease_id, (mention_uri, content)); } Ok(contents) }) @@ -1694,7 +1705,7 @@ mod tests { }); let (content, _) = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await .unwrap(); @@ -1757,7 +1768,7 @@ mod tests { }); let contents_result = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await; // Should fail because available_commands is empty (no commands supported) @@ -1780,7 +1791,7 @@ mod tests { }); let contents_result = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await; assert!(contents_result.is_err()); @@ -1795,7 +1806,7 @@ mod tests { }); let contents_result = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await; // Should succeed because /help is in available_commands @@ -1807,7 +1818,7 @@ mod tests { }); let (content, _) = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await .unwrap(); @@ -1825,7 +1836,7 @@ mod tests { // The @ mention functionality should not be affected let (content, _) = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await .unwrap(); @@ -2271,9 +2282,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2290,9 +2304,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&acp::PromptCapabilities::default(), cx) + message_editor.mention_set().contents( + &acp::PromptCapabilities::default(), + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2341,9 +2358,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2451,9 +2471,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2501,9 +2524,12 @@ mod tests { // Getting the message contents fails message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .expect_err("Should fail to load x.png"); @@ -2548,9 +2574,12 @@ mod tests { // Now getting the contents succeeds, because the invalid mention was removed let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap(); diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 391486a68eca87e238f9efb88288bc970e3eb412..cf6f563ecdeb5a60e2b3faa4a5ee36d3282e1bdb 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1038,10 +1038,7 @@ impl AcpThreadView { return; } - let contents = self - .message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)); - self.send_impl(contents, window, cx) + self.send_impl(self.message_editor.clone(), window, cx) } fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context) { @@ -1051,15 +1048,11 @@ impl AcpThreadView { let cancelled = thread.update(cx, |thread, cx| thread.cancel(cx)); - let contents = self - .message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)); - cx.spawn_in(window, async move |this, cx| { cancelled.await; this.update_in(cx, |this, window, cx| { - this.send_impl(contents, window, cx); + this.send_impl(this.message_editor.clone(), window, cx); }) .ok(); }) @@ -1068,10 +1061,23 @@ impl AcpThreadView { fn send_impl( &mut self, - contents: Task, Vec>)>>, + message_editor: Entity, window: &mut Window, cx: &mut Context, ) { + let full_mention_content = self.as_native_thread(cx).is_some_and(|thread| { + // Include full contents when using minimal profile + let thread = thread.read(cx); + AgentSettings::get_global(cx) + .profiles + .get(thread.profile()) + .is_some_and(|profile| profile.tools.is_empty()) + }); + + let contents = message_editor.update(cx, |message_editor, cx| { + message_editor.contents(full_mention_content, cx) + }); + let agent_telemetry_id = self.agent.telemetry_id(); self.thread_error.take(); @@ -1200,10 +1206,8 @@ impl AcpThreadView { thread .update(cx, |thread, cx| thread.rewind(user_message_id, cx))? .await?; - let contents = - message_editor.update(cx, |message_editor, cx| message_editor.contents(cx))?; this.update_in(cx, |this, window, cx| { - this.send_impl(contents, window, cx); + this.send_impl(message_editor, window, cx); })?; anyhow::Ok(()) }) From 2759f541da451bd297ead1c1719126a807d336d1 Mon Sep 17 00:00:00 2001 From: Dino Date: Tue, 23 Sep 2025 16:39:12 +0100 Subject: [PATCH 213/721] vim: Fix cursor position being set to end of line in normal mode (#38161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address an issue where, in Vim mode, clicking past the end of a line after selecting the entire line would place the cursor on the newline character instead of the last character of the line, which is inconsistent with Vim's normal mode expectations. I believe the root cause was that the cursor’s position was updated to the end of the line before the mode switch from Visual to Normal, at which point `DisplayMap.clip_at_line_ends` was still set to `false`. As a result, the cursor could end up in an invalid position for Normal mode. The fix ensures that when switching between these two modes, and if the selection is empty, the selection point is properly clipped, preventing the cursor from being placed past the end of the line. Related #38049 Release Notes: - Fixed issue in Vim mode where switching from any mode to normal mode could end up with the cursor in the newline character --------- Co-authored-by: Conrad Irwin --- crates/editor/src/editor.rs | 4 +++ crates/vim/src/test.rs | 49 +++++++++++++++++++++++++++++++++++++ crates/vim/src/vim.rs | 4 ++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1d6ac4bcca2c1dfb9e34b461965adc97e4a6b6eb..eec6e367dc597eb61a37ad33e116fd6688ba7c66 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2518,6 +2518,10 @@ impl Editor { key_context } + pub fn last_bounds(&self) -> Option<&Bounds> { + self.last_bounds.as_ref() + } + fn show_mouse_cursor(&mut self, cx: &mut Context) { if self.mouse_cursor_hidden { self.mouse_cursor_hidden = false; diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 03adfc8af15cf92f7ee6c4c857c0f154e2c969f3..867b0829dde3eef418049f2bb4eec2985fabcfad 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -2213,3 +2213,52 @@ async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) { Mode::Normal, ); } + +#[gpui::test] +async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! { + " + ˇverylongline + andsomelinebelow + " + }, + Mode::Normal, + ); + + cx.simulate_keystrokes("v e"); + cx.assert_state( + indoc! { + " + «verylonglineˇ» + andsomelinebelow + " + }, + Mode::Visual, + ); + + let mut pixel_position = cx.update_editor(|editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let current_head = editor.selections.newest_display(cx).end; + editor.last_bounds().unwrap().origin + + editor + .display_to_pixel_point(current_head, &snapshot, window) + .unwrap() + }); + pixel_position.x += px(100.); + // click beyond end of the line + cx.simulate_click(pixel_position, Modifiers::default()); + cx.run_until_parked(); + + cx.assert_state( + indoc! { + " + verylonglinˇe + andsomelinebelow + " + }, + Mode::Normal, + ); +} diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index c7fb8ffa35ea090296f137b11f08379db968ce3d..1c2ea0dccbc6e84d7a871f7a4d30fb6c6949044e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -1092,6 +1092,8 @@ impl Vim { let mut point = selection.head(); if !selection.reversed && !selection.is_empty() { point = movement::left(map, selection.head()); + } else if selection.is_empty() { + point = map.clip_point(point, Bias::Left); } selection.collapse_to(point, selection.goal) } else if !last_mode.is_visual() && mode.is_visual() && selection.is_empty() { @@ -1608,7 +1610,7 @@ impl Vim { && !is_multicursor && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode) { - self.switch_mode(Mode::Normal, true, window, cx); + self.switch_mode(Mode::Normal, false, window, cx); } } From 3cf6fa8f61a5b99bc02276564658534e57e67113 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:26:45 -0300 Subject: [PATCH 214/721] agent: Make the panel's textarea font size be controlled by `buffer_font_size` (#38726) Closes https://github.com/zed-industries/zed/issues/37882 Previously, every piece of text in the agent panel was controlled by `agent_font_size`. Although it is nice to only have one setting to tweak that, it could be a bit misleading particularly because we use monospaced and sans-serif fonts for different elements in the panel. Any editor/textarea in the panel, whehter it is the main message editor or the previous message editor, uses the buffer font. Therefore, I think it is reasonable to expect that tweaking `buffer_font_size` would also change the agent panel's usage of buffer fonts. With this change, regular buffers and the agent panel's message editor will always have the same size. Release Notes: - agent: Made the agent panel's textarea font size follow the font size of regular buffers. They're now both controlled by the `buffer_font_size` setting. --- crates/agent_ui/src/acp/message_editor.rs | 15 +++------------ docs/src/ai/agent-settings.md | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 1be8cb53872582cc26af65c87189195fa32b643a..c369d36d78eb179ec109c90365237d39159735fa 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -47,12 +47,7 @@ use std::{ }; use text::OffsetRangeExt; use theme::ThemeSettings; -use ui::{ - ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, ButtonStyle, Color, Element as _, - FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, - LabelCommon, LabelSize, ParentElement, Render, SelectableButton, Styled, TextSize, TintColor, - Toggleable, Window, div, h_flex, -}; +use ui::{ButtonLike, TintColor, Toggleable, prelude::*}; use util::{ResultExt, debug_panic}; use workspace::{Workspace, notifications::NotifyResultExt as _}; use zed_actions::agent::Chat; @@ -1291,18 +1286,14 @@ impl Render for MessageEditor { .flex_1() .child({ let settings = ThemeSettings::get_global(cx); - let font_size = TextSize::Small - .rems(cx) - .to_pixels(settings.agent_font_size(cx)); - let line_height = settings.buffer_line_height.value() * font_size; let text_style = TextStyle { color: cx.theme().colors().text, font_family: settings.buffer_font.family.clone(), font_fallbacks: settings.buffer_font.fallbacks.clone(), font_features: settings.buffer_font.features.clone(), - font_size: font_size.into(), - line_height: line_height.into(), + font_size: settings.buffer_font_size(cx).into(), + line_height: relative(settings.buffer_line_height.value()), ..Default::default() }; diff --git a/docs/src/ai/agent-settings.md b/docs/src/ai/agent-settings.md index d13e7733878339165293a250d1d5f253ef799e5d..4d12a87f95f6fd34a6dcfdd822b6e1f71dfeefa2 100644 --- a/docs/src/ai/agent-settings.md +++ b/docs/src/ai/agent-settings.md @@ -123,6 +123,20 @@ You can choose between `thread` (the default) and `text_thread`: } ``` +### Font Size + +Use the `agent_font_size` setting to change the font size of rendered agent responses in the panel. + +```json +{ + "agent": { + "agent_font_size": 18 + } +} +``` + +> (Preview Version Note): Editors in the Agent Panel—whether that is the main message textarea or previous messages—use monospace fonts and therefore, are controlled by the `buffer_font_size` setting, which is defined globally in your `settings.json`. + ### Auto-run Commands Control whether you want to allow the agent to run commands without asking you for permission. From f6e2a2a80801b222584ebbaae4bb16c22b926152 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:31:56 -0300 Subject: [PATCH 215/721] docs: Tweak the toolchains page (#38728) Mostly just breaking a massive wall of text in small paragraphs for ease of reading/parsing. Release Notes: - N/A --- docs/src/toolchains.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/src/toolchains.md b/docs/src/toolchains.md index 213042d5e4690bf3313b617839d9290595a4304f..68e7baa8cf225d85862eadb0ef02674f84b59fd2 100644 --- a/docs/src/toolchains.md +++ b/docs/src/toolchains.md @@ -1,27 +1,30 @@ # Toolchains Zed projects offer a dedicated UI for toolchain selection, which lets you pick a set of tools for working with a given language in a current project. + Imagine you're working with Python project, which has virtual environments that encapsulate a set of dependencies of your project along with a suitable interpreter to run it with. The language server has to know which virtual environment you're working with, as it uses it to understand your project's code. -With toolchain selector, you don't need to spend time configuring your language server to point it at the right virtual environment directory - you can just select the right virtual environment (toolchain) from a dropdown. +With toolchain selector, you don't need to spend time configuring your language server to point it at the right virtual environment directory—you can just select the right virtual environment (toolchain) from a dropdown. + You can even select different toolchains for different subprojects within your Zed project. A definition of a subproject is language-specific. In collaborative scenarios, only the project owner can see and modify an active toolchain. -In [remote projects](./remote-development.md)., you can use the toolchain selector to control the active toolchain on the SSH host. When [sharing your project](./collaboration.md), the toolchain selector is not available to guests. + +In [remote projects](./remote-development.md), you can use the toolchain selector to control the active toolchain on the SSH host. When [sharing your project](./collaboration.md), the toolchain selector is not available to guests. ## Why do we need toolchains? -The active toolchain is relevant for launching language servers, which may need it to function properly - it may not be able to resolve dependencies, which in turn may make functionalities like "Go to definition" or "Code completions" unavailable. +The active toolchain is relevant for launching language servers, which may need it to function properly—it may not be able to resolve dependencies, which in turn may make functionalities like "Go to definition" or "Code completions" unavailable. The active toolchain is also relevant when launching a shell in the terminal panel: some toolchains provide "activation scripts" for shells, which make those toolchains available in the shell environment for your convenience. Zed will run these activation scripts automatically when you create a new terminal. -This also applies to [tasks](./tasks.md) - Zed tasks behave "as if" you opened a new terminal tab and ran a given task invocation yourself, which in turn means that Zed task execution is affected by the active toolchain and its activation script. +This also applies to [tasks](./tasks.md)—Zed tasks behave "as if" you opened a new terminal tab and ran a given task invocation yourself, which in turn means that Zed task execution is affected by the active toolchain and its activation script. ## Selecting toolchains -The active toolchain (if there is one) is displayed in the status bar (on the right hand side). Click on it to access the toolchain selector - you can also use an action from a command palette ({#action toolchain::Select}). +The active toolchain (if there is one) is displayed in the status bar (on the right hand side). Click on it to access the toolchain selector—you can also use an action from a command palette ({#action toolchain::Select}). Zed will automatically infer a set of toolchains to choose from based on the project you're working with. A default will also be selected on your behalf on a best-effort basis when you open a project for the first time. -The toolchain selection applies to a current subproject, which - depending on the structure of your Zed project - might be your whole project or just a subset of it. For example, if you have a monorepo with multiple subprojects, you might want to select a different toolchain for each subproject. +The toolchain selection applies to a current subproject, which—depending on the structure of your Zed project—might be your whole project or just a subset of it. For example, if you have a monorepo with multiple subprojects, you might want to select a different toolchain for each subproject. ## Adding toolchains manually From 8fefd793f0c0704017a20209b7a2b216de142a65 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 23 Sep 2025 18:45:37 +0200 Subject: [PATCH 216/721] zeta2: Include edit events in cloud request (#38724) Release Notes: - N/A Co-authored-by: Agus Zubiaga Co-authored-by: Michael Sloan --- crates/zeta2/src/zeta2.rs | 42 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 4ecc0fa3acf9bc04935d3e7e443e0e610b907589..159173493668754eb87f159699694b43c76798c4 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -305,6 +305,44 @@ impl Zeta { .collect::>(); let debug_tx = self.debug_tx.clone(); + let events = project_state + .map(|state| { + state + .events + .iter() + .map(|event| match event { + Event::BufferChange { + old_snapshot, + new_snapshot, + .. + } => { + let path = new_snapshot.file().map(|f| f.path().to_path_buf()); + + let old_path = old_snapshot.file().and_then(|f| { + let old_path = f.path().as_ref(); + if Some(old_path) != path.as_deref() { + Some(old_path.to_path_buf()) + } else { + None + } + }); + + predict_edits_v3::Event::BufferChange { + old_path, + path, + diff: language::unified_diff( + &old_snapshot.text(), + &new_snapshot.text(), + ), + //todo: Actually detect if this edit was predicted or not + predicted: false, + } + } + }) + .collect::>() + }) + .unwrap_or_default(); + let request_task = cx.background_spawn({ let snapshot = snapshot.clone(); let buffer = buffer.clone(); @@ -337,8 +375,8 @@ impl Zeta { let request = make_cloud_request( excerpt_path.clone(), context, - // TODO pass everything - Vec::new(), + events, + // TODO data collection false, Vec::new(), None, From 6a24ad7d39c4f738cdfac22fd97bcc358f82495e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 23 Sep 2025 19:49:45 +0300 Subject: [PATCH 217/721] Fix the markdown table (#38729) Closes https://github.com/zed-industries/zed/issues/38597 Release Notes: - N/A --- legal/subprocessors.md | 1 + 1 file changed, 1 insertion(+) diff --git a/legal/subprocessors.md b/legal/subprocessors.md index 1d655861e39ef3005f257a2048bf70cc20ca12d1..df3a5f7c9fd1ff5d3fb309a58d58700f8a08681a 100644 --- a/legal/subprocessors.md +++ b/legal/subprocessors.md @@ -22,4 +22,5 @@ This page provides information about the Subprocessors Zed has engaged to provid | Exa Labs | AI Services | United States | | Google | AI Services | United States | | OpenAI | AI Services | United States | + **DATE: May 6th, 2025** From dc20a41e0d73a5f8e9ce5ec42a79499aa229027c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:58:46 +0200 Subject: [PATCH 218/721] windows: Encrypt SSH passwords stored in memory (#38427) Release Notes: - N/A --------- Co-authored-by: Julia --- Cargo.lock | 7 +- Cargo.toml | 2 + crates/askpass/Cargo.toml | 6 +- crates/askpass/src/askpass.rs | 33 ++++-- crates/askpass/src/encrypted_password.rs | 111 ++++++++++++++++++ crates/git_ui/Cargo.toml | 1 + crates/git_ui/src/askpass_modal.rs | 25 +++- crates/project/Cargo.toml | 1 + crates/project/src/git_store.rs | 10 +- crates/recent_projects/Cargo.toml | 1 + .../recent_projects/src/remote_connections.rs | 39 ++++-- crates/remote/Cargo.toml | 1 + crates/remote/src/remote_client.rs | 11 +- crates/remote/src/transport/ssh.rs | 21 +++- 14 files changed, 232 insertions(+), 37 deletions(-) create mode 100644 crates/askpass/src/encrypted_password.rs diff --git a/Cargo.lock b/Cargo.lock index c57d06ef884b78d22042324d02a78d23ec485608..898533b5a531fb92e63a709cd30b022779f5c03a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,11 +813,13 @@ dependencies = [ "futures 0.3.31", "gpui", "net", - "parking_lot", + "proto", "smol", "tempfile", "util", + "windows 0.61.1", "workspace-hack", + "zeroize", ] [[package]] @@ -6946,6 +6948,7 @@ dependencies = [ "workspace", "workspace-hack", "zed_actions", + "zeroize", "zlog", ] @@ -13141,6 +13144,7 @@ dependencies = [ "which 6.0.3", "workspace-hack", "worktree", + "zeroize", "zlog", ] @@ -13839,6 +13843,7 @@ name = "recent_projects" version = "0.1.0" dependencies = [ "anyhow", + "askpass", "auto_update", "dap", "editor", diff --git a/Cargo.toml b/Cargo.toml index 8aabe6ad40c9d6b854d3453b08972dd8a4364e09..f49a0374bbe2f4be99d52c755b3438f7f6e31217 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -717,6 +717,7 @@ windows-core = "0.61" wit-component = "0.221" workspace-hack = "0.1.0" yawc = "0.2.5" +zeroize = "1.8" zstd = "0.11" [workspace.dependencies.windows] @@ -743,6 +744,7 @@ features = [ "Win32_Networking_WinSock", "Win32_Security", "Win32_Security_Credentials", + "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Com_StructuredStorage", diff --git a/crates/askpass/Cargo.toml b/crates/askpass/Cargo.toml index 0527399af8b6f45ef18650ee5c286c0b51a83608..297139545caeb1c23386cb9f5d8f97b84791dc10 100644 --- a/crates/askpass/Cargo.toml +++ b/crates/askpass/Cargo.toml @@ -16,8 +16,12 @@ anyhow.workspace = true futures.workspace = true gpui.workspace = true net.workspace = true -parking_lot.workspace = true +proto.workspace = true smol.workspace = true tempfile.workspace = true util.workspace = true workspace-hack.workspace = true +zeroize.workspace = true + +[target.'cfg(target_os = "windows")'.dependencies] +windows.workspace = true diff --git a/crates/askpass/src/askpass.rs b/crates/askpass/src/askpass.rs index 9e84a9fed03c8a620c7cb33cc76ef22c000c3fa6..6405be5966ba512d68a9ab638d1216d678200968 100644 --- a/crates/askpass/src/askpass.rs +++ b/crates/askpass/src/askpass.rs @@ -1,3 +1,9 @@ +mod encrypted_password; + +pub use encrypted_password::{EncryptedPassword, ProcessExt}; + +#[cfg(target_os = "windows")] +use std::sync::OnceLock; use std::{ffi::OsStr, time::Duration}; use anyhow::{Context as _, Result}; @@ -10,6 +16,8 @@ use gpui::{AsyncApp, BackgroundExecutor, Task}; use smol::fs; use util::ResultExt as _; +use crate::encrypted_password::decrypt; + #[derive(PartialEq, Eq)] pub enum AskPassResult { CancelledByUser, @@ -17,16 +25,19 @@ pub enum AskPassResult { } pub struct AskPassDelegate { - tx: mpsc::UnboundedSender<(String, oneshot::Sender)>, + tx: mpsc::UnboundedSender<(String, oneshot::Sender)>, _task: Task<()>, } impl AskPassDelegate { pub fn new( cx: &mut AsyncApp, - password_prompt: impl Fn(String, oneshot::Sender, &mut AsyncApp) + Send + Sync + 'static, + password_prompt: impl Fn(String, oneshot::Sender, &mut AsyncApp) + + Send + + Sync + + 'static, ) -> Self { - let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender)>(); + let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender<_>)>(); let task = cx.spawn(async move |cx: &mut AsyncApp| { while let Some((prompt, channel)) = rx.next().await { password_prompt(prompt, channel, cx); @@ -35,7 +46,7 @@ impl AskPassDelegate { Self { tx, _task: task } } - pub async fn ask_password(&mut self, prompt: String) -> Result { + pub async fn ask_password(&mut self, prompt: String) -> Result { let (tx, rx) = oneshot::channel(); self.tx.send((prompt, tx)).await?; Ok(rx.await?) @@ -48,7 +59,7 @@ pub struct AskPassSession { #[cfg(target_os = "windows")] askpass_helper: String, #[cfg(target_os = "windows")] - secret: std::sync::Arc>, + secret: std::sync::Arc>, _askpass_task: Task<()>, askpass_opened_rx: Option>, askpass_kill_master_rx: Option>, @@ -68,7 +79,7 @@ impl AskPassSession { use util::fs::make_file_executable; #[cfg(target_os = "windows")] - let secret = std::sync::Arc::new(parking_lot::Mutex::new(String::new())); + let secret = std::sync::Arc::new(OnceLock::new()); let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?; let askpass_socket = temp_dir.path().join("askpass.sock"); let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME); @@ -104,10 +115,12 @@ impl AskPassSession { .context("getting askpass password") .log_err() { - stream.write_all(password.as_bytes()).await.log_err(); #[cfg(target_os = "windows")] { - *askpass_secret.lock() = password; + askpass_secret.get_or_init(|| password.clone()); + } + if let Ok(decrypted) = decrypt(password) { + stream.write_all(decrypted.as_bytes()).await.log_err(); } } else { if let Some(kill_tx) = kill_tx.take() { @@ -188,8 +201,8 @@ impl AskPassSession { /// This will return the password that was last set by the askpass script. #[cfg(target_os = "windows")] - pub fn get_password(&self) -> String { - self.secret.lock().clone() + pub fn get_password(&self) -> Option { + self.secret.get().cloned() } } diff --git a/crates/askpass/src/encrypted_password.rs b/crates/askpass/src/encrypted_password.rs new file mode 100644 index 0000000000000000000000000000000000000000..f027faa2cda163e0aa5344623991e7082a0983ec --- /dev/null +++ b/crates/askpass/src/encrypted_password.rs @@ -0,0 +1,111 @@ +//! This module provides [EncryptedPassword] for storage of passwords in memory. +//! On Windows that's implemented with CryptProtectMemory/CryptUnprotectMemory; on other platforms it just falls through +//! to string for now. +//! +//! The "safety" of this module lies in exploiting visibility rules of Rust: +//! 1. No outside module has access to the internal representation of [EncryptedPassword]. +//! 2. [EncryptedPassword] cannot be converted into a [String] or any other plaintext representation. +//! All use cases that do need such functionality (of which we have two right now) are implemented within this module. +//! +//! Note that this is not bulletproof. +//! 1. [ProcessExt] is implemented for [smol::process::Command], which is a builder for smol processes. +//! Before the process itself is spawned the contents of [EncryptedPassword] are unencrypted in env var storage of said builder. +//! 2. We're also sending plaintext passwords over RPC with [proto::AskPassResponse]. Go figure how great that is. +//! +//! Still, the goal of this module is to not have passwords laying around nilly-willy in memory. +//! We do not claim that it is fool-proof. +use anyhow::Result; +use zeroize::Zeroize; + +type LengthWithoutPadding = u32; +#[derive(Clone)] +pub struct EncryptedPassword(Vec, LengthWithoutPadding); + +pub trait ProcessExt { + fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self; +} + +impl ProcessExt for smol::process::Command { + fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self { + if let Ok(password) = decrypt(value) { + self.env(name, password); + } + self + } +} + +impl TryFrom for proto::AskPassResponse { + type Error = anyhow::Error; + fn try_from(pw: EncryptedPassword) -> Result { + let pw = decrypt(pw)?; + Ok(Self { response: pw }) + } +} + +impl Drop for EncryptedPassword { + fn drop(&mut self) { + self.0.zeroize(); + self.1.zeroize(); + } +} + +impl TryFrom<&str> for EncryptedPassword { + type Error = anyhow::Error; + fn try_from(password: &str) -> Result { + let len: u32 = password.len().try_into()?; + #[cfg(windows)] + { + use windows::Win32::Security::Cryptography::{ + CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptProtectMemory, + }; + let mut value = password.bytes().collect::>(); + let padded_length = len.next_multiple_of(CRYPTPROTECTMEMORY_BLOCK_SIZE); + if padded_length != len { + value.resize(padded_length as usize, 0); + } + unsafe { + CryptProtectMemory( + value.as_mut_ptr() as _, + len, + CRYPTPROTECTMEMORY_SAME_PROCESS, + )?; + } + Ok(Self(value, len)) + } + #[cfg(not(windows))] + Ok(Self(String::from(password).into(), len)) + } +} + +pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result { + #[cfg(windows)] + { + use anyhow::Context; + use windows::Win32::Security::Cryptography::{ + CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptUnprotectMemory, + }; + assert_eq!( + password.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize, + 0, + "Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.", + password.0.len(), + CRYPTPROTECTMEMORY_BLOCK_SIZE + ); + unsafe { + CryptUnprotectMemory( + password.0.as_mut_ptr() as _, + password.1, + CRYPTPROTECTMEMORY_SAME_PROCESS, + ) + .context("while decrypting a SSH password")? + }; + + { + // Remove padding + _ = password.0.drain(password.1 as usize..); + } + Ok(String::from_utf8(std::mem::take(&mut password.0))?) + } + #[cfg(not(windows))] + Ok(String::from_utf8(std::mem::take(&mut password.0))?) +} diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index 52577d033a94fb819b320d3b18fd384431fe5a1f..6905b3eb890b5ac96d9ccd75a08dd88f0288787b 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -61,6 +61,7 @@ watch.workspace = true workspace-hack.workspace = true workspace.workspace = true zed_actions.workspace = true +zeroize.workspace = true [target.'cfg(windows)'.dependencies] windows.workspace = true diff --git a/crates/git_ui/src/askpass_modal.rs b/crates/git_ui/src/askpass_modal.rs index 1705ad6732ef57095a7e550a6c27978596a6b11e..bbd507cfc4cce55f02e3c77a4317a6ca69049987 100644 --- a/crates/git_ui/src/askpass_modal.rs +++ b/crates/git_ui/src/askpass_modal.rs @@ -1,3 +1,4 @@ +use askpass::EncryptedPassword; use editor::Editor; use futures::channel::oneshot; use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Styled}; @@ -7,13 +8,15 @@ use ui::{ LabelSize, ParentElement, Render, SharedString, StyledExt, StyledTypography, Window, div, h_flex, v_flex, }; +use util::maybe; use workspace::ModalView; +use zeroize::Zeroize; pub(crate) struct AskPassModal { operation: SharedString, prompt: SharedString, editor: Entity, - tx: Option>, + tx: Option>, } impl EventEmitter for AskPassModal {} @@ -28,7 +31,7 @@ impl AskPassModal { pub fn new( operation: SharedString, prompt: SharedString, - tx: oneshot::Sender, + tx: oneshot::Sender, window: &mut Window, cx: &mut Context, ) -> Self { @@ -53,10 +56,20 @@ impl AskPassModal { cx.emit(DismissEvent); } - fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context) { - if let Some(tx) = self.tx.take() { - tx.send(self.editor.read(cx).text(cx)).ok(); - } + fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { + maybe!({ + let tx = self.tx.take()?; + let mut text = self.editor.update(cx, |this, cx| { + let text = this.text(cx); + this.clear(window, cx); + text + }); + let pw = askpass::EncryptedPassword::try_from(text.as_ref()).ok()?; + text.zeroize(); + tx.send(pw).ok(); + Some(()) + }); + cx.emit(DismissEvent); } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 3d46a44770ec2504991899e98c1504116611c20b..1af622df351f05721c850debebfe5b3e84284dad 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -89,6 +89,7 @@ util.workspace = true watch.workspace = true which.workspace = true worktree.workspace = true +zeroize.workspace = true zlog.workspace = true workspace-hack.workspace = true diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index a3d777ac774216967b2a5ffab03c72cf51dd9e7d..401a610ba0a2d0e17657547553ed03d8b9fca38f 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -7,7 +7,7 @@ use crate::{ worktree_store::{WorktreeStore, WorktreeStoreEvent}, }; use anyhow::{Context as _, Result, anyhow, bail}; -use askpass::AskPassDelegate; +use askpass::{AskPassDelegate, EncryptedPassword}; use buffer_diff::{BufferDiff, BufferDiffEvent}; use client::ProjectId; use collections::HashMap; @@ -68,6 +68,7 @@ use worktree::{ File, PathChange, PathKey, PathProgress, PathSummary, PathTarget, ProjectEntryId, UpdatedGitRepositoriesSet, UpdatedGitRepository, Worktree, }; +use zeroize::Zeroize; pub struct GitStore { state: GitStoreState, @@ -2106,7 +2107,7 @@ impl GitStore { .lock() .insert(envelope.payload.askpass_id, askpass); - Ok(proto::AskPassResponse { response }) + response.try_into() } async fn handle_check_for_pushed_commits( @@ -2740,7 +2741,10 @@ fn make_remote_delegate( prompt, }); cx.spawn(async move |_, _| { - tx.send(response.await?.response).ok(); + let mut response = response.await?.response; + tx.send(EncryptedPassword::try_from(response.as_ref())?) + .ok(); + response.zeroize(); anyhow::Ok(()) }) .detach_and_log_err(cx); diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 1445b80fa6118bbc79fa83f113bc23bdf66c7dc4..d81ac89e7cdacb96198313b2b8cdea86430dd0b2 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -14,6 +14,7 @@ doctest = false [dependencies] anyhow.workspace = true +askpass.workspace = true auto_update.workspace = true editor.workspace = true extension_host.workspace = true diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index fba7b122dd7842edfcb5f66054040e378acbfc70..689b06da71e43cfa451113a87b2db46be3bcaa75 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -1,6 +1,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{Context as _, Result}; +use askpass::EncryptedPassword; use auto_update::AutoUpdater; use editor::Editor; use extension_host::ExtensionStore; @@ -118,7 +119,7 @@ pub struct RemoteConnectionPrompt { nickname: Option, is_wsl: bool, status_message: Option, - prompt: Option<(Entity, oneshot::Sender)>, + prompt: Option<(Entity, oneshot::Sender)>, cancellation: Option>, editor: Entity, } @@ -160,10 +161,10 @@ impl RemoteConnectionPrompt { self.cancellation = Some(tx); } - pub fn set_prompt( + fn set_prompt( &mut self, prompt: String, - tx: oneshot::Sender, + tx: oneshot::Sender, window: &mut Window, cx: &mut Context, ) { @@ -203,8 +204,12 @@ impl RemoteConnectionPrompt { pub fn confirm(&mut self, window: &mut Window, cx: &mut Context) { if let Some((_, tx)) = self.prompt.take() { self.status_message = Some("Connecting".into()); + self.editor.update(cx, |editor, cx| { - tx.send(editor.text(cx)).ok(); + let pw = editor.text(cx); + if let Ok(secure) = EncryptedPassword::try_from(pw.as_ref()) { + tx.send(secure).ok(); + } editor.clear(window, cx); }); } @@ -438,11 +443,16 @@ impl ModalView for RemoteConnectionModal { pub struct RemoteClientDelegate { window: AnyWindowHandle, ui: WeakEntity, - known_password: Option, + known_password: Option, } impl remote::RemoteClientDelegate for RemoteClientDelegate { - fn ask_password(&self, prompt: String, tx: oneshot::Sender, cx: &mut AsyncApp) { + fn ask_password( + &self, + prompt: String, + tx: oneshot::Sender, + cx: &mut AsyncApp, + ) { let mut known_password = self.known_password.clone(); if let Some(password) = known_password.take() { tx.send(password).ok(); @@ -531,7 +541,10 @@ pub fn connect_over_ssh( cx: &mut App, ) -> Task>>> { let window = window.window_handle(); - let known_password = connection_options.password.clone(); + let known_password = connection_options + .password + .as_deref() + .and_then(|pw| EncryptedPassword::try_from(pw).ok()); let (tx, rx) = oneshot::channel(); ui.update(cx, |ui, _cx| ui.set_cancellation_tx(tx)); @@ -557,9 +570,10 @@ pub fn connect( ) -> Task>>> { let window = window.window_handle(); let known_password = match &connection_options { - RemoteConnectionOptions::Ssh(ssh_connection_options) => { - ssh_connection_options.password.clone() - } + RemoteConnectionOptions::Ssh(ssh_connection_options) => ssh_connection_options + .password + .as_deref() + .and_then(|pw| pw.try_into().ok()), _ => None, }; let (tx, rx) = oneshot::channel(); @@ -644,7 +658,10 @@ pub async fn open_remote_project( known_password: if let RemoteConnectionOptions::Ssh(options) = &connection_options { - options.password.clone() + options + .password + .as_deref() + .and_then(|pw| EncryptedPassword::try_from(pw).ok()) } else { None }, diff --git a/crates/remote/Cargo.toml b/crates/remote/Cargo.toml index 59d80206b0585d59df6c73e2fee44b9ba1fb70c1..545eb8d43dc445732e20615d4f8aad9b927531f4 100644 --- a/crates/remote/Cargo.toml +++ b/crates/remote/Cargo.toml @@ -44,6 +44,7 @@ util.workspace = true which.workspace = true workspace-hack.workspace = true + [dev-dependencies] gpui = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] } diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index 0363fc721a2d51971f49112af520b5dd34b52cb1..46fd903fc310a3964ff6571b0bdf4369d0707238 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -8,6 +8,7 @@ use crate::{ }, }; use anyhow::{Context as _, Result, anyhow}; +use askpass::EncryptedPassword; use async_trait::async_trait; use collections::HashMap; use futures::{ @@ -60,7 +61,12 @@ pub struct CommandTemplate { } pub trait RemoteClientDelegate: Send + Sync { - fn ask_password(&self, prompt: String, tx: oneshot::Sender, cx: &mut AsyncApp); + fn ask_password( + &self, + prompt: String, + tx: oneshot::Sender, + cx: &mut AsyncApp, + ); fn get_download_params( &self, platform: RemotePlatform, @@ -1373,6 +1379,7 @@ mod fake { use super::{ChannelClient, RemoteClientDelegate, RemoteConnection, RemotePlatform}; use crate::remote_client::{CommandTemplate, RemoteConnectionOptions}; use anyhow::Result; + use askpass::EncryptedPassword; use async_trait::async_trait; use collections::HashMap; use futures::{ @@ -1517,7 +1524,7 @@ mod fake { pub(super) struct Delegate; impl RemoteClientDelegate for Delegate { - fn ask_password(&self, _: String, _: oneshot::Sender, _: &mut AsyncApp) { + fn ask_password(&self, _: String, _: oneshot::Sender, _: &mut AsyncApp) { unreachable!() } diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 85ebf4659ccfbda35933f8b8aa4bec25d44a489c..2afed3f900012e969135b82bae627b5e4a9ddac2 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -74,6 +74,8 @@ struct SshSocket { #[cfg(not(target_os = "windows"))] socket_path: PathBuf, envs: HashMap, + #[cfg(target_os = "windows")] + password: askpass::EncryptedPassword, } macro_rules! shell_script { @@ -347,7 +349,13 @@ impl SshRemoteConnection { #[cfg(not(target_os = "windows"))] let socket = SshSocket::new(connection_options, socket_path)?; #[cfg(target_os = "windows")] - let socket = SshSocket::new(connection_options, &temp_dir, askpass.get_password())?; + let socket = SshSocket::new( + connection_options, + &temp_dir, + askpass + .get_password() + .context("Failed to fetch askpass password")?, + )?; drop(askpass); let ssh_platform = socket.platform().await?; @@ -674,16 +682,21 @@ impl SshSocket { } #[cfg(target_os = "windows")] - fn new(options: SshConnectionOptions, temp_dir: &TempDir, secret: String) -> Result { + fn new( + options: SshConnectionOptions, + temp_dir: &TempDir, + password: askpass::EncryptedPassword, + ) -> Result { let askpass_script = temp_dir.path().join("askpass.bat"); std::fs::write(&askpass_script, "@ECHO OFF\necho %ZED_SSH_ASKPASS%")?; let mut envs = HashMap::default(); envs.insert("SSH_ASKPASS_REQUIRE".into(), "force".into()); envs.insert("SSH_ASKPASS".into(), askpass_script.display().to_string()); - envs.insert("ZED_SSH_ASKPASS".into(), secret); + Ok(Self { connection_options: options, envs, + password, }) } @@ -737,12 +750,14 @@ impl SshSocket { #[cfg(target_os = "windows")] fn ssh_options<'a>(&self, command: &'a mut process::Command) -> &'a mut process::Command { + use askpass::ProcessExt; command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .args(self.connection_options.additional_args()) .envs(self.envs.clone()) + .encrypted_env("ZED_SSH_ASKPASS", self.password.clone()) } // On Windows, we need to use `SSH_ASKPASS` to provide the password to ssh. From 3646aa6bbaa437b0e173c8960f1a4ae42dd934b4 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:46:52 +0530 Subject: [PATCH 219/721] language_models: Actually override Ollama model from settings (#38628) The current problem is that if I specify model parameters, like `max_tokens`, in `settings.json` for an Ollama model, they do not override the values coming from the Ollama API. Instead, the parameters from the API are used. For example, in the settings below, even though I have overridden `max_tokens`, Zed will still use the API's default `context_length` of 4k. ``` "language_models": { "ollama": { "available_models": [ { "name": "qwen3-coder:latest", "display_name": "Qwen 3 Coder", "max_tokens": 64000, "supports_tools": true, "keep_alive": "15m", "supports_thinking": false, "supports_images": false } ] } }, ``` Release Notes: - Fixed an issue where Ollama model parameters were not being correctly overridden by user settings. --- crates/language_models/src/provider/ollama.rs | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index ff4a8d6c2c3b7b3cb11f6f597e129ca769459bc0..abaf503326f7178db8ebdb339dd3571ad8146552 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -249,19 +249,32 @@ impl LanguageModelProvider for OllamaLanguageModelProvider { } // Override with available models from settings - for model in &OllamaLanguageModelProvider::settings(cx).available_models { - models.insert( - model.name.clone(), - ollama::Model { - name: model.name.clone(), - display_name: model.display_name.clone(), - max_tokens: model.max_tokens, - keep_alive: model.keep_alive.clone(), - supports_tools: model.supports_tools, - supports_vision: model.supports_images, - supports_thinking: model.supports_thinking, - }, - ); + for setting_model in &OllamaLanguageModelProvider::settings(cx).available_models { + let setting_base = setting_model.name.split(':').next().unwrap(); + if let Some(model) = models + .values_mut() + .find(|m| m.name.split(':').next().unwrap() == setting_base) + { + model.max_tokens = setting_model.max_tokens; + model.display_name = setting_model.display_name.clone(); + model.keep_alive = setting_model.keep_alive.clone(); + model.supports_tools = setting_model.supports_tools; + model.supports_vision = setting_model.supports_images; + model.supports_thinking = setting_model.supports_thinking; + } else { + models.insert( + setting_model.name.clone(), + ollama::Model { + name: setting_model.name.clone(), + display_name: setting_model.display_name.clone(), + max_tokens: setting_model.max_tokens, + keep_alive: setting_model.keep_alive.clone(), + supports_tools: setting_model.supports_tools, + supports_vision: setting_model.supports_images, + supports_thinking: setting_model.supports_thinking, + }, + ); + } } let mut models = models From f78699eb71180d3cd91f907de42bef66aae2c47b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 23 Sep 2025 13:44:43 -0400 Subject: [PATCH 220/721] Update plan text (#38731) Release Notes: - N/A --------- Co-authored-by: David Kleingeld --- Cargo.lock | 1 + crates/agent_ui/src/ui/end_trial_upsell.rs | 4 +- crates/ai_onboarding/src/ai_onboarding.rs | 8 +- crates/ai_onboarding/src/ai_upsell_card.rs | 14 ++-- crates/ai_onboarding/src/plan_definitions.rs | 75 +++++++++++++------ .../ai_onboarding/src/young_account_banner.rs | 19 ++++- crates/feature_flags/src/flags.rs | 4 + crates/language_models/Cargo.toml | 1 + crates/language_models/src/provider/cloud.rs | 22 ++++-- 9 files changed, 104 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 898533b5a531fb92e63a709cd30b022779f5c03a..b34c98fa72cc8cd2739fe24577af67a940cac10f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9626,6 +9626,7 @@ dependencies = [ "credentials_provider", "deepseek", "editor", + "feature_flags", "fs", "futures 0.3.31", "google_ai", diff --git a/crates/agent_ui/src/ui/end_trial_upsell.rs b/crates/agent_ui/src/ui/end_trial_upsell.rs index 4db9244469cf1ad7fab414874a64e45f3b97e377..9c25519659056354ed5f575be885a46151497c2e 100644 --- a/crates/agent_ui/src/ui/end_trial_upsell.rs +++ b/crates/agent_ui/src/ui/end_trial_upsell.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions}; use client::zed_urls; -use cloud_llm_client::{Plan, PlanV1}; +use cloud_llm_client::{Plan, PlanV2}; use gpui::{AnyElement, App, IntoElement, RenderOnce, Window}; use ui::{Divider, Tooltip, prelude::*}; @@ -112,7 +112,7 @@ impl Component for EndTrialUpsell { Some( v_flex() .child(EndTrialUpsell { - plan: Plan::V1(PlanV1::ZedFree), + plan: Plan::V2(PlanV2::ZedFree), dismiss_upsell: Arc::new(|_, _| {}), }) .into_any_element(), diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs index e131a60b12c2b330ff3a6c099713fa4a4a083358..35e5832d3eeae4450ab9d91deed1c3a08b564aab 100644 --- a/crates/ai_onboarding/src/ai_onboarding.rs +++ b/crates/ai_onboarding/src/ai_onboarding.rs @@ -120,7 +120,7 @@ impl ZedAiOnboarding { .max_w_full() .gap_1() .child(Headline::new("Welcome to Zed AI")) - .child(YoungAccountBanner) + .child(YoungAccountBanner::new(is_v2)) .child( v_flex() .mt_2() @@ -372,7 +372,7 @@ impl Component for ZedAiOnboarding { "Free Plan", onboarding( SignInStatus::SignedIn, - Some(Plan::V1(PlanV1::ZedFree)), + Some(Plan::V2(PlanV2::ZedFree)), false, ), ), @@ -380,7 +380,7 @@ impl Component for ZedAiOnboarding { "Pro Trial", onboarding( SignInStatus::SignedIn, - Some(Plan::V1(PlanV1::ZedProTrial)), + Some(Plan::V2(PlanV2::ZedProTrial)), false, ), ), @@ -388,7 +388,7 @@ impl Component for ZedAiOnboarding { "Pro Plan", onboarding( SignInStatus::SignedIn, - Some(Plan::V1(PlanV1::ZedPro)), + Some(Plan::V2(PlanV2::ZedPro)), false, ), ), diff --git a/crates/ai_onboarding/src/ai_upsell_card.rs b/crates/ai_onboarding/src/ai_upsell_card.rs index 51758dd9ac123b309450018bd254a2aae31d68af..f6a47596664b3963c7a8d104c7b53fd4a4100e78 100644 --- a/crates/ai_onboarding/src/ai_upsell_card.rs +++ b/crates/ai_onboarding/src/ai_upsell_card.rs @@ -175,7 +175,7 @@ impl RenderOnce for AiUpsellCard { .child(Label::new("Try Zed AI").size(LabelSize::Large)) .map(|this| { if self.account_too_young { - this.child(YoungAccountBanner).child( + this.child(YoungAccountBanner::new(is_v2_plan)).child( v_flex() .mt_2() .gap_1() @@ -215,7 +215,7 @@ impl RenderOnce for AiUpsellCard { .child( footer_container .child( - Button::new("start_trial", "Start 14-day Free Pro Trial") + Button::new("start_trial", "Start Pro Trial") .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .when_some(self.tab_index, |this, tab_index| { @@ -230,7 +230,7 @@ impl RenderOnce for AiUpsellCard { }), ) .child( - Label::new("No credit card required") + Label::new("14 days, no credit card required") .size(LabelSize::Small) .color(Color::Muted), ), @@ -327,7 +327,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: false, - user_plan: Some(Plan::V1(PlanV1::ZedFree)), + user_plan: Some(Plan::V2(PlanV2::ZedFree)), tab_index: Some(1), } .into_any_element(), @@ -338,7 +338,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: true, - user_plan: Some(Plan::V1(PlanV1::ZedFree)), + user_plan: Some(Plan::V2(PlanV2::ZedFree)), tab_index: Some(1), } .into_any_element(), @@ -349,7 +349,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: false, - user_plan: Some(Plan::V1(PlanV1::ZedProTrial)), + user_plan: Some(Plan::V2(PlanV2::ZedProTrial)), tab_index: Some(1), } .into_any_element(), @@ -360,7 +360,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: false, - user_plan: Some(Plan::V1(PlanV1::ZedPro)), + user_plan: Some(Plan::V2(PlanV2::ZedPro)), tab_index: Some(1), } .into_any_element(), diff --git a/crates/ai_onboarding/src/plan_definitions.rs b/crates/ai_onboarding/src/plan_definitions.rs index dce67d421006ce918018923b86dbe22012efef01..11f563117132bd6860d8aef655bd0ed6b392e0e7 100644 --- a/crates/ai_onboarding/src/plan_definitions.rs +++ b/crates/ai_onboarding/src/plan_definitions.rs @@ -7,33 +7,62 @@ pub struct PlanDefinitions; impl PlanDefinitions { pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI."; - pub fn free_plan(&self, _is_v2: bool) -> impl IntoElement { - List::new() - .child(ListBulletItem::new("50 prompts with Claude models")) - .child(ListBulletItem::new("2,000 accepted edit predictions")) + pub fn free_plan(&self, is_v2: bool) -> impl IntoElement { + if is_v2 { + List::new() + .child(ListBulletItem::new("2,000 accepted edit predictions")) + .child(ListBulletItem::new( + "Unlimited prompts with your AI API keys", + )) + .child(ListBulletItem::new( + "Unlimited use of external agents like Claude Code", + )) + } else { + List::new() + .child(ListBulletItem::new("50 prompts with Claude models")) + .child(ListBulletItem::new("2,000 accepted edit predictions")) + } } - pub fn pro_trial(&self, _is_v2: bool, period: bool) -> impl IntoElement { - List::new() - .child(ListBulletItem::new("150 prompts with Claude models")) - .child(ListBulletItem::new( - "Unlimited edit predictions with Zeta, our open-source model", - )) - .when(period, |this| { - this.child(ListBulletItem::new( - "Try it out for 14 days for free, no credit card required", + pub fn pro_trial(&self, is_v2: bool, period: bool) -> impl IntoElement { + if is_v2 { + List::new() + .child(ListBulletItem::new("Unlimited edit predictions")) + .child(ListBulletItem::new("$20 of tokens")) + .when(period, |this| { + this.child(ListBulletItem::new( + "Try it out for 14 days, no credit card required", + )) + }) + } else { + List::new() + .child(ListBulletItem::new("150 prompts with Claude models")) + .child(ListBulletItem::new( + "Unlimited edit predictions with Zeta, our open-source model", )) - }) + .when(period, |this| { + this.child(ListBulletItem::new( + "Try it out for 14 days, no credit card required", + )) + }) + } } - pub fn pro_plan(&self, _is_v2: bool, price: bool) -> impl IntoElement { - List::new() - .child(ListBulletItem::new("500 prompts with Claude models")) - .child(ListBulletItem::new( - "Unlimited edit predictions with Zeta, our open-source model", - )) - .when(price, |this| { - this.child(ListBulletItem::new("$20 USD per month")) - }) + pub fn pro_plan(&self, is_v2: bool, price: bool) -> impl IntoElement { + if is_v2 { + List::new() + .child(ListBulletItem::new("Unlimited edit predictions")) + .child(ListBulletItem::new("$5 of tokens")) + .child(ListBulletItem::new("Usage-based billing beyond $5")) + } else { + List::new() + .child(ListBulletItem::new("500 prompts with Claude models")) + .child(ListBulletItem::new( + "Unlimited edit predictions with Zeta, our open-source model", + )) + .when(price, |this| { + this.child(ListBulletItem::new("$20 USD per month")) + }) + } } } diff --git a/crates/ai_onboarding/src/young_account_banner.rs b/crates/ai_onboarding/src/young_account_banner.rs index ae13b9556885c1552f7e90935f844347cd76a778..fde9a31c49bc950db2fb77702cfd0befe61d4fa3 100644 --- a/crates/ai_onboarding/src/young_account_banner.rs +++ b/crates/ai_onboarding/src/young_account_banner.rs @@ -2,17 +2,30 @@ use gpui::{IntoElement, ParentElement}; use ui::{Banner, prelude::*}; #[derive(IntoElement)] -pub struct YoungAccountBanner; +pub struct YoungAccountBanner { + is_v2: bool, +} + +impl YoungAccountBanner { + pub fn new(is_v2: bool) -> Self { + Self { is_v2 } + } +} impl RenderOnce for YoungAccountBanner { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. To request an exception, reach out to billing-support@zed.dev."; + const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. You can request an exception by reaching out to billing-support@zed.dev"; + const YOUNG_ACCOUNT_DISCLAIMER_V2: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for the Pro trial. You can request an exception by reaching out to billing-support@zed.dev"; let label = div() .w_full() .text_sm() .text_color(cx.theme().colors().text_muted) - .child(YOUNG_ACCOUNT_DISCLAIMER); + .child(if self.is_v2 { + YOUNG_ACCOUNT_DISCLAIMER_V2 + } else { + YOUNG_ACCOUNT_DISCLAIMER + }); div() .max_w_full() diff --git a/crates/feature_flags/src/flags.rs b/crates/feature_flags/src/flags.rs index eaae9ce42519a8ebfd40d73d242536c5188fa3dd..d8c8bec873699328b9d2740c97ffc97ffe190429 100644 --- a/crates/feature_flags/src/flags.rs +++ b/crates/feature_flags/src/flags.rs @@ -10,6 +10,10 @@ pub struct BillingV2FeatureFlag {} impl FeatureFlag for BillingV2FeatureFlag { const NAME: &'static str = "billing-v2"; + + fn enabled_for_all() -> bool { + true + } } pub struct NotebookFeatureFlag; diff --git a/crates/language_models/Cargo.toml b/crates/language_models/Cargo.toml index 8a2a681c26ede21ce948b6667b4aaea589724dcf..d4ae4b26b6626684e458193e0fb6ca628aa6c087 100644 --- a/crates/language_models/Cargo.toml +++ b/crates/language_models/Cargo.toml @@ -29,6 +29,7 @@ copilot.workspace = true credentials_provider.workspace = true deepseek = { workspace = true, features = ["schemars"] } editor.workspace = true +feature_flags.workspace = true fs.workspace = true futures.workspace = true google_ai = { workspace = true, features = ["schemars"] } diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 40958af77535b34e2d68afde49e7ab97f07f911f..65fcae0f6a56fe8093b7c0d694843818e278ce87 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -11,6 +11,7 @@ use cloud_llm_client::{ SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME, }; +use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt}; use futures::{ AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream, }; @@ -973,12 +974,13 @@ struct ZedAiConfiguration { } impl RenderOnce for ZedAiConfiguration { - fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { - let young_account_banner = YoungAccountBanner; - + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let is_pro = self.plan.is_some_and(|plan| { matches!(plan, Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)) }); + let is_free_v2 = self + .plan + .is_some_and(|plan| plan == Plan::V2(PlanV2::ZedFree)); let subscription_text = match (self.plan, self.subscription_period) { (Some(Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)), Some(_)) => { "You have access to Zed's hosted models through your Pro subscription." @@ -986,9 +988,16 @@ impl RenderOnce for ZedAiConfiguration { (Some(Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial)), Some(_)) => { "You have access to Zed's hosted models through your Pro trial." } - (Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)), Some(_)) => { + (Some(Plan::V1(PlanV1::ZedFree)), Some(_)) => { "You have basic access to Zed's hosted models through the Free plan." } + (Some(Plan::V2(PlanV2::ZedFree)), Some(_)) => { + if self.eligible_for_trial { + "Subscribe for access to Zed's hosted models. Start with a 14 day free trial." + } else { + "Subscribe for access to Zed's hosted models." + } + } _ => { if self.eligible_for_trial { "Subscribe for access to Zed's hosted models. Start with a 14 day free trial." @@ -1038,7 +1047,10 @@ impl RenderOnce for ZedAiConfiguration { v_flex().gap_2().w_full().map(|this| { if self.account_too_young { - this.child(young_account_banner).child( + this.child(YoungAccountBanner::new( + is_free_v2 || cx.has_flag::(), + )) + .child( Button::new("upgrade", "Upgrade to Pro") .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .full_width() From afaed3af623dd58a972640a33a8925ba6fdfaa3f Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 23 Sep 2025 13:47:32 -0400 Subject: [PATCH 221/721] Windows: Fix keybinds for onboarding dialog (#38730) Closes: https://github.com/zed-industries/zed/issues/38482 - Previously fixed by: https://github.com/zed-industries/zed/pull/36712 - Regressed in: https://github.com/zed-industries/zed/pull/36572 Release Notes: - N/A --- assets/keymaps/default-windows.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 10b7a85d7c56f733228c75bf5e9cde028349f829..65de7a522b4d6ad7b86f429ae3102d87b974add3 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -17,7 +17,6 @@ "up": "menu::SelectPrevious", "enter": "menu::Confirm", "ctrl-enter": "menu::SecondaryConfirm", - "ctrl-escape": "menu::Cancel", "ctrl-c": "menu::Cancel", "escape": "menu::Cancel", "shift-alt-enter": "menu::Restart", @@ -1249,8 +1248,8 @@ "ctrl-1": "onboarding::ActivateBasicsPage", "ctrl-2": "onboarding::ActivateEditingPage", "ctrl-3": "onboarding::ActivateAISetupPage", - "ctrl-escape": "onboarding::Finish", - "alt-tab": "onboarding::SignIn", + "ctrl-enter": "onboarding::Finish", + "alt-shift-l": "onboarding::SignIn", "shift-alt-a": "onboarding::OpenAccount" } } From 9ac511e47cf6dc313641cba7364de60ce78b737c Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 23 Sep 2025 12:32:17 -0600 Subject: [PATCH 222/721] zeta2: Collect nearby diagnostics (#38732) Release Notes: - N/A Co-authored-by: Bennet --- .../cloud_llm_client/src/predict_edits_v3.rs | 12 ++- crates/language/src/buffer.rs | 6 ++ crates/zeta2/src/zeta2.rs | 102 +++++++++++++++--- crates/zeta2_tools/src/zeta2_tools.rs | 36 ++++--- crates/zeta_cli/src/main.rs | 17 +-- 5 files changed, 135 insertions(+), 38 deletions(-) diff --git a/crates/cloud_llm_client/src/predict_edits_v3.rs b/crates/cloud_llm_client/src/predict_edits_v3.rs index 35d05ff81f9eb72c4b8261dc3e3340b18c79ebfa..90f2a8b24fda6f38d52a21c90d184f9a66e1e80d 100644 --- a/crates/cloud_llm_client/src/predict_edits_v3.rs +++ b/crates/cloud_llm_client/src/predict_edits_v3.rs @@ -24,6 +24,8 @@ pub struct PredictEditsRequest { pub can_collect_data: bool, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub diagnostic_groups: Vec, + #[serde(skip_serializing_if = "is_default", default)] + pub diagnostic_groups_truncated: bool, /// Info about the git repository state, only present when can_collect_data is true. #[serde(skip_serializing_if = "Option::is_none", default)] pub git_info: Option, @@ -92,10 +94,8 @@ pub struct ScoreComponents { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DiagnosticGroup { - pub language_server: String, - pub diagnostic_group: serde_json::Value, -} +#[serde(transparent)] +pub struct DiagnosticGroup(pub Box); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PredictEditsResponse { @@ -119,3 +119,7 @@ pub struct Edit { pub range: Range, pub content: String, } + +fn is_default(value: &T) -> bool { + *value == T::default() +} diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 2610447c92107c34bca52fa004600a9fe10ab6d4..8e104fc8cb8a2a1cd211cbd59a0caa4d2f79f18e 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -4558,6 +4558,12 @@ impl BufferSnapshot { }) } + /// Raw access to the diagnostic sets. Typically `diagnostic_groups` or `diagnostic_group` + /// should be used instead. + pub fn diagnostic_sets(&self) -> &SmallVec<[(LanguageServerId, DiagnosticSet); 2]> { + &self.diagnostics + } + /// Returns all the diagnostic groups associated with the given /// language server ID. If no language server ID is provided, /// all diagnostics groups are returned. diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 159173493668754eb87f159699694b43c76798c4..4319f946f6cbf7319826e22594ed6331b2726889 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -18,7 +18,9 @@ use gpui::{ App, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task, WeakEntity, http_client, prelude::*, }; -use language::{Anchor, Buffer, OffsetRangeExt as _, ToPoint}; +use language::{ + Anchor, Buffer, DiagnosticSet, LanguageServerId, OffsetRangeExt as _, ToOffset as _, ToPoint, +}; use language::{BufferSnapshot, EditPreview}; use language_model::{LlmApiToken, RefreshLlmTokenListener}; use project::Project; @@ -45,6 +47,11 @@ pub const DEFAULT_EXCERPT_OPTIONS: EditPredictionExcerptOptions = EditPrediction target_before_cursor_over_total_bytes: 0.5, }; +pub const DEFAULT_OPTIONS: ZetaOptions = ZetaOptions { + excerpt: DEFAULT_EXCERPT_OPTIONS, + max_diagnostic_bytes: 2048, +}; + #[derive(Clone)] struct ZetaGlobal(Entity); @@ -56,11 +63,17 @@ pub struct Zeta { llm_token: LlmApiToken, _llm_token_subscription: Subscription, projects: HashMap, - pub excerpt_options: EditPredictionExcerptOptions, + options: ZetaOptions, update_required: bool, debug_tx: Option>>, } +#[derive(Debug, Clone, PartialEq)] +pub struct ZetaOptions { + pub excerpt: EditPredictionExcerptOptions, + pub max_diagnostic_bytes: usize, +} + pub struct PredictionDebugInfo { pub context: EditPredictionContext, pub retrieval_time: TimeDelta, @@ -113,7 +126,7 @@ impl Zeta { projects: HashMap::new(), client, user_store, - excerpt_options: DEFAULT_EXCERPT_OPTIONS, + options: DEFAULT_OPTIONS, llm_token: LlmApiToken::default(), _llm_token_subscription: cx.subscribe( &refresh_llm_token_listener, @@ -138,12 +151,12 @@ impl Zeta { debug_watch_rx } - pub fn excerpt_options(&self) -> &EditPredictionExcerptOptions { - &self.excerpt_options + pub fn options(&self) -> &ZetaOptions { + &self.options } - pub fn set_excerpt_options(&mut self, options: EditPredictionExcerptOptions) { - self.excerpt_options = options; + pub fn set_options(&mut self, options: ZetaOptions) { + self.options = options; } pub fn usage(&self, cx: &App) -> Option { @@ -290,7 +303,7 @@ impl Zeta { .syntax_index .read_with(cx, |index, _cx| index.state().clone()) }); - let excerpt_options = self.excerpt_options.clone(); + let options = self.options.clone(); let snapshot = buffer.read(cx).snapshot(); let Some(excerpt_path) = snapshot.file().map(|path| path.full_path(cx)) else { return Task::ready(Err(anyhow!("No file path for excerpt"))); @@ -343,6 +356,8 @@ impl Zeta { }) .unwrap_or_default(); + let diagnostics = snapshot.diagnostic_sets().clone(); + let request_task = cx.background_spawn({ let snapshot = snapshot.clone(); let buffer = buffer.clone(); @@ -353,14 +368,15 @@ impl Zeta { None }; - let cursor_point = position.to_point(&snapshot); + let cursor_offset = position.to_offset(&snapshot); + let cursor_point = cursor_offset.to_point(&snapshot); let before_retrieval = chrono::Utc::now(); let Some(context) = EditPredictionContext::gather_context( cursor_point, &snapshot, - &excerpt_options, + &options.excerpt, index_state.as_deref(), ) else { return Ok(None); @@ -372,13 +388,22 @@ impl Zeta { None }; + let (diagnostic_groups, diagnostic_groups_truncated) = + Self::gather_nearby_diagnostics( + cursor_offset, + &diagnostics, + &snapshot, + options.max_diagnostic_bytes, + ); + let request = make_cloud_request( excerpt_path.clone(), context, events, // TODO data collection false, - Vec::new(), + diagnostic_groups, + diagnostic_groups_truncated, None, debug_context.is_some(), &worktree_snapshots, @@ -575,6 +600,52 @@ impl Zeta { } } + fn gather_nearby_diagnostics( + cursor_offset: usize, + diagnostic_sets: &[(LanguageServerId, DiagnosticSet)], + snapshot: &BufferSnapshot, + max_diagnostics_bytes: usize, + ) -> (Vec, bool) { + // TODO: Could make this more efficient + let mut diagnostic_groups = Vec::new(); + for (language_server_id, diagnostics) in diagnostic_sets { + let mut groups = Vec::new(); + diagnostics.groups(*language_server_id, &mut groups, &snapshot); + diagnostic_groups.extend( + groups + .into_iter() + .map(|(_, group)| group.resolve::(&snapshot)), + ); + } + + // sort by proximity to cursor + diagnostic_groups.sort_by_key(|group| { + let range = &group.entries[group.primary_ix].range; + if range.start >= cursor_offset { + range.start - cursor_offset + } else if cursor_offset >= range.end { + cursor_offset - range.end + } else { + (cursor_offset - range.start).min(range.end - cursor_offset) + } + }); + + let mut results = Vec::new(); + let mut diagnostic_groups_truncated = false; + let mut diagnostics_byte_count = 0; + for group in diagnostic_groups { + let raw_value = serde_json::value::to_raw_value(&group).unwrap(); + diagnostics_byte_count += raw_value.get().len(); + if diagnostics_byte_count > max_diagnostics_bytes { + diagnostic_groups_truncated = true; + break; + } + results.push(predict_edits_v3::DiagnosticGroup(raw_value)); + } + + (results, diagnostic_groups_truncated) + } + // TODO: Dedupe with similar code in request_prediction? pub fn cloud_request_for_zeta_cli( &mut self, @@ -590,7 +661,7 @@ impl Zeta { .syntax_index .read_with(cx, |index, _cx| index.state().clone()) }); - let excerpt_options = self.excerpt_options.clone(); + let options = self.options.clone(); let snapshot = buffer.read(cx).snapshot(); let Some(excerpt_path) = snapshot.file().map(|path| path.full_path(cx)) else { return Task::ready(Err(anyhow!("No file path for excerpt"))); @@ -614,7 +685,7 @@ impl Zeta { EditPredictionContext::gather_context( cursor_point, &snapshot, - &excerpt_options, + &options.excerpt, index_state.as_deref(), ) .context("Failed to select excerpt") @@ -626,6 +697,7 @@ impl Zeta { Vec::new(), false, Vec::new(), + false, None, debug_info, &worktree_snapshots, @@ -985,6 +1057,7 @@ fn make_cloud_request( events: Vec, can_collect_data: bool, diagnostic_groups: Vec, + diagnostic_groups_truncated: bool, git_info: Option, debug_info: bool, worktrees: &Vec, @@ -1058,6 +1131,8 @@ fn make_cloud_request( events, can_collect_data, diagnostic_groups, + diagnostic_groups_truncated, + git_info, debug_info, } @@ -1141,7 +1216,6 @@ fn interpolate( mod tests { use super::*; use gpui::TestAppContext; - use language::ToOffset as _; #[gpui::test] async fn test_edit_prediction_basic_interpolation(cx: &mut TestAppContext) { diff --git a/crates/zeta2_tools/src/zeta2_tools.rs b/crates/zeta2_tools/src/zeta2_tools.rs index 0d743e388095f9c4658d102c69faec2eb3ad0275..de2cc660bef9f1326ee95eb145cd8b3c551a7acf 100644 --- a/crates/zeta2_tools/src/zeta2_tools.rs +++ b/crates/zeta2_tools/src/zeta2_tools.rs @@ -22,7 +22,7 @@ use ui::prelude::*; use ui_input::SingleLineInput; use util::ResultExt; use workspace::{Item, SplitDirection, Workspace}; -use zeta2::Zeta; +use zeta2::{Zeta, ZetaOptions}; use edit_prediction_context::{EditPredictionExcerptOptions, SnippetStyle}; @@ -137,25 +137,28 @@ impl Zeta2Inspector { _update_state_task: Task::ready(()), _receive_task: receive_task, }; - this.set_input_options(&zeta.read(cx).excerpt_options().clone(), window, cx); + this.set_input_options(&zeta.read(cx).options().clone(), window, cx); this } fn set_input_options( &mut self, - options: &EditPredictionExcerptOptions, + options: &ZetaOptions, window: &mut Window, cx: &mut Context, ) { self.max_bytes_input.update(cx, |input, cx| { - input.set_text(options.max_bytes.to_string(), window, cx); + input.set_text(options.excerpt.max_bytes.to_string(), window, cx); }); self.min_bytes_input.update(cx, |input, cx| { - input.set_text(options.min_bytes.to_string(), window, cx); + input.set_text(options.excerpt.min_bytes.to_string(), window, cx); }); self.cursor_context_ratio_input.update(cx, |input, cx| { input.set_text( - format!("{:.2}", options.target_before_cursor_over_total_bytes), + format!( + "{:.2}", + options.excerpt.target_before_cursor_over_total_bytes + ), window, cx, ); @@ -163,9 +166,8 @@ impl Zeta2Inspector { cx.notify(); } - fn set_options(&mut self, options: EditPredictionExcerptOptions, cx: &mut Context) { - self.zeta - .update(cx, |this, _cx| this.set_excerpt_options(options)); + fn set_options(&mut self, options: ZetaOptions, cx: &mut Context) { + self.zeta.update(cx, |this, _cx| this.set_options(options)); const THROTTLE_TIME: Duration = Duration::from_millis(100); @@ -233,7 +235,7 @@ impl Zeta2Inspector { .unwrap_or_default() } - let options = EditPredictionExcerptOptions { + let excerpt_options = EditPredictionExcerptOptions { max_bytes: number_input_value(&this.max_bytes_input, cx), min_bytes: number_input_value(&this.min_bytes_input, cx), target_before_cursor_over_total_bytes: number_input_value( @@ -242,7 +244,13 @@ impl Zeta2Inspector { ), }; - this.set_options(options, cx); + this.set_options( + ZetaOptions { + excerpt: excerpt_options, + ..this.zeta.read(cx).options().clone() + }, + cx, + ); }, ) .detach(); @@ -525,15 +533,15 @@ impl Render for Zeta2Inspector { .child( ui::Button::new("reset-options", "Reset") .disabled( - self.zeta.read(cx).excerpt_options() - == &zeta2::DEFAULT_EXCERPT_OPTIONS, + self.zeta.read(cx).options() + == &zeta2::DEFAULT_OPTIONS, ) .style(ButtonStyle::Outlined) .size(ButtonSize::Large) .on_click(cx.listener( |this, _, window, cx| { this.set_input_options( - &zeta2::DEFAULT_EXCERPT_OPTIONS, + &zeta2::DEFAULT_OPTIONS, window, cx, ); diff --git a/crates/zeta_cli/src/main.rs b/crates/zeta_cli/src/main.rs index d2cde6be589a8138ef1e88e872a1e18294f4cb30..44ed567a5df1dce4862523d0e2f1c04b44cffa94 100644 --- a/crates/zeta_cli/src/main.rs +++ b/crates/zeta_cli/src/main.rs @@ -70,6 +70,8 @@ struct Zeta2Args { excerpt_min_bytes: usize, #[arg(long, default_value_t = 0.66)] target_before_cursor_over_total_bytes: f32, + #[arg(long, default_value_t = 1024)] + max_diagnostic_bytes: usize, } #[derive(Debug, Clone)] @@ -221,12 +223,15 @@ async fn get_context( }); zeta.update(cx, |zeta, cx| { zeta.register_buffer(&buffer, &project, cx); - zeta.excerpt_options = EditPredictionExcerptOptions { - max_bytes: zeta2_args.excerpt_max_bytes, - min_bytes: zeta2_args.excerpt_min_bytes, - target_before_cursor_over_total_bytes: zeta2_args - .target_before_cursor_over_total_bytes, - } + zeta.set_options(zeta2::ZetaOptions { + excerpt: EditPredictionExcerptOptions { + max_bytes: zeta2_args.excerpt_max_bytes, + min_bytes: zeta2_args.excerpt_min_bytes, + target_before_cursor_over_total_bytes: zeta2_args + .target_before_cursor_over_total_bytes, + }, + max_diagnostic_bytes: zeta2_args.max_diagnostic_bytes, + }) }); // TODO: Actually wait for indexing. let timer = cx.background_executor().timer(Duration::from_secs(5)); From bc528411dfd931162f9e4a0803ab19dda7775297 Mon Sep 17 00:00:00 2001 From: ImFeH2 Date: Wed, 24 Sep 2025 03:11:35 +0800 Subject: [PATCH 223/721] Preserve trailing newline in `TerminalOutput::full_text` (#38061) Closes #30678 This is caused by `TerminalOutput::full_text` triming trailing newline when creating the "REPL Output" buffer. Release Notes: - fix: Preserve trailing newline in `TerminalOutput::full_text` --- crates/repl/src/outputs/plain.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/repl/src/outputs/plain.rs b/crates/repl/src/outputs/plain.rs index 844bf6ed132fbe004b1aa22ddffb78ba39911855..8a07e8748a515c5678ab6a2803fd36495aefe4fc 100644 --- a/crates/repl/src/outputs/plain.rs +++ b/crates/repl/src/outputs/plain.rs @@ -231,8 +231,7 @@ impl TerminalOutput { } } - // Trim any trailing newlines - full_text.trim_end().to_string() + full_text } } From 831de8e48fd39e079f413ff2673dc072d7cd2c48 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Tue, 23 Sep 2025 16:50:07 -0300 Subject: [PATCH 224/721] zeta2: Include edits in prompt and add `max_prompt_bytes` param (#38737) Release Notes: - N/A Co-authored-by: Michael Sloan --- Cargo.lock | 2 + .../cloud_llm_client/src/predict_edits_v3.rs | 2 + crates/cloud_zeta2_prompt/Cargo.toml | 1 + .../src/cloud_zeta2_prompt.rs | 89 ++++++++++++++++--- crates/zeta2/Cargo.toml | 1 + crates/zeta2/src/zeta2.rs | 8 +- crates/zeta2_tools/src/zeta2_tools.rs | 35 +++++--- crates/zeta_cli/src/main.rs | 13 +-- 8 files changed, 118 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b34c98fa72cc8cd2739fe24577af67a940cac10f..d2e287de62aff60ceeb7ccd313db603c303690d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3233,6 +3233,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cloud_llm_client", + "indoc", "ordered-float 2.10.1", "rustc-hash 2.1.1", "strum 0.27.1", @@ -21651,6 +21652,7 @@ dependencies = [ "chrono", "client", "cloud_llm_client", + "cloud_zeta2_prompt", "edit_prediction", "edit_prediction_context", "futures 0.3.31", diff --git a/crates/cloud_llm_client/src/predict_edits_v3.rs b/crates/cloud_llm_client/src/predict_edits_v3.rs index 90f2a8b24fda6f38d52a21c90d184f9a66e1e80d..eeca7ed4e24d594d4a7e9555b3d9fbfd6f3706d2 100644 --- a/crates/cloud_llm_client/src/predict_edits_v3.rs +++ b/crates/cloud_llm_client/src/predict_edits_v3.rs @@ -29,8 +29,10 @@ pub struct PredictEditsRequest { /// Info about the git repository state, only present when can_collect_data is true. #[serde(skip_serializing_if = "Option::is_none", default)] pub git_info: Option, + // Only available to staff #[serde(default)] pub debug_info: bool, + pub prompt_max_bytes: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/cloud_zeta2_prompt/Cargo.toml b/crates/cloud_zeta2_prompt/Cargo.toml index a1194f13615964fd3013eb8dbdf3057984946e32..b06431baf652c67459280ffec64d45ca072420ef 100644 --- a/crates/cloud_zeta2_prompt/Cargo.toml +++ b/crates/cloud_zeta2_prompt/Cargo.toml @@ -14,6 +14,7 @@ path = "src/cloud_zeta2_prompt.rs" [dependencies] anyhow.workspace = true cloud_llm_client.workspace = true +indoc.workspace = true ordered-float.workspace = true rustc-hash.workspace = true strum.workspace = true diff --git a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs index 6690380c74b0d4880210b683f34eea1d98a7946b..89cfb4c41f2d05c521c81c6e136d7bc46861d7bc 100644 --- a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs +++ b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs @@ -1,17 +1,28 @@ //! Zeta2 prompt planning and generation code shared with cloud. use anyhow::{Result, anyhow}; -use cloud_llm_client::predict_edits_v3::{self, ReferencedDeclaration}; +use cloud_llm_client::predict_edits_v3::{self, Event, ReferencedDeclaration}; +use indoc::indoc; use ordered_float::OrderedFloat; use rustc_hash::{FxHashMap, FxHashSet}; +use std::fmt::Write; use std::{cmp::Reverse, collections::BinaryHeap, ops::Range, path::Path}; use strum::{EnumIter, IntoEnumIterator}; +pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024; + pub const CURSOR_MARKER: &str = "<|user_cursor_is_here|>"; /// NOTE: Differs from zed version of constant - includes a newline -pub const EDITABLE_REGION_START_MARKER: &str = "<|editable_region_start|>\n"; +pub const EDITABLE_REGION_START_MARKER_WITH_NEWLINE: &str = "<|editable_region_start|>\n"; /// NOTE: Differs from zed version of constant - includes a newline -pub const EDITABLE_REGION_END_MARKER: &str = "<|editable_region_end|>\n"; +pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n"; + +// TODO: use constants for markers? +pub const SYSTEM_PROMPT: &str = indoc! {" + You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location. + + The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|user_cursor_is_here|>. Please respond with edited code for that region. +"}; pub struct PlannedPrompt<'a> { request: &'a predict_edits_v3::PredictEditsRequest, @@ -286,7 +297,7 @@ impl<'a> PlannedPrompt<'a> { let mut excerpt_file_insertions = vec![ ( self.request.excerpt_range.start, - EDITABLE_REGION_START_MARKER, + EDITABLE_REGION_START_MARKER_WITH_NEWLINE, ), ( self.request.excerpt_range.start + self.request.cursor_offset, @@ -298,10 +309,67 @@ impl<'a> PlannedPrompt<'a> { .end .saturating_sub(0) .max(self.request.excerpt_range.start), - EDITABLE_REGION_END_MARKER, + EDITABLE_REGION_END_MARKER_WITH_NEWLINE, ), ]; + let mut output = String::new(); + output.push_str("## User Edits\n\n"); + Self::push_events(&mut output, &self.request.events); + + output.push_str("\n## Code\n\n"); + Self::push_file_snippets(&mut output, &mut excerpt_file_insertions, file_snippets); + output + } + + fn push_events(output: &mut String, events: &[predict_edits_v3::Event]) { + for event in events { + match event { + Event::BufferChange { + path, + old_path, + diff, + predicted, + } => { + if let Some(old_path) = &old_path + && let Some(new_path) = &path + { + if old_path != new_path { + writeln!( + output, + "User renamed {} to {}\n\n", + old_path.display(), + new_path.display() + ) + .unwrap(); + } + } + + let path = path + .as_ref() + .map_or_else(|| "untitled".to_string(), |path| path.display().to_string()); + + if *predicted { + writeln!( + output, + "User accepted prediction {:?}:\n```diff\n{}\n```\n", + path, diff + ) + .unwrap(); + } else { + writeln!(output, "User edited {:?}:\n```diff\n{}\n```\n", path, diff) + .unwrap(); + } + } + } + } + } + + fn push_file_snippets( + output: &mut String, + excerpt_file_insertions: &mut Vec<(usize, &'static str)>, + file_snippets: Vec<(&Path, Vec<&PlannedSnippet>, bool)>, + ) { fn push_excerpt_file_range( range: Range, text: &str, @@ -325,7 +393,6 @@ impl<'a> PlannedPrompt<'a> { output.push_str(&text[last_offset - range.start..]); } - let mut output = String::new(); for (file_path, mut snippets, is_excerpt_file) in file_snippets { output.push_str(&format!("```{}\n", file_path.display())); @@ -345,8 +412,8 @@ impl<'a> PlannedPrompt<'a> { push_excerpt_file_range( last_range.end..snippet.range.end, text, - &mut excerpt_file_insertions, - &mut output, + excerpt_file_insertions, + output, ); } else { output.push_str(text); @@ -361,8 +428,8 @@ impl<'a> PlannedPrompt<'a> { push_excerpt_file_range( snippet.range.clone(), snippet.text, - &mut excerpt_file_insertions, - &mut output, + excerpt_file_insertions, + output, ); } else { output.push_str(snippet.text); @@ -372,8 +439,6 @@ impl<'a> PlannedPrompt<'a> { output.push_str("```\n\n"); } - - output } } diff --git a/crates/zeta2/Cargo.toml b/crates/zeta2/Cargo.toml index d362441cdd522e012d75bec9bde6daeff50e3e89..11ca5fcfddd3e7a7c1085401e7575bc599aea91e 100644 --- a/crates/zeta2/Cargo.toml +++ b/crates/zeta2/Cargo.toml @@ -17,6 +17,7 @@ arrayvec.workspace = true chrono.workspace = true client.workspace = true cloud_llm_client.workspace = true +cloud_zeta2_prompt.workspace = true edit_prediction.workspace = true edit_prediction_context.workspace = true futures.workspace = true diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 4319f946f6cbf7319826e22594ed6331b2726889..47afc797d5d1d4dafca71f39ba15fbe6fd621c20 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -6,6 +6,7 @@ use cloud_llm_client::predict_edits_v3::{self, Signature}; use cloud_llm_client::{ EXPIRED_LLM_TOKEN_HEADER_NAME, MINIMUM_REQUIRED_VERSION_HEADER_NAME, ZED_VERSION_HEADER_NAME, }; +use cloud_zeta2_prompt::DEFAULT_MAX_PROMPT_BYTES; use edit_prediction::{DataCollectionState, Direction, EditPredictionProvider}; use edit_prediction_context::{ DeclarationId, EditPredictionContext, EditPredictionExcerptOptions, SyntaxIndex, @@ -49,6 +50,7 @@ pub const DEFAULT_EXCERPT_OPTIONS: EditPredictionExcerptOptions = EditPrediction pub const DEFAULT_OPTIONS: ZetaOptions = ZetaOptions { excerpt: DEFAULT_EXCERPT_OPTIONS, + max_prompt_bytes: DEFAULT_MAX_PROMPT_BYTES, max_diagnostic_bytes: 2048, }; @@ -71,6 +73,7 @@ pub struct Zeta { #[derive(Debug, Clone, PartialEq)] pub struct ZetaOptions { pub excerpt: EditPredictionExcerptOptions, + pub max_prompt_bytes: usize, pub max_diagnostic_bytes: usize, } @@ -408,6 +411,7 @@ impl Zeta { debug_context.is_some(), &worktree_snapshots, index_state.as_deref(), + Some(options.max_prompt_bytes), ); let retrieval_time = chrono::Utc::now() - before_retrieval; @@ -702,6 +706,7 @@ impl Zeta { debug_info, &worktree_snapshots, index_state.as_deref(), + Some(options.max_prompt_bytes), ) }) }) @@ -1062,6 +1067,7 @@ fn make_cloud_request( debug_info: bool, worktrees: &Vec, index_state: Option<&SyntaxIndexState>, + prompt_max_bytes: Option, ) -> predict_edits_v3::PredictEditsRequest { let mut signatures = Vec::new(); let mut declaration_to_signature_index = HashMap::default(); @@ -1132,9 +1138,9 @@ fn make_cloud_request( can_collect_data, diagnostic_groups, diagnostic_groups_truncated, - git_info, debug_info, + prompt_max_bytes, } } diff --git a/crates/zeta2_tools/src/zeta2_tools.rs b/crates/zeta2_tools/src/zeta2_tools.rs index de2cc660bef9f1326ee95eb145cd8b3c551a7acf..2dfa292c4380bd7295dbe50669237f0bf865cb7e 100644 --- a/crates/zeta2_tools/src/zeta2_tools.rs +++ b/crates/zeta2_tools/src/zeta2_tools.rs @@ -57,13 +57,16 @@ pub fn init(cx: &mut App) { .detach(); } +// TODO show included diagnostics, and events + pub struct Zeta2Inspector { focus_handle: FocusHandle, project: Entity, last_prediction: Option, - max_bytes_input: Entity, - min_bytes_input: Entity, + max_excerpt_bytes_input: Entity, + min_excerpt_bytes_input: Entity, cursor_context_ratio_input: Entity, + max_prompt_bytes_input: Entity, active_view: ActiveView, zeta: Entity, _active_editor_subscription: Option, @@ -129,9 +132,10 @@ impl Zeta2Inspector { project: project.clone(), last_prediction: None, active_view: ActiveView::Context, - max_bytes_input: Self::number_input("Max Bytes", window, cx), - min_bytes_input: Self::number_input("Min Bytes", window, cx), + max_excerpt_bytes_input: Self::number_input("Max Excerpt Bytes", window, cx), + min_excerpt_bytes_input: Self::number_input("Min Excerpt Bytes", window, cx), cursor_context_ratio_input: Self::number_input("Cursor Context Ratio", window, cx), + max_prompt_bytes_input: Self::number_input("Max Prompt Bytes", window, cx), zeta: zeta.clone(), _active_editor_subscription: None, _update_state_task: Task::ready(()), @@ -147,10 +151,10 @@ impl Zeta2Inspector { window: &mut Window, cx: &mut Context, ) { - self.max_bytes_input.update(cx, |input, cx| { + self.max_excerpt_bytes_input.update(cx, |input, cx| { input.set_text(options.excerpt.max_bytes.to_string(), window, cx); }); - self.min_bytes_input.update(cx, |input, cx| { + self.min_excerpt_bytes_input.update(cx, |input, cx| { input.set_text(options.excerpt.min_bytes.to_string(), window, cx); }); self.cursor_context_ratio_input.update(cx, |input, cx| { @@ -163,6 +167,9 @@ impl Zeta2Inspector { cx, ); }); + self.max_prompt_bytes_input.update(cx, |input, cx| { + input.set_text(options.max_prompt_bytes.to_string(), window, cx); + }); cx.notify(); } @@ -236,8 +243,8 @@ impl Zeta2Inspector { } let excerpt_options = EditPredictionExcerptOptions { - max_bytes: number_input_value(&this.max_bytes_input, cx), - min_bytes: number_input_value(&this.min_bytes_input, cx), + max_bytes: number_input_value(&this.max_excerpt_bytes_input, cx), + min_bytes: number_input_value(&this.min_excerpt_bytes_input, cx), target_before_cursor_over_total_bytes: number_input_value( &this.cursor_context_ratio_input, cx, @@ -247,7 +254,8 @@ impl Zeta2Inspector { this.set_options( ZetaOptions { excerpt: excerpt_options, - ..this.zeta.read(cx).options().clone() + max_prompt_bytes: number_input_value(&this.max_prompt_bytes_input, cx), + max_diagnostic_bytes: this.zeta.read(cx).options().max_diagnostic_bytes, }, cx, ); @@ -520,16 +528,15 @@ impl Render for Zeta2Inspector { .child( v_flex() .gap_2() - .child( - Headline::new("Excerpt Options").size(HeadlineSize::Small), - ) + .child(Headline::new("Options").size(HeadlineSize::Small)) .child( h_flex() .gap_2() .items_end() - .child(self.max_bytes_input.clone()) - .child(self.min_bytes_input.clone()) + .child(self.max_excerpt_bytes_input.clone()) + .child(self.min_excerpt_bytes_input.clone()) .child(self.cursor_context_ratio_input.clone()) + .child(self.max_prompt_bytes_input.clone()) .child( ui::Button::new("reset-options", "Reset") .disabled( diff --git a/crates/zeta_cli/src/main.rs b/crates/zeta_cli/src/main.rs index 44ed567a5df1dce4862523d0e2f1c04b44cffa94..40380c9ae143f6588c4a23565a25ebd17b6f64c9 100644 --- a/crates/zeta_cli/src/main.rs +++ b/crates/zeta_cli/src/main.rs @@ -63,11 +63,11 @@ struct ContextArgs { #[derive(Debug, Args)] struct Zeta2Args { #[arg(long, default_value_t = 8192)] - prompt_max_bytes: usize, + max_prompt_bytes: usize, #[arg(long, default_value_t = 2048)] - excerpt_max_bytes: usize, + max_excerpt_bytes: usize, #[arg(long, default_value_t = 1024)] - excerpt_min_bytes: usize, + min_excerpt_bytes: usize, #[arg(long, default_value_t = 0.66)] target_before_cursor_over_total_bytes: f32, #[arg(long, default_value_t = 1024)] @@ -225,12 +225,13 @@ async fn get_context( zeta.register_buffer(&buffer, &project, cx); zeta.set_options(zeta2::ZetaOptions { excerpt: EditPredictionExcerptOptions { - max_bytes: zeta2_args.excerpt_max_bytes, - min_bytes: zeta2_args.excerpt_min_bytes, + max_bytes: zeta2_args.max_excerpt_bytes, + min_bytes: zeta2_args.min_excerpt_bytes, target_before_cursor_over_total_bytes: zeta2_args .target_before_cursor_over_total_bytes, }, max_diagnostic_bytes: zeta2_args.max_diagnostic_bytes, + max_prompt_bytes: zeta2_args.max_prompt_bytes, }) }); // TODO: Actually wait for indexing. @@ -246,7 +247,7 @@ async fn get_context( let planned_prompt = cloud_zeta2_prompt::PlannedPrompt::populate( &request, &cloud_zeta2_prompt::PlanOptions { - max_bytes: zeta2_args.prompt_max_bytes, + max_bytes: zeta2_args.max_prompt_bytes, }, )?; anyhow::Ok(planned_prompt.to_prompt_string()) From 52c467ea3aef3c32f617d4053dc9fbffccf2dc07 Mon Sep 17 00:00:00 2001 From: Chris Ewald Date: Tue, 23 Sep 2025 16:26:33 -0400 Subject: [PATCH 225/721] Document task filtering based on variables (#38642) Closes: https://github.com/zed-industries/zed/issues/38525 Documentation follow up for #38614 Task filtering behavior is currently undocumented. Release Notes: - N/A --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Kirill Bulatov --- docs/src/tasks.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index b30b722007116d32435936205971419b5652689d..f2986c9951fe660efdc54e2bb984403cdb6da1cd 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -132,6 +132,27 @@ Or explicitly include escaped quotes like so: } ``` +### Task filtering based on variables + +Task definitions with variables which are not present at the moment the task list is determined are filtered out. +For example, the following task will appear in the spawn modal only if there is a text selection: + +```json +{ + "label": "selected text", + "command": "echo \"$ZED_SELECTED_TEXT\"" +} +``` + +Set default values to such variables to have such tasks always displayed: + +```json +{ + "label": "selected text with default", + "command": "echo \"${ZED_SELECTED_TEXT:no text selected}\"" +} +``` + ## Oneshot tasks The same task modal opened via `task: spawn` supports arbitrary bash-like command execution: type a command inside the modal text field, and use `opt-enter` to spawn it. From 3b79490e8fcff7d4b8fddafa6ee358fa5abb0899 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 23 Sep 2025 16:49:51 -0400 Subject: [PATCH 226/721] Bump Zed to v0.207 (#38741) Release Notes: - N/A --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2e287de62aff60ceeb7ccd313db603c303690d5..29dfdc518a291c37945ed8146f0864cf474aaab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21217,7 +21217,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.206.0" +version = "0.207.0" dependencies = [ "acp_tools", "activity_indicator", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c2d8733b8ae1f98faa7e1a5ba0d1851ba7c9ccc4..e52d3e7b4c8310a1765fc9d39b278b7c9e284d36 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -2,7 +2,7 @@ description = "The fast, collaborative code editor." edition.workspace = true name = "zed" -version = "0.206.0" +version = "0.207.0" publish.workspace = true license = "GPL-3.0-or-later" authors = ["Zed Team "] From 9112554262a3054c279f6a8942696e5ed099a663 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 24 Sep 2025 00:00:55 +0300 Subject: [PATCH 227/721] Clear buffer colors on empty LSP response (#38742) Follow-up of https://github.com/zed-industries/zed/pull/32816 Closes https://github.com/zed-industries/zed/issues/38602 https://github.com/user-attachments/assets/26058c91-4ffd-4c6f-a41d-17da0c3d7220 Release Notes: - Fixed buffer colors not cleared on empty LSP responses --- crates/editor/src/editor_tests.rs | 46 +++++++++++- crates/editor/src/lsp_colors.rs | 112 ++++++++++++++++-------------- 2 files changed, 106 insertions(+), 52 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 9f888702f99b0b916d35625806c18e53043d0101..7aa441b4c7083aa4e39b006bfd03aea47fec23f0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -25701,7 +25701,7 @@ async fn test_document_colors(cx: &mut TestAppContext) { workspace .update(cx, |workspace, window, cx| { workspace.active_pane().update(cx, |pane, cx| { - pane.navigate_backward(&Default::default(), window, cx); + pane.navigate_backward(&workspace::GoBack, window, cx); }) }) .unwrap(); @@ -25729,6 +25729,50 @@ async fn test_document_colors(cx: &mut TestAppContext) { "Should have an initial inlay" ); }); + + drop(color_request_handle); + let closure_requests_made = Arc::clone(&requests_made); + let mut empty_color_request_handle = fake_language_server + .set_request_handler::(move |params, _| { + let requests_made = Arc::clone(&closure_requests_made); + async move { + assert_eq!( + params.text_document.uri, + lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap() + ); + requests_made.fetch_add(1, atomic::Ordering::Release); + Ok(Vec::new()) + } + }); + let save = editor.update_in(cx, |editor, window, cx| { + editor.move_to_end(&MoveToEnd, window, cx); + editor.handle_input("dirty_again", window, cx); + editor.save( + SaveOptions { + format: false, + autosave: true, + }, + project.clone(), + window, + cx, + ) + }); + save.await.unwrap(); + + empty_color_request_handle.next().await.unwrap(); + cx.run_until_parked(); + assert_eq!( + 4, + requests_made.load(atomic::Ordering::Acquire), + "Should query for colors once per save only, as formatting was not requested" + ); + editor.update(cx, |editor, cx| { + assert_eq!( + Vec::::new(), + extract_color_inlays(editor, cx), + "Should clear all colors when the server returns an empty response" + ); + }); } #[gpui::test] diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index 29eb9f249abca71ff3cfbb9f5ad3f56464efe942..4d703d219f88cb10566c9e02faa76cc12408b677 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -228,60 +228,70 @@ impl Editor { }; match colors { Ok(colors) => { - for color in colors.colors { - let color_start = point_from_lsp(color.lsp_range.start); - let color_end = point_from_lsp(color.lsp_range.end); + if colors.colors.is_empty() { + let new_entry = + new_editor_colors.entry(buffer_id).or_insert_with(|| { + (Vec::<(Range, DocumentColor)>::new(), None) + }); + new_entry.0.clear(); + new_entry.1 = colors.cache_version; + } else { + for color in colors.colors { + let color_start = point_from_lsp(color.lsp_range.start); + let color_end = point_from_lsp(color.lsp_range.end); - for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts { - if !excerpt_range.contains(&color_start.0) - || !excerpt_range.contains(&color_end.0) - { - continue; - } - let Some(color_start_anchor) = multi_buffer_snapshot - .anchor_in_excerpt( - *excerpt_id, - buffer_snapshot.anchor_before( - buffer_snapshot - .clip_point_utf16(color_start, Bias::Left), - ), - ) - else { - continue; - }; - let Some(color_end_anchor) = multi_buffer_snapshot - .anchor_in_excerpt( - *excerpt_id, - buffer_snapshot.anchor_after( - buffer_snapshot - .clip_point_utf16(color_end, Bias::Right), - ), - ) - else { - continue; - }; + for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts { + if !excerpt_range.contains(&color_start.0) + || !excerpt_range.contains(&color_end.0) + { + continue; + } + let Some(color_start_anchor) = multi_buffer_snapshot + .anchor_in_excerpt( + *excerpt_id, + buffer_snapshot.anchor_before( + buffer_snapshot + .clip_point_utf16(color_start, Bias::Left), + ), + ) + else { + continue; + }; + let Some(color_end_anchor) = multi_buffer_snapshot + .anchor_in_excerpt( + *excerpt_id, + buffer_snapshot.anchor_after( + buffer_snapshot + .clip_point_utf16(color_end, Bias::Right), + ), + ) + else { + continue; + }; - let new_entry = - new_editor_colors.entry(buffer_id).or_insert_with(|| { - (Vec::<(Range, DocumentColor)>::new(), None) - }); - new_entry.1 = colors.cache_version; - let new_buffer_colors = &mut new_entry.0; + let new_entry = + new_editor_colors.entry(buffer_id).or_insert_with(|| { + (Vec::<(Range, DocumentColor)>::new(), None) + }); + new_entry.1 = colors.cache_version; + let new_buffer_colors = &mut new_entry.0; - let (Ok(i) | Err(i)) = - new_buffer_colors.binary_search_by(|(probe, _)| { - probe - .start - .cmp(&color_start_anchor, &multi_buffer_snapshot) - .then_with(|| { - probe - .end - .cmp(&color_end_anchor, &multi_buffer_snapshot) - }) - }); - new_buffer_colors - .insert(i, (color_start_anchor..color_end_anchor, color)); - break; + let (Ok(i) | Err(i)) = + new_buffer_colors.binary_search_by(|(probe, _)| { + probe + .start + .cmp(&color_start_anchor, &multi_buffer_snapshot) + .then_with(|| { + probe.end.cmp( + &color_end_anchor, + &multi_buffer_snapshot, + ) + }) + }); + new_buffer_colors + .insert(i, (color_start_anchor..color_end_anchor, color)); + break; + } } } } From 74fe3b17f7f4d4356b6000f1f67e7d79ac345563 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 23 Sep 2025 15:18:04 -0600 Subject: [PATCH 228/721] Delete `edit_prediction_tools.rs` (was moved to `zeta2_tools.rs`) (#38745) Move happened in #38718 Release Notes: - N/A --- .../src/edit_prediction_tools.rs | 461 ------------------ 1 file changed, 461 deletions(-) delete mode 100644 crates/edit_prediction_tools/src/edit_prediction_tools.rs diff --git a/crates/edit_prediction_tools/src/edit_prediction_tools.rs b/crates/edit_prediction_tools/src/edit_prediction_tools.rs deleted file mode 100644 index 7e18b468bc6aac5ab53a9ce55f195644e19e9367..0000000000000000000000000000000000000000 --- a/crates/edit_prediction_tools/src/edit_prediction_tools.rs +++ /dev/null @@ -1,461 +0,0 @@ -use std::{ - collections::hash_map::Entry, - ffi::OsStr, - path::{Path, PathBuf}, - str::FromStr, - sync::Arc, - time::{Duration, Instant}, -}; - -use collections::HashMap; -use editor::{Editor, EditorEvent, EditorMode, ExcerptRange, MultiBuffer}; -use gpui::{ - Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, actions, - prelude::*, -}; -use language::{Buffer, DiskState}; -use project::{Project, WorktreeId}; -use text::ToPoint; -use ui::prelude::*; -use ui_input::SingleLineInput; -use workspace::{Item, SplitDirection, Workspace}; - -use edit_prediction_context::{ - EditPredictionContext, EditPredictionExcerptOptions, SnippetStyle, SyntaxIndex, -}; - -actions!( - dev, - [ - /// Opens the language server protocol logs viewer. - OpenEditPredictionContext - ] -); - -pub fn init(cx: &mut App) { - cx.observe_new(move |workspace: &mut Workspace, _, _cx| { - workspace.register_action( - move |workspace, _: &OpenEditPredictionContext, window, cx| { - let workspace_entity = cx.entity(); - let project = workspace.project(); - let active_editor = workspace.active_item_as::(cx); - workspace.split_item( - SplitDirection::Right, - Box::new(cx.new(|cx| { - EditPredictionTools::new( - &workspace_entity, - &project, - active_editor, - window, - cx, - ) - })), - window, - cx, - ); - }, - ); - }) - .detach(); -} - -pub struct EditPredictionTools { - focus_handle: FocusHandle, - project: Entity, - last_context: Option, - max_bytes_input: Entity, - min_bytes_input: Entity, - cursor_context_ratio_input: Entity, - // TODO move to project or provider? - syntax_index: Entity, - last_editor: WeakEntity, - _active_editor_subscription: Option, - _edit_prediction_context_task: Task<()>, -} - -struct ContextState { - context_editor: Entity, - retrieval_duration: Duration, -} - -impl EditPredictionTools { - pub fn new( - workspace: &Entity, - project: &Entity, - active_editor: Option>, - window: &mut Window, - cx: &mut Context, - ) -> Self { - cx.subscribe_in(workspace, window, |this, workspace, event, window, cx| { - if let workspace::Event::ActiveItemChanged = event { - if let Some(editor) = workspace.read(cx).active_item_as::(cx) { - this._active_editor_subscription = Some(cx.subscribe_in( - &editor, - window, - |this, editor, event, window, cx| { - if let EditorEvent::SelectionsChanged { .. } = event { - this.update_context(editor, window, cx); - } - }, - )); - this.update_context(&editor, window, cx); - } else { - this._active_editor_subscription = None; - } - } - }) - .detach(); - let syntax_index = cx.new(|cx| SyntaxIndex::new(project, cx)); - - let number_input = |label: &'static str, - value: &'static str, - window: &mut Window, - cx: &mut Context| - -> Entity { - let input = cx.new(|cx| { - let input = SingleLineInput::new(window, cx, "") - .label(label) - .label_min_width(px(64.)); - input.set_text(value, window, cx); - input - }); - cx.subscribe_in( - &input.read(cx).editor().clone(), - window, - |this, _, event, window, cx| { - if let EditorEvent::BufferEdited = event - && let Some(editor) = this.last_editor.upgrade() - { - this.update_context(&editor, window, cx); - } - }, - ) - .detach(); - input - }; - - let mut this = Self { - focus_handle: cx.focus_handle(), - project: project.clone(), - last_context: None, - max_bytes_input: number_input("Max Bytes", "512", window, cx), - min_bytes_input: number_input("Min Bytes", "128", window, cx), - cursor_context_ratio_input: number_input("Cursor Context Ratio", "0.5", window, cx), - syntax_index, - last_editor: WeakEntity::new_invalid(), - _active_editor_subscription: None, - _edit_prediction_context_task: Task::ready(()), - }; - - if let Some(editor) = active_editor { - this.update_context(&editor, window, cx); - } - - this - } - - fn update_context( - &mut self, - editor: &Entity, - window: &mut Window, - cx: &mut Context, - ) { - self.last_editor = editor.downgrade(); - - let editor = editor.read(cx); - let buffer = editor.buffer().clone(); - let cursor_position = editor.selections.newest_anchor().start; - - let Some(buffer) = buffer.read(cx).buffer_for_anchor(cursor_position, cx) else { - self.last_context.take(); - return; - }; - let current_buffer_snapshot = buffer.read(cx).snapshot(); - let cursor_position = cursor_position - .text_anchor - .to_point(¤t_buffer_snapshot); - - let language = current_buffer_snapshot.language().cloned(); - let Some(worktree_id) = self - .project - .read(cx) - .worktrees(cx) - .next() - .map(|worktree| worktree.read(cx).id()) - else { - log::error!("Open a worktree to use edit prediction debug view"); - self.last_context.take(); - return; - }; - - self._edit_prediction_context_task = cx.spawn_in(window, { - let language_registry = self.project.read(cx).languages().clone(); - async move |this, cx| { - cx.background_executor() - .timer(Duration::from_millis(50)) - .await; - - let mut start_time = None; - - let Ok(task) = this.update(cx, |this, cx| { - fn number_input_value( - input: &Entity, - cx: &App, - ) -> T { - input - .read(cx) - .editor() - .read(cx) - .text(cx) - .parse::() - .unwrap_or_default() - } - - let options = EditPredictionExcerptOptions { - max_bytes: number_input_value(&this.max_bytes_input, cx), - min_bytes: number_input_value(&this.min_bytes_input, cx), - target_before_cursor_over_total_bytes: number_input_value( - &this.cursor_context_ratio_input, - cx, - ), - }; - - start_time = Some(Instant::now()); - - // TODO use global zeta instead - EditPredictionContext::gather_context_in_background( - cursor_position, - current_buffer_snapshot, - options, - Some(this.syntax_index.clone()), - cx, - ) - }) else { - this.update(cx, |this, _cx| { - this.last_context.take(); - }) - .ok(); - return; - }; - - let Some(context) = task.await else { - // TODO: Display message - this.update(cx, |this, _cx| { - this.last_context.take(); - }) - .ok(); - return; - }; - let retrieval_duration = start_time.unwrap().elapsed(); - - let mut languages = HashMap::default(); - for snippet in context.snippets.iter() { - let lang_id = snippet.declaration.identifier().language_id; - if let Entry::Vacant(entry) = languages.entry(lang_id) { - // Most snippets are gonna be the same language, - // so we think it's fine to do this sequentially for now - entry.insert(language_registry.language_for_id(lang_id).await.ok()); - } - } - - this.update_in(cx, |this, window, cx| { - let context_editor = cx.new(|cx| { - let multibuffer = cx.new(|cx| { - let mut multibuffer = MultiBuffer::new(language::Capability::ReadOnly); - let excerpt_file = Arc::new(ExcerptMetadataFile { - title: PathBuf::from("Cursor Excerpt").into(), - worktree_id, - }); - - let excerpt_buffer = cx.new(|cx| { - let mut buffer = Buffer::local(context.excerpt_text.body, cx); - buffer.set_language(language, cx); - buffer.file_updated(excerpt_file, cx); - buffer - }); - - multibuffer.push_excerpts( - excerpt_buffer, - [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], - cx, - ); - - for snippet in context.snippets { - let path = this - .project - .read(cx) - .path_for_entry(snippet.declaration.project_entry_id(), cx); - - let snippet_file = Arc::new(ExcerptMetadataFile { - title: PathBuf::from(format!( - "{} (Score density: {})", - path.map(|p| p.path.to_string_lossy().to_string()) - .unwrap_or_else(|| "".to_string()), - snippet.score_density(SnippetStyle::Declaration) - )) - .into(), - worktree_id, - }); - - let excerpt_buffer = cx.new(|cx| { - let mut buffer = - Buffer::local(snippet.declaration.item_text().0, cx); - buffer.file_updated(snippet_file, cx); - if let Some(language) = - languages.get(&snippet.declaration.identifier().language_id) - { - buffer.set_language(language.clone(), cx); - } - buffer - }); - - multibuffer.push_excerpts( - excerpt_buffer, - [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], - cx, - ); - } - - multibuffer - }); - - Editor::new(EditorMode::full(), multibuffer, None, window, cx) - }); - - this.last_context = Some(ContextState { - context_editor, - retrieval_duration, - }); - cx.notify(); - }) - .ok(); - } - }); - } -} - -impl Focusable for EditPredictionTools { - fn focus_handle(&self, _cx: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Item for EditPredictionTools { - type Event = (); - - fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { - "Edit Prediction Context Debug View".into() - } - - fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { - Some(Icon::new(IconName::ZedPredict)) - } -} - -impl EventEmitter<()> for EditPredictionTools {} - -impl Render for EditPredictionTools { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - v_flex() - .size_full() - .bg(cx.theme().colors().editor_background) - .child( - h_flex() - .items_start() - .w_full() - .child( - v_flex() - .flex_1() - .p_4() - .gap_2() - .child(Headline::new("Excerpt Options").size(HeadlineSize::Small)) - .child( - h_flex() - .gap_2() - .child(self.max_bytes_input.clone()) - .child(self.min_bytes_input.clone()) - .child(self.cursor_context_ratio_input.clone()), - ), - ) - .child(ui::Divider::vertical()) - .when_some(self.last_context.as_ref(), |this, last_context| { - this.child( - v_flex() - .p_4() - .gap_2() - .min_w(px(160.)) - .child(Headline::new("Stats").size(HeadlineSize::Small)) - .child( - h_flex() - .gap_1() - .child( - Label::new("Time to retrieve") - .color(Color::Muted) - .size(LabelSize::Small), - ) - .child( - Label::new( - if last_context.retrieval_duration.as_micros() - > 1000 - { - format!( - "{} ms", - last_context.retrieval_duration.as_millis() - ) - } else { - format!( - "{} µs", - last_context.retrieval_duration.as_micros() - ) - }, - ) - .size(LabelSize::Small), - ), - ), - ) - }), - ) - .children(self.last_context.as_ref().map(|c| c.context_editor.clone())) - } -} - -// Using same approach as commit view - -struct ExcerptMetadataFile { - title: Arc, - worktree_id: WorktreeId, -} - -impl language::File for ExcerptMetadataFile { - fn as_local(&self) -> Option<&dyn language::LocalFile> { - None - } - - fn disk_state(&self) -> DiskState { - DiskState::New - } - - fn path(&self) -> &Arc { - &self.title - } - - fn full_path(&self, _: &App) -> PathBuf { - self.title.as_ref().into() - } - - fn file_name<'a>(&'a self, _: &'a App) -> &'a OsStr { - self.title.file_name().unwrap() - } - - fn worktree_id(&self, _: &App) -> WorktreeId { - self.worktree_id - } - - fn to_proto(&self, _: &App) -> language::proto::File { - unimplemented!() - } - - fn is_private(&self) -> bool { - false - } -} From 28ed08340c329ef070b0fd9c11c0562e26cd9681 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 23 Sep 2025 17:40:22 -0400 Subject: [PATCH 229/721] Remove experimental jj UI, for now (#38743) This PR removes the experimental jj bookmark picker that was added in #30883. This was just an exploratory prototype and while I would like to have native jj UI at some point, I don't know when we'll get back to it. Release Notes: - N/A --- Cargo.lock | 1180 ++------------------------- Cargo.toml | 5 - crates/feature_flags/src/flags.rs | 6 - crates/jj/Cargo.toml | 18 - crates/jj/LICENSE-GPL | 1 - crates/jj/src/jj.rs | 5 - crates/jj/src/jj_repository.rs | 69 -- crates/jj/src/jj_store.rs | 41 - crates/jj_ui/Cargo.toml | 25 - crates/jj_ui/LICENSE-GPL | 1 - crates/jj_ui/src/bookmark_picker.rs | 198 ----- crates/jj_ui/src/jj_ui.rs | 39 - crates/zed/Cargo.toml | 1 - crates/zed/src/main.rs | 1 - crates/zed/src/zed.rs | 1 - crates/zed_actions/src/lib.rs | 12 - tooling/workspace-hack/Cargo.toml | 44 +- 17 files changed, 78 insertions(+), 1569 deletions(-) delete mode 100644 crates/jj/Cargo.toml delete mode 120000 crates/jj/LICENSE-GPL delete mode 100644 crates/jj/src/jj.rs delete mode 100644 crates/jj/src/jj_repository.rs delete mode 100644 crates/jj/src/jj_store.rs delete mode 100644 crates/jj_ui/Cargo.toml delete mode 120000 crates/jj_ui/LICENSE-GPL delete mode 100644 crates/jj_ui/src/bookmark_picker.rs delete mode 100644 crates/jj_ui/src/jj_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 29dfdc518a291c37945ed8146f0864cf474aaab1..793c092e71a9e3717841832452e8758fde24064f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -699,12 +699,6 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -2157,12 +2151,6 @@ dependencies = [ "workspace-hack", ] -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bigdecimal" version = "0.4.8" @@ -2370,15 +2358,6 @@ dependencies = [ "profiling", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - [[package]] name = "block" version = "0.1.6" @@ -3240,12 +3219,6 @@ dependencies = [ "workspace-hack", ] -[[package]] -name = "clru" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" - [[package]] name = "cmake" version = "0.1.54" @@ -5857,25 +5830,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" -[[package]] -name = "faster-hex" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" -dependencies = [ - "serde", -] - -[[package]] -name = "faster-hex" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" -dependencies = [ - "heapless", - "serde", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -6026,7 +5980,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", ] @@ -6900,817 +6853,57 @@ dependencies = [ name = "git_ui" version = "0.1.0" dependencies = [ - "agent_settings", - "anyhow", - "askpass", - "buffer_diff", - "call", - "chrono", - "cloud_llm_client", - "collections", - "command_palette_hooks", - "component", - "ctor", - "db", - "editor", - "futures 0.3.31", - "fuzzy", - "git", - "gpui", - "indoc", - "itertools 0.14.0", - "language", - "language_model", - "linkify", - "log", - "markdown", - "menu", - "multi_buffer", - "notifications", - "panel", - "picker", - "postage", - "pretty_assertions", - "project", - "schemars 1.0.1", - "serde", - "serde_json", - "settings", - "strum 0.27.1", - "telemetry", - "theme", - "time", - "time_format", - "ui", - "unindent", - "util", - "watch", - "windows 0.61.1", - "workspace", - "workspace-hack", - "zed_actions", - "zeroize", - "zlog", -] - -[[package]] -name = "gix" -version = "0.71.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a61e71ec6817fc3c9f12f812682cfe51ee6ea0d2e27e02fc3849c35524617435" -dependencies = [ - "gix-actor", - "gix-attributes", - "gix-command", - "gix-commitgraph", - "gix-config", - "gix-date", - "gix-diff", - "gix-discover", - "gix-features 0.41.1", - "gix-filter", - "gix-fs 0.14.0", - "gix-glob", - "gix-hash 0.17.0", - "gix-hashtable", - "gix-ignore", - "gix-index", - "gix-lock", - "gix-object", - "gix-odb", - "gix-pack", - "gix-path", - "gix-pathspec", - "gix-protocol", - "gix-ref", - "gix-refspec", - "gix-revision", - "gix-revwalk", - "gix-sec", - "gix-shallow", - "gix-submodule", - "gix-tempfile", - "gix-trace", - "gix-traverse", - "gix-url", - "gix-utils 0.2.0", - "gix-validate 0.9.4", - "gix-worktree", - "once_cell", - "smallvec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-actor" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f438c87d4028aca4b82f82ba8d8ab1569823cfb3e5bc5fa8456a71678b2a20e7" -dependencies = [ - "bstr", - "gix-date", - "gix-utils 0.2.0", - "itoa", - "thiserror 2.0.12", - "winnow", -] - -[[package]] -name = "gix-attributes" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e25825e0430aa11096f8b65ced6780d4a96a133f81904edceebb5344c8dd7f" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-quote", - "gix-trace", - "kstring", - "smallvec", - "thiserror 2.0.12", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" -dependencies = [ - "thiserror 2.0.12", -] - -[[package]] -name = "gix-chunk" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" -dependencies = [ - "thiserror 2.0.12", -] - -[[package]] -name = "gix-command" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0378995847773a697f8e157fe2963ecf3462fe64be05b7b3da000b3b472def8" -dependencies = [ - "bstr", - "gix-path", - "gix-quote", - "gix-trace", - "shell-words", -] - -[[package]] -name = "gix-commitgraph" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043cbe49b7a7505150db975f3cb7c15833335ac1e26781f615454d9d640a28fe" -dependencies = [ - "bstr", - "gix-chunk", - "gix-hash 0.17.0", - "memmap2", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-config" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6f830bf746604940261b49abf7f655d2c19cadc9f4142ae9379e3a316e8cfa" -dependencies = [ - "bstr", - "gix-config-value", - "gix-features 0.41.1", - "gix-glob", - "gix-path", - "gix-ref", - "gix-sec", - "memchr", - "once_cell", - "smallvec", - "thiserror 2.0.12", - "unicode-bom", - "winnow", -] - -[[package]] -name = "gix-config-value" -version = "0.14.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6" -dependencies = [ - "bitflags 2.9.0", - "bstr", - "gix-path", - "libc", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-date" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa30058ec7d3511fbc229e4f9e696a35abd07ec5b82e635eff864a2726217e4" -dependencies = [ - "bstr", - "itoa", - "jiff", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-diff" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2c975dad2afc85e4e233f444d1efbe436c3cdcf3a07173984509c436d00a3f8" -dependencies = [ - "bstr", - "gix-command", - "gix-filter", - "gix-fs 0.14.0", - "gix-hash 0.17.0", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-trace", - "gix-traverse", - "gix-worktree", - "imara-diff", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-discover" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fb8a4349b854506a3915de18d3341e5f1daa6b489c8affc9ca0d69efe86781" -dependencies = [ - "bstr", - "dunce", - "gix-fs 0.14.0", - "gix-hash 0.17.0", - "gix-path", - "gix-ref", - "gix-sec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-features" -version = "0.41.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016d6050219458d14520fe22bdfdeb9cb71631dec9bc2724767c983f60109634" -dependencies = [ - "crc32fast", - "crossbeam-channel", - "flate2", - "gix-path", - "gix-trace", - "gix-utils 0.2.0", - "libc", - "once_cell", - "parking_lot", - "prodash", - "thiserror 2.0.12", - "walkdir", -] - -[[package]] -name = "gix-features" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" -dependencies = [ - "gix-trace", - "gix-utils 0.3.0", - "libc", - "prodash", -] - -[[package]] -name = "gix-filter" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb2b2bbffdc5cc9b2b82fc82da1b98163c9b423ac2b45348baa83a947ac9ab89" -dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes", - "gix-command", - "gix-hash 0.17.0", - "gix-object", - "gix-packetline-blocking", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils 0.2.0", - "smallvec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-fs" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951e886120dc5fa8cac053e5e5c89443f12368ca36811b2e43d1539081f9c111" -dependencies = [ - "bstr", - "fastrand 2.3.0", - "gix-features 0.41.1", - "gix-path", - "gix-utils 0.2.0", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-fs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" -dependencies = [ - "bstr", - "fastrand 2.3.0", - "gix-features 0.42.1", - "gix-path", - "gix-utils 0.3.0", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-glob" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20972499c03473e773a2099e5fd0c695b9b72465837797a51a43391a1635a030" -dependencies = [ - "bitflags 2.9.0", - "bstr", - "gix-features 0.41.1", - "gix-path", -] - -[[package]] -name = "gix-hash" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834e79722063958b03342edaa1e17595cd2939bb2b3306b3225d0815566dcb49" -dependencies = [ - "faster-hex 0.9.0", - "gix-features 0.41.1", - "sha1-checked", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-hash" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" -dependencies = [ - "faster-hex 0.10.0", - "gix-features 0.42.1", - "sha1-checked", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-hashtable" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" -dependencies = [ - "gix-hash 0.18.0", - "hashbrown 0.14.5", - "parking_lot", -] - -[[package]] -name = "gix-ignore" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a27c8380f493a10d1457f756a3f81924d578fc08d6535e304dfcafbf0261d18" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-trace", - "unicode-bom", -] - -[[package]] -name = "gix-index" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "855bece2d4153453aa5d0a80d51deea1ce8cd6a3b4cf213da85ac344ccb908a7" -dependencies = [ - "bitflags 2.9.0", - "bstr", - "filetime", - "fnv", - "gix-bitmap", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-hash 0.17.0", - "gix-lock", - "gix-object", - "gix-traverse", - "gix-utils 0.2.0", - "gix-validate 0.9.4", - "hashbrown 0.14.5", - "itoa", - "libc", - "memmap2", - "rustix 0.38.44", - "smallvec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-lock" -version = "17.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" -dependencies = [ - "gix-tempfile", - "gix-utils 0.3.0", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-object" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4943fcdae6ffc135920c9ea71e0362ed539182924ab7a85dd9dac8d89b0dd69a" -dependencies = [ - "bstr", - "gix-actor", - "gix-date", - "gix-features 0.41.1", - "gix-hash 0.17.0", - "gix-hashtable", - "gix-path", - "gix-utils 0.2.0", - "gix-validate 0.9.4", - "itoa", - "smallvec", - "thiserror 2.0.12", - "winnow", -] - -[[package]] -name = "gix-odb" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50306d40dcc982eb6b7593103f066ea6289c7b094cb9db14f3cd2be0b9f5e610" -dependencies = [ - "arc-swap", - "gix-date", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-hash 0.17.0", - "gix-hashtable", - "gix-object", - "gix-pack", - "gix-path", - "gix-quote", - "parking_lot", - "tempfile", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-pack" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b65fffb09393c26624ca408d32cfe8776fb94cd0a5cdf984905e1d2f39779cb" -dependencies = [ - "clru", - "gix-chunk", - "gix-features 0.41.1", - "gix-hash 0.17.0", - "gix-hashtable", - "gix-object", - "gix-path", - "memmap2", - "smallvec", - "thiserror 2.0.12", - "uluru", -] - -[[package]] -name = "gix-packetline" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123844a70cf4d5352441dc06bab0da8aef61be94ec239cb631e0ba01dc6d3a04" -dependencies = [ - "bstr", - "faster-hex 0.9.0", - "gix-trace", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-packetline-blocking" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecf3ea2e105c7e45587bac04099824301262a6c43357fad5205da36dbb233b3" -dependencies = [ - "bstr", - "faster-hex 0.9.0", - "gix-trace", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-path" -version = "0.10.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567f65fec4ef10dfab97ae71f26a27fd4d7fe7b8e3f90c8a58551c41ff3fb65b" -dependencies = [ - "bstr", - "gix-trace", - "gix-validate 0.10.0", - "home", - "once_cell", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-pathspec" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8422c3c9066d649074b24025125963f85232bfad32d6d16aea9453b82ec14" -dependencies = [ - "bitflags 2.9.0", - "bstr", - "gix-attributes", - "gix-config-value", - "gix-glob", - "gix-path", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-protocol" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5678ddae1d62880bc30e2200be1b9387af3372e0e88e21f81b4e7f8367355b5a" -dependencies = [ - "bstr", - "gix-date", - "gix-features 0.41.1", - "gix-hash 0.17.0", - "gix-ref", - "gix-shallow", - "gix-transport", - "gix-utils 0.2.0", - "maybe-async", - "thiserror 2.0.12", - "winnow", -] - -[[package]] -name = "gix-quote" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b005c550bf84de3b24aa5e540a23e6146a1c01c7d30470e35d75a12f827f969" -dependencies = [ - "bstr", - "gix-utils 0.2.0", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-ref" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e1f7eb6b7ce82d2d19961f74bd637bab3ea79b1bc7bfb23dbefc67b0415d8b" -dependencies = [ - "gix-actor", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-hash 0.17.0", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils 0.2.0", - "gix-validate 0.9.4", - "memmap2", - "thiserror 2.0.12", - "winnow", -] - -[[package]] -name = "gix-refspec" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8587b21e2264a6e8938d940c5c99662779c13a10741a5737b15fc85c252ffc" -dependencies = [ - "bstr", - "gix-hash 0.17.0", - "gix-revision", - "gix-validate 0.9.4", - "smallvec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-revision" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342caa4e158df3020cadf62f656307c3948fe4eacfdf67171d7212811860c3e9" -dependencies = [ - "bstr", - "gix-commitgraph", - "gix-date", - "gix-hash 0.17.0", - "gix-object", - "gix-revwalk", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-revwalk" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc7c3d7e5cdc1ab8d35130106e4af0a4f9f9eca0c81f4312b690780e92bde0d" -dependencies = [ - "gix-commitgraph", - "gix-date", - "gix-hash 0.17.0", - "gix-hashtable", - "gix-object", - "smallvec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-sec" -version = "0.10.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888" -dependencies = [ - "bitflags 2.9.0", - "gix-path", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "gix-shallow" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0598aacfe1d52575a21c9492fee086edbb21e228ec36c819c42ab923f434c3" -dependencies = [ - "bstr", - "gix-hash 0.17.0", - "gix-lock", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-submodule" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c7390c2059505c365e9548016d4edc9f35749c6a9112b7b1214400bbc68da2" -dependencies = [ - "bstr", - "gix-config", - "gix-path", - "gix-pathspec", - "gix-refspec", - "gix-url", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-tempfile" -version = "17.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" -dependencies = [ - "dashmap 6.1.0", - "gix-fs 0.15.0", - "libc", - "once_cell", - "parking_lot", - "tempfile", -] - -[[package]] -name = "gix-trace" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" - -[[package]] -name = "gix-transport" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f68c2870bfca8278389d2484a7f2215b67d0b0cc5277d3c72ad72acf41787e" -dependencies = [ - "bstr", - "gix-command", - "gix-features 0.41.1", - "gix-packetline", - "gix-quote", - "gix-sec", - "gix-url", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-traverse" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c0b049f8bdb61b20016694102f7b507f2e1727e83e9c5e6dad4f7d84ff7384" -dependencies = [ - "bitflags 2.9.0", - "gix-commitgraph", - "gix-date", - "gix-hash 0.17.0", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-url" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dfe23f93f1ddb84977d80bb0dd7aa09d1bf5d5afc0c9b6820cccacc25ae860" -dependencies = [ - "bstr", - "gix-features 0.41.1", - "gix-path", - "percent-encoding", - "thiserror 2.0.12", - "url", -] - -[[package]] -name = "gix-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189f8724cf903e7fd57cfe0b7bc209db255cacdcb22c781a022f52c3a774f8d0" -dependencies = [ - "fastrand 2.3.0", - "unicode-normalization", -] - -[[package]] -name = "gix-utils" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" -dependencies = [ - "fastrand 2.3.0", - "unicode-normalization", -] - -[[package]] -name = "gix-validate" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084" -dependencies = [ - "bstr", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-validate" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" -dependencies = [ - "bstr", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-worktree" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7760dbc4b79aa274fed30adc0d41dca6b917641f26e7867c4071b1fb4dc727b" -dependencies = [ - "bstr", - "gix-attributes", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-glob", - "gix-hash 0.17.0", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "gix-validate 0.9.4", + "agent_settings", + "anyhow", + "askpass", + "buffer_diff", + "call", + "chrono", + "cloud_llm_client", + "collections", + "command_palette_hooks", + "component", + "ctor", + "db", + "editor", + "futures 0.3.31", + "fuzzy", + "git", + "gpui", + "indoc", + "itertools 0.14.0", + "language", + "language_model", + "linkify", + "log", + "markdown", + "menu", + "multi_buffer", + "notifications", + "panel", + "picker", + "postage", + "pretty_assertions", + "project", + "schemars 1.0.1", + "serde", + "serde_json", + "settings", + "strum 0.27.1", + "telemetry", + "theme", + "time", + "time_format", + "ui", + "unindent", + "util", + "watch", + "windows 0.61.1", + "workspace", + "workspace-hack", + "zed_actions", + "zeroize", + "zlog", ] [[package]] @@ -8056,15 +7249,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -8138,16 +7322,6 @@ dependencies = [ "http 0.2.12", ] -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.3.3" @@ -8942,16 +8116,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "interim" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ce9099a85f468663d3225bf87e85d0548968441e1db12248b996b24f0f5b5a" -dependencies = [ - "chrono", - "logos", -] - [[package]] name = "interpolate_name" version = "0.2.4" @@ -9129,12 +8293,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" dependencies = [ "jiff-static", - "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", ] [[package]] @@ -9148,106 +8310,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "jiff-tzdb" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - -[[package]] -name = "jj" -version = "0.1.0" -dependencies = [ - "anyhow", - "gpui", - "jj-lib", - "workspace-hack", -] - -[[package]] -name = "jj-lib" -version = "0.29.0" -source = "git+https://github.com/jj-vcs/jj?rev=e18eb8e05efaa153fad5ef46576af145bba1807f#e18eb8e05efaa153fad5ef46576af145bba1807f" -dependencies = [ - "async-trait", - "blake2", - "bstr", - "chrono", - "clru", - "digest", - "dunce", - "either", - "futures 0.3.31", - "gix", - "glob", - "hashbrown 0.15.3", - "hex", - "ignore", - "indexmap 2.9.0", - "interim", - "itertools 0.14.0", - "jj-lib-proc-macros", - "maplit", - "once_cell", - "pest", - "pest_derive", - "pollster 0.4.0", - "prost 0.13.5", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rayon", - "ref-cast", - "regex", - "rustix 1.0.7", - "same-file", - "serde", - "serde_json", - "smallvec", - "strsim", - "tempfile", - "thiserror 2.0.12", - "toml_edit", - "tracing", - "version_check", - "winreg 0.52.0", -] - -[[package]] -name = "jj-lib-proc-macros" -version = "0.29.0" -source = "git+https://github.com/jj-vcs/jj?rev=e18eb8e05efaa153fad5ef46576af145bba1807f#e18eb8e05efaa153fad5ef46576af145bba1807f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - -[[package]] -name = "jj_ui" -version = "0.1.0" -dependencies = [ - "command_palette_hooks", - "feature_flags", - "fuzzy", - "gpui", - "jj", - "picker", - "ui", - "util", - "workspace", - "workspace-hack", - "zed_actions", -] - [[package]] name = "jni" version = "0.21.1" @@ -9465,15 +8527,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kstring" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" -dependencies = [ - "static_assertions", -] - [[package]] name = "kurbo" version = "0.11.1" @@ -9958,15 +9011,6 @@ dependencies = [ "webrtc-sys", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a" -dependencies = [ - "zlib-rs", -] - [[package]] name = "libz-sys" version = "1.1.22" @@ -10203,40 +9247,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "logos" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-codegen" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e" -dependencies = [ - "beef", - "fnv", - "lazy_static", - "proc-macro2", - "quote", - "regex-syntax", - "rustc_version", - "syn 2.0.101", -] - -[[package]] -name = "logos-derive" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba" -dependencies = [ - "logos-codegen", -] - [[package]] name = "loom" version = "0.7.2" @@ -10518,17 +9528,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "maybe-owned" version = "0.3.4" @@ -12841,12 +11840,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" -[[package]] -name = "pollster" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" - [[package]] name = "portable-atomic" version = "1.11.0" @@ -12895,7 +11888,7 @@ dependencies = [ "log", "parking_lot", "pin-project", - "pollster 0.2.5", + "pollster", "static_assertions", "thiserror 1.0.69", ] @@ -13045,16 +12038,6 @@ dependencies = [ "hex", ] -[[package]] -name = "prodash" -version = "29.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" -dependencies = [ - "log", - "parking_lot", -] - [[package]] name = "profiling" version = "1.0.16" @@ -13270,16 +12253,6 @@ dependencies = [ "prost-derive 0.12.6", ] -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes 1.10.1", - "prost-derive 0.13.5", -] - [[package]] name = "prost-build" version = "0.9.0" @@ -13347,19 +12320,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "prost-types" version = "0.9.0" @@ -15553,16 +14513,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha1-checked" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" -dependencies = [ - "digest", - "sha1", -] - [[package]] name = "sha1_smol" version = "1.0.1" @@ -18341,15 +17291,6 @@ dependencies = [ "workspace-hack", ] -[[package]] -name = "uluru" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" -dependencies = [ - "arrayvec", -] - [[package]] name = "unicase" version = "2.8.1" @@ -18374,12 +17315,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" -[[package]] -name = "unicode-bom" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" - [[package]] name = "unicode-ccc" version = "0.2.0" @@ -20736,7 +19671,6 @@ dependencies = [ "inout", "itertools 0.12.1", "itertools 0.13.0", - "jiff", "lazy_static", "libc", "libsqlite3-sys", @@ -20805,7 +19739,6 @@ dependencies = [ "serde", "serde_core", "serde_json", - "sha1", "simd-adler32", "smallvec", "spin", @@ -20834,7 +19767,6 @@ dependencies = [ "tracing", "tracing-core", "tungstenite 0.26.2", - "unicode-normalization", "unicode-properties", "url", "uuid", @@ -20849,7 +19781,6 @@ dependencies = [ "windows-sys 0.52.0", "windows-sys 0.59.0", "windows-sys 0.61.0", - "winnow", "zeroize", "zvariant", ] @@ -21275,7 +20206,6 @@ dependencies = [ "inspector_ui", "install_cli", "itertools 0.14.0", - "jj_ui", "journal", "keymap_editor", "language", @@ -21775,12 +20705,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "zlib-rs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8" - [[package]] name = "zlog" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f49a0374bbe2f4be99d52c755b3438f7f6e31217..a06ba76326e1b3c6482507119c824f143e26065a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,8 +90,6 @@ members = [ "crates/image_viewer", "crates/inspector_ui", "crates/install_cli", - "crates/jj", - "crates/jj_ui", "crates/journal", "crates/keymap_editor", "crates/language", @@ -322,8 +320,6 @@ edit_prediction_context = { path = "crates/edit_prediction_context" } zeta2_tools = { path = "crates/zeta2_tools" } inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } -jj = { path = "crates/jj" } -jj_ui = { path = "crates/jj_ui" } journal = { path = "crates/journal" } keymap_editor = { path = "crates/keymap_editor" } language = { path = "crates/language" } @@ -529,7 +525,6 @@ indexmap = { version = "2.7.0", features = ["serde"] } indoc = "2" inventory = "0.3.19" itertools = "0.14.0" -jj-lib = { git = "https://github.com/jj-vcs/jj", rev = "e18eb8e05efaa153fad5ef46576af145bba1807f" } json_dotpath = "1.1" jsonschema = "0.30.0" jsonwebtoken = "9.3" diff --git a/crates/feature_flags/src/flags.rs b/crates/feature_flags/src/flags.rs index d8c8bec873699328b9d2740c97ffc97ffe190429..bacb22bd8db7aa5ec3d494a6188e097fce9230fa 100644 --- a/crates/feature_flags/src/flags.rs +++ b/crates/feature_flags/src/flags.rs @@ -27,9 +27,3 @@ pub struct PanicFeatureFlag; impl FeatureFlag for PanicFeatureFlag { const NAME: &'static str = "panic"; } - -pub struct JjUiFeatureFlag {} - -impl FeatureFlag for JjUiFeatureFlag { - const NAME: &'static str = "jj-ui"; -} diff --git a/crates/jj/Cargo.toml b/crates/jj/Cargo.toml deleted file mode 100644 index 3bce6b2b47d66be82a5ba7c7c08d1ead7bbc765b..0000000000000000000000000000000000000000 --- a/crates/jj/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "jj" -version = "0.1.0" -publish.workspace = true -edition.workspace = true -license = "GPL-3.0-or-later" - -[lints] -workspace = true - -[lib] -path = "src/jj.rs" - -[dependencies] -anyhow.workspace = true -gpui.workspace = true -jj-lib.workspace = true -workspace-hack.workspace = true diff --git a/crates/jj/LICENSE-GPL b/crates/jj/LICENSE-GPL deleted file mode 120000 index 89e542f750cd3860a0598eff0dc34b56d7336dc4..0000000000000000000000000000000000000000 --- a/crates/jj/LICENSE-GPL +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-GPL \ No newline at end of file diff --git a/crates/jj/src/jj.rs b/crates/jj/src/jj.rs deleted file mode 100644 index 45fa2b07e14e8bcfb3693df0f88405a5f52516e8..0000000000000000000000000000000000000000 --- a/crates/jj/src/jj.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod jj_repository; -mod jj_store; - -pub use jj_repository::*; -pub use jj_store::*; diff --git a/crates/jj/src/jj_repository.rs b/crates/jj/src/jj_repository.rs deleted file mode 100644 index afbe54c99dcb40a039e8f7cc87c14dc393ebac3a..0000000000000000000000000000000000000000 --- a/crates/jj/src/jj_repository.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::path::Path; -use std::sync::Arc; - -use anyhow::Result; -use gpui::SharedString; -use jj_lib::config::StackedConfig; -use jj_lib::repo::StoreFactories; -use jj_lib::settings::UserSettings; -use jj_lib::workspace::{self, DefaultWorkspaceLoaderFactory, WorkspaceLoaderFactory}; - -#[derive(Debug, Clone)] -pub struct Bookmark { - pub ref_name: SharedString, -} - -pub trait JujutsuRepository: Send + Sync { - fn list_bookmarks(&self) -> Vec; -} - -pub struct RealJujutsuRepository { - repository: Arc, -} - -impl RealJujutsuRepository { - pub fn new(cwd: &Path) -> Result { - let workspace_loader_factory = DefaultWorkspaceLoaderFactory; - let workspace_loader = workspace_loader_factory.create(Self::find_workspace_dir(cwd))?; - - let config = StackedConfig::with_defaults(); - let settings = UserSettings::from_config(config)?; - - let workspace = workspace_loader.load( - &settings, - &StoreFactories::default(), - &workspace::default_working_copy_factories(), - )?; - - let repo_loader = workspace.repo_loader(); - let repository = repo_loader.load_at_head()?; - - Ok(Self { repository }) - } - - fn find_workspace_dir(cwd: &Path) -> &Path { - cwd.ancestors() - .find(|path| path.join(".jj").is_dir()) - .unwrap_or(cwd) - } -} - -impl JujutsuRepository for RealJujutsuRepository { - fn list_bookmarks(&self) -> Vec { - self.repository - .view() - .bookmarks() - .map(|(ref_name, _target)| Bookmark { - ref_name: ref_name.as_str().to_string().into(), - }) - .collect() - } -} - -pub struct FakeJujutsuRepository {} - -impl JujutsuRepository for FakeJujutsuRepository { - fn list_bookmarks(&self) -> Vec { - Vec::new() - } -} diff --git a/crates/jj/src/jj_store.rs b/crates/jj/src/jj_store.rs deleted file mode 100644 index 2d2d958d7f964cdfc7723827fb2241e50d172697..0000000000000000000000000000000000000000 --- a/crates/jj/src/jj_store.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::path::Path; -use std::sync::Arc; - -use gpui::{App, Entity, Global, prelude::*}; - -use crate::{JujutsuRepository, RealJujutsuRepository}; - -/// Note: We won't ultimately be storing the jj store in a global, we're just doing this for exploration purposes. -struct GlobalJujutsuStore(Entity); - -impl Global for GlobalJujutsuStore {} - -pub struct JujutsuStore { - repository: Arc, -} - -impl JujutsuStore { - pub fn init_global(cx: &mut App) { - let Some(repository) = RealJujutsuRepository::new(Path::new(".")).ok() else { - return; - }; - - let repository = Arc::new(repository); - let jj_store = cx.new(|cx| JujutsuStore::new(repository, cx)); - - cx.set_global(GlobalJujutsuStore(jj_store)); - } - - pub fn try_global(cx: &App) -> Option> { - cx.try_global::() - .map(|global| global.0.clone()) - } - - pub fn new(repository: Arc, _cx: &mut Context) -> Self { - Self { repository } - } - - pub fn repository(&self) -> &Arc { - &self.repository - } -} diff --git a/crates/jj_ui/Cargo.toml b/crates/jj_ui/Cargo.toml deleted file mode 100644 index 34dac76db11bf9cae6a4277ae8fff58d073e19be..0000000000000000000000000000000000000000 --- a/crates/jj_ui/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "jj_ui" -version = "0.1.0" -publish.workspace = true -edition.workspace = true -license = "GPL-3.0-or-later" - -[lints] -workspace = true - -[lib] -path = "src/jj_ui.rs" - -[dependencies] -command_palette_hooks.workspace = true -feature_flags.workspace = true -fuzzy.workspace = true -gpui.workspace = true -jj.workspace = true -picker.workspace = true -ui.workspace = true -util.workspace = true -workspace-hack.workspace = true -workspace.workspace = true -zed_actions.workspace = true diff --git a/crates/jj_ui/LICENSE-GPL b/crates/jj_ui/LICENSE-GPL deleted file mode 120000 index 89e542f750cd3860a0598eff0dc34b56d7336dc4..0000000000000000000000000000000000000000 --- a/crates/jj_ui/LICENSE-GPL +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-GPL \ No newline at end of file diff --git a/crates/jj_ui/src/bookmark_picker.rs b/crates/jj_ui/src/bookmark_picker.rs deleted file mode 100644 index 95c23e73f56059c92397222a132700351c710147..0000000000000000000000000000000000000000 --- a/crates/jj_ui/src/bookmark_picker.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::sync::Arc; - -use fuzzy::{StringMatchCandidate, match_strings}; -use gpui::{ - App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity, Window, - prelude::*, -}; -use jj::{Bookmark, JujutsuStore}; -use picker::{Picker, PickerDelegate}; -use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*}; -use util::ResultExt as _; -use workspace::{ModalView, Workspace}; - -pub fn register(workspace: &mut Workspace) { - workspace.register_action(open); -} - -fn open( - workspace: &mut Workspace, - _: &zed_actions::jj::BookmarkList, - window: &mut Window, - cx: &mut Context, -) { - let Some(jj_store) = JujutsuStore::try_global(cx) else { - return; - }; - - workspace.toggle_modal(window, cx, |window, cx| { - let delegate = BookmarkPickerDelegate::new(cx.entity().downgrade(), jj_store, cx); - BookmarkPicker::new(delegate, window, cx) - }); -} - -pub struct BookmarkPicker { - picker: Entity>, -} - -impl BookmarkPicker { - pub fn new( - delegate: BookmarkPickerDelegate, - window: &mut Window, - cx: &mut Context, - ) -> Self { - let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx)); - Self { picker } - } -} - -impl ModalView for BookmarkPicker {} - -impl EventEmitter for BookmarkPicker {} - -impl Focusable for BookmarkPicker { - fn focus_handle(&self, cx: &App) -> FocusHandle { - self.picker.focus_handle(cx) - } -} - -impl Render for BookmarkPicker { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - v_flex().w(rems(34.)).child(self.picker.clone()) - } -} - -#[derive(Debug, Clone)] -struct BookmarkEntry { - bookmark: Bookmark, - positions: Vec, -} - -pub struct BookmarkPickerDelegate { - picker: WeakEntity, - matches: Vec, - all_bookmarks: Vec, - selected_index: usize, -} - -impl BookmarkPickerDelegate { - fn new( - picker: WeakEntity, - jj_store: Entity, - cx: &mut Context, - ) -> Self { - let bookmarks = jj_store.read(cx).repository().list_bookmarks(); - - Self { - picker, - matches: Vec::new(), - all_bookmarks: bookmarks, - selected_index: 0, - } - } -} - -impl PickerDelegate for BookmarkPickerDelegate { - type ListItem = ListItem; - - fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { - "Select Bookmark…".into() - } - - fn match_count(&self) -> usize { - self.matches.len() - } - - fn selected_index(&self) -> usize { - self.selected_index - } - - fn set_selected_index( - &mut self, - ix: usize, - _window: &mut Window, - _cx: &mut Context>, - ) { - self.selected_index = ix; - } - - fn update_matches( - &mut self, - query: String, - window: &mut Window, - cx: &mut Context>, - ) -> Task<()> { - let background = cx.background_executor().clone(); - let all_bookmarks = self.all_bookmarks.clone(); - - cx.spawn_in(window, async move |this, cx| { - let matches = if query.is_empty() { - all_bookmarks - .into_iter() - .map(|bookmark| BookmarkEntry { - bookmark, - positions: Vec::new(), - }) - .collect() - } else { - let candidates = all_bookmarks - .iter() - .enumerate() - .map(|(ix, bookmark)| StringMatchCandidate::new(ix, &bookmark.ref_name)) - .collect::>(); - match_strings( - &candidates, - &query, - false, - true, - 100, - &Default::default(), - background, - ) - .await - .into_iter() - .map(|mat| BookmarkEntry { - bookmark: all_bookmarks[mat.candidate_id].clone(), - positions: mat.positions, - }) - .collect() - }; - - this.update(cx, |this, _cx| { - this.delegate.matches = matches; - }) - .log_err(); - }) - } - - fn confirm(&mut self, _secondary: bool, _window: &mut Window, _cx: &mut Context>) { - // - } - - fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { - self.picker - .update(cx, |_, cx| cx.emit(DismissEvent)) - .log_err(); - } - - fn render_match( - &self, - ix: usize, - selected: bool, - _window: &mut Window, - _cx: &mut Context>, - ) -> Option { - let entry = &self.matches.get(ix)?; - - Some( - ListItem::new(ix) - .inset(true) - .spacing(ListItemSpacing::Sparse) - .toggle_state(selected) - .child(HighlightedLabel::new( - entry.bookmark.ref_name.clone(), - entry.positions.clone(), - )), - ) - } -} diff --git a/crates/jj_ui/src/jj_ui.rs b/crates/jj_ui/src/jj_ui.rs deleted file mode 100644 index 5a2ecb78b1102ea27d6a661b4ab736206ad3151d..0000000000000000000000000000000000000000 --- a/crates/jj_ui/src/jj_ui.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod bookmark_picker; - -use command_palette_hooks::CommandPaletteFilter; -use feature_flags::FeatureFlagAppExt as _; -use gpui::App; -use jj::JujutsuStore; -use workspace::Workspace; - -pub fn init(cx: &mut App) { - JujutsuStore::init_global(cx); - - cx.observe_new(|workspace: &mut Workspace, _window, _cx| { - bookmark_picker::register(workspace); - }) - .detach(); - - feature_gate_jj_ui_actions(cx); -} - -fn feature_gate_jj_ui_actions(cx: &mut App) { - const JJ_ACTION_NAMESPACE: &str = "jj"; - - CommandPaletteFilter::update_global(cx, |filter, _cx| { - filter.hide_namespace(JJ_ACTION_NAMESPACE); - }); - - cx.observe_flag::({ - move |is_enabled, cx| { - CommandPaletteFilter::update_global(cx, |filter, _cx| { - if is_enabled { - filter.show_namespace(JJ_ACTION_NAMESPACE); - } else { - filter.hide_namespace(JJ_ACTION_NAMESPACE); - } - }); - } - }) - .detach(); -} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e52d3e7b4c8310a1765fc9d39b278b7c9e284d36..18e2226ece905ceea0b262879dc59225f356155d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -80,7 +80,6 @@ image_viewer.workspace = true edit_prediction_button.workspace = true inspector_ui.workspace = true install_cli.workspace = true -jj_ui.workspace = true journal.workspace = true language.workspace = true language_extension.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index af882e76231458f160fac9354d2c57150560c00b..98140cc61497ac5c1cfd3cf9f0ff1b6d19e4888c 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -610,7 +610,6 @@ pub fn main() { notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); git_ui::init(cx); - jj_ui::init(cx); feedback::init(cx); markdown_preview::init(cx); svg_preview::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 5e9ebb3433ee10451102b6066bda73f4cb4d1a51..41d7e98a56b31cbc1107f8f027cb67e142ae1077 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4492,7 +4492,6 @@ mod tests { "git_panel", "go_to_line", "icon_theme_selector", - "jj", "journal", "keymap_editor", "keystroke_input", diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 81cca94f067de206a52241d202acf517dc80c614..0eedce222982c9a5076dc0c2eecb82446d8d4d95 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -187,18 +187,6 @@ pub mod git { ); } -pub mod jj { - use gpui::actions; - - actions!( - jj, - [ - /// Opens the Jujutsu bookmark list. - BookmarkList - ] - ); -} - pub mod toast { use gpui::actions; diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 342d675bf38c3f9233d3dee4f8eefd77bfbc7836..1aa6aef70be81b099e48b9ec0c15d1e5bfdeb99f 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -56,7 +56,6 @@ either = { version = "1", features = ["serde", "use_std"] } euclid = { version = "0.22" } event-listener = { version = "5" } event-listener-strategy = { version = "0.5" } -flate2 = { version = "1", features = ["zlib-rs"] } form_urlencoded = { version = "1" } futures = { version = "0.3", features = ["io-compat"] } futures-channel = { version = "0.3", features = ["sink"] } @@ -76,7 +75,6 @@ hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", " idna = { version = "1" } indexmap = { version = "2", features = ["serde"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } -jiff = { version = "0.2" } lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } libc = { version = "0.2", features = ["extra_traits"] } libsqlite3-sys = { version = "0.30", features = ["bundled", "unlock_notify"] } @@ -102,7 +100,7 @@ phf_shared = { version = "0.11" } prost-274715c4dabd11b0 = { package = "prost", version = "0.9" } prost-types = { version = "0.9" } rand-c38e5c1d305a1b54 = { package = "rand", version = "0.8", features = ["small_rng"] } -rand_chacha = { version = "0.3" } +rand_chacha = { version = "0.3", default-features = false, features = ["std"] } rand_core = { version = "0.6", default-features = false, features = ["std"] } rand_distr = { version = "0.5" } regalloc2 = { version = "0.11", features = ["checker", "enable-serde"] } @@ -120,9 +118,8 @@ semver = { version = "1", features = ["serde"] } serde = { version = "1", features = ["alloc", "derive", "rc"] } serde_core = { version = "1", default-features = false, features = ["alloc", "rc", "result", "std"] } serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] } -sha1 = { version = "0.10", features = ["compress"] } simd-adler32 = { version = "0.3" } -smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] } +smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union"] } spin = { version = "0.9" } sqlx = { version = "0.8", features = ["bigdecimal", "chrono", "postgres", "runtime-tokio-rustls", "rust_decimal", "sqlite", "time", "uuid"] } sqlx-postgres = { version = "0.8", default-features = false, features = ["any", "bigdecimal", "chrono", "json", "migrate", "offline", "rust_decimal", "time", "uuid"] } @@ -136,11 +133,10 @@ tokio = { version = "1", features = ["full"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"] } tokio-util = { version = "0.7", features = ["codec", "compat", "io"] } toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } -toml_edit = { version = "0.22", features = ["serde"] } +toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] } tracing = { version = "0.1", features = ["log"] } tracing-core = { version = "0.1" } tungstenite = { version = "0.26", default-features = false, features = ["__rustls-tls", "handshake"] } -unicode-normalization = { version = "0.1" } unicode-properties = { version = "0.1" } url = { version = "2", features = ["serde"] } uuid = { version = "1", features = ["serde", "v4", "v5", "v7"] } @@ -148,7 +144,6 @@ wasmparser = { version = "0.221" } wasmtime = { version = "29", default-features = false, features = ["async", "component-model", "cranelift", "demangle", "gc-drc", "incremental-cache", "parallel-compilation"] } wasmtime-cranelift = { version = "29", default-features = false, features = ["component-model", "gc-drc", "incremental-cache"] } wasmtime-environ = { version = "29", default-features = false, features = ["compile", "component-model", "demangle", "gc-drc"] } -winnow = { version = "0.7", features = ["simd"] } [build-dependencies] ahash = { version = "0.8", features = ["serde"] } @@ -193,7 +188,6 @@ either = { version = "1", features = ["serde", "use_std"] } euclid = { version = "0.22" } event-listener = { version = "5" } event-listener-strategy = { version = "0.5" } -flate2 = { version = "1", features = ["zlib-rs"] } form_urlencoded = { version = "1" } futures = { version = "0.3", features = ["io-compat"] } futures-channel = { version = "0.3", features = ["sink"] } @@ -215,7 +209,6 @@ idna = { version = "1" } indexmap = { version = "2", features = ["serde"] } itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } -jiff = { version = "0.2" } lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } libc = { version = "0.2", features = ["extra_traits"] } libsqlite3-sys = { version = "0.30", features = ["bundled", "unlock_notify"] } @@ -244,7 +237,7 @@ prost-274715c4dabd11b0 = { package = "prost", version = "0.9" } prost-types = { version = "0.9" } quote = { version = "1" } rand-c38e5c1d305a1b54 = { package = "rand", version = "0.8", features = ["small_rng"] } -rand_chacha = { version = "0.3" } +rand_chacha = { version = "0.3", default-features = false, features = ["std"] } rand_core = { version = "0.6", default-features = false, features = ["std"] } rand_distr = { version = "0.5" } regalloc2 = { version = "0.11", features = ["checker", "enable-serde"] } @@ -262,9 +255,8 @@ semver = { version = "1", features = ["serde"] } serde = { version = "1", features = ["alloc", "derive", "rc"] } serde_core = { version = "1", default-features = false, features = ["alloc", "rc", "result", "std"] } serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] } -sha1 = { version = "0.10", features = ["compress"] } simd-adler32 = { version = "0.3" } -smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] } +smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union"] } spin = { version = "0.9" } sqlx = { version = "0.8", features = ["bigdecimal", "chrono", "postgres", "runtime-tokio-rustls", "rust_decimal", "sqlite", "time", "uuid"] } sqlx-macros = { version = "0.8", features = ["_rt-tokio", "_tls-rustls-ring-webpki", "bigdecimal", "chrono", "derive", "json", "macros", "migrate", "postgres", "rust_decimal", "sqlite", "time", "uuid"] } @@ -283,11 +275,10 @@ tokio = { version = "1", features = ["full"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"] } tokio-util = { version = "0.7", features = ["codec", "compat", "io"] } toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } -toml_edit = { version = "0.22", features = ["serde"] } +toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] } tracing = { version = "0.1", features = ["log"] } tracing-core = { version = "0.1" } tungstenite = { version = "0.26", default-features = false, features = ["__rustls-tls", "handshake"] } -unicode-normalization = { version = "0.1" } unicode-properties = { version = "0.1" } url = { version = "2", features = ["serde"] } uuid = { version = "1", features = ["serde", "v4", "v5", "v7"] } @@ -295,12 +286,12 @@ wasmparser = { version = "0.221" } wasmtime = { version = "29", default-features = false, features = ["async", "component-model", "cranelift", "demangle", "gc-drc", "incremental-cache", "parallel-compilation"] } wasmtime-cranelift = { version = "29", default-features = false, features = ["component-model", "gc-drc", "incremental-cache"] } wasmtime-environ = { version = "29", default-features = false, features = ["compile", "component-model", "demangle", "gc-drc"] } -winnow = { version = "0.7", features = ["simd"] } [target.x86_64-apple-darwin.dependencies] codespan-reporting = { version = "0.12" } core-foundation = { version = "0.9" } core-foundation-sys = { version = "0.8" } +flate2 = { version = "1" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } @@ -331,6 +322,7 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti codespan-reporting = { version = "0.12" } core-foundation = { version = "0.9" } core-foundation-sys = { version = "0.8" } +flate2 = { version = "1" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } @@ -362,6 +354,7 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti codespan-reporting = { version = "0.12" } core-foundation = { version = "0.9" } core-foundation-sys = { version = "0.8" } +flate2 = { version = "1" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } @@ -392,6 +385,7 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti codespan-reporting = { version = "0.12" } core-foundation = { version = "0.9" } core-foundation-sys = { version = "0.8" } +flate2 = { version = "1" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } @@ -426,6 +420,7 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] } codespan-reporting = { version = "0.12" } crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -451,6 +446,7 @@ ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } +smallvec = { version = "1", default-features = false, features = ["write"] } syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } @@ -467,6 +463,7 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] } codespan-reporting = { version = "0.12" } crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -491,6 +488,7 @@ ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } +smallvec = { version = "1", default-features = false, features = ["write"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } @@ -506,6 +504,7 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] } codespan-reporting = { version = "0.12" } crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -531,6 +530,7 @@ ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } +smallvec = { version = "1", default-features = false, features = ["write"] } syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } @@ -547,6 +547,7 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] } codespan-reporting = { version = "0.12" } crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -571,6 +572,7 @@ ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } +smallvec = { version = "1", default-features = false, features = ["write"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } @@ -581,6 +583,7 @@ zvariant = { version = "5", features = ["enumflags2", "gvariant", "url"] } [target.x86_64-pc-windows-msvc.dependencies] codespan-reporting = { version = "0.12" } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -601,12 +604,13 @@ winapi = { version = "0.3", default-features = false, features = ["cfg", "commap windows-core = { version = "0.61" } windows-numerics = { version = "0.2" } windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } -windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } +windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } windows-sys-d4189bed749088b6 = { package = "windows-sys", version = "0.61", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } [target.x86_64-pc-windows-msvc.build-dependencies] codespan-reporting = { version = "0.12" } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -628,7 +632,7 @@ winapi = { version = "0.3", default-features = false, features = ["cfg", "commap windows-core = { version = "0.61" } windows-numerics = { version = "0.2" } windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } -windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } +windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] } windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } windows-sys-d4189bed749088b6 = { package = "windows-sys", version = "0.61", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] } @@ -639,6 +643,7 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] } codespan-reporting = { version = "0.12" } crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -664,6 +669,7 @@ ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } +smallvec = { version = "1", default-features = false, features = ["write"] } syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } @@ -680,6 +686,7 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] } codespan-reporting = { version = "0.12" } crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] } +flate2 = { version = "1" } flume = { version = "0.11" } foldhash = { version = "0.1", default-features = false, features = ["std"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] } @@ -704,6 +711,7 @@ ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "net", "param", "pipe", "process", "shm", "system"] } rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", default-features = false, features = ["event", "pipe", "process", "pty", "stdio", "termios", "time"] } scopeguard = { version = "1" } +smallvec = { version = "1", default-features = false, features = ["write"] } sync_wrapper = { version = "1", default-features = false, features = ["futures"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } tokio-socks = { version = "0.5", features = ["futures-io"] } From 0a261ad8d02c1903a6874ee7870e09e08e13b1e6 Mon Sep 17 00:00:00 2001 From: Jonathan Hart <43473765+Quplet@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:44:40 -0400 Subject: [PATCH 230/721] Implement regex_select action for Helix (#38736) Closes #31561 Release Notes: - Implemented the select_regex Helix keymap Prior: The keymap `s` defaulted to `vim::Substitute` After: image Thank you to @ConradIrwin for pairing to work on this --- assets/keymaps/vim.json | 1 + crates/editor/src/items.rs | 23 ++++---- crates/search/src/buffer_search.rs | 63 +++++++++++++------- crates/vim/src/helix.rs | 93 +++++++++++++++++++++++++++++- crates/vim/src/normal/search.rs | 7 +++ crates/vim/src/state.rs | 1 + crates/workspace/src/searchable.rs | 26 ++++++++- 7 files changed, 180 insertions(+), 34 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 8f5f99e96f708dcc08cc1a9c1fcfc799d6ba43e7..5d98fd2a1c833f3003edf623b997cb321656fdbe 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -426,6 +426,7 @@ ";": "vim::HelixCollapseSelection", ":": "command_palette::Toggle", "m": "vim::PushHelixMatch", + "s": "vim::HelixSelectRegex", "]": ["vim::PushHelixNext", { "around": true }], "[": ["vim::PushHelixPrevious", { "around": true }], "left": "vim::WrappingLeft", diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index a1b311a3ac3b8ed330fee0f015c41d327efe342d..b5ae47bbdf0fc13a87b6bdac63f9f2a85594aca0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -44,7 +44,9 @@ use workspace::{ CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, invalid_buffer_view::InvalidBufferView, item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions}, - searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, + searchable::{ + Direction, FilteredSearchRange, SearchEvent, SearchableItem, SearchableItemHandle, + }, }; use workspace::{ OpenOptions, @@ -1510,7 +1512,7 @@ impl SearchableItem for Editor { fn toggle_filtered_search_ranges( &mut self, - enabled: bool, + enabled: Option, _: &mut Window, cx: &mut Context, ) { @@ -1520,15 +1522,16 @@ impl SearchableItem for Editor { .map(|(_, ranges)| ranges) } - if !enabled { - return; - } + if let Some(range) = enabled { + let ranges = self.selections.disjoint_anchor_ranges().collect::>(); - let ranges = self.selections.disjoint_anchor_ranges().collect::>(); - if ranges.iter().any(|s| s.start != s.end) { - self.set_search_within_ranges(&ranges, cx); - } else if let Some(previous_search_ranges) = self.previous_search_ranges.take() { - self.set_search_within_ranges(&previous_search_ranges, cx) + if ranges.iter().any(|s| s.start != s.end) { + self.set_search_within_ranges(&ranges, cx); + } else if let Some(previous_search_ranges) = self.previous_search_ranges.take() + && range != FilteredSearchRange::Selection + { + self.set_search_within_ranges(&previous_search_ranges, cx); + } } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 126215a0a75ee5057c560462f40958ba71d8cf74..81dd81050e2504013a173b8165532c6177126845 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -38,7 +38,9 @@ use util::ResultExt; use workspace::{ ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::ItemHandle, - searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle}, + searchable::{ + Direction, FilteredSearchRange, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle, + }, }; pub use registrar::DivRegistrar; @@ -117,7 +119,7 @@ pub struct BufferSearchBar { search_history: SearchHistory, search_history_cursor: SearchHistoryCursor, replace_enabled: bool, - selection_search_enabled: bool, + selection_search_enabled: Option, scroll_handle: ScrollHandle, editor_scroll_handle: ScrollHandle, editor_needed_width: Pixels, @@ -255,13 +257,13 @@ impl Render for BufferSearchBar { ) .style(ButtonStyle::Subtle) .shape(IconButtonShape::Square) - .when(self.selection_search_enabled, |button| { + .when(self.selection_search_enabled.is_some(), |button| { button.style(ButtonStyle::Filled) }) .on_click(cx.listener(|this, _: &ClickEvent, window, cx| { this.toggle_selection(&ToggleSelection, window, cx); })) - .toggle_state(self.selection_search_enabled) + .toggle_state(self.selection_search_enabled.is_some()) .tooltip({ let focus_handle = focus_handle.clone(); move |window, cx| { @@ -673,7 +675,7 @@ impl BufferSearchBar { search_history_cursor: Default::default(), active_search: None, replace_enabled: false, - selection_search_enabled: false, + selection_search_enabled: None, scroll_handle: ScrollHandle::new(), editor_scroll_handle: ScrollHandle::new(), editor_needed_width: px(0.), @@ -696,10 +698,10 @@ impl BufferSearchBar { } } if let Some(active_editor) = self.active_searchable_item.as_mut() { - self.selection_search_enabled = false; + self.selection_search_enabled = None; self.replace_enabled = false; active_editor.search_bar_visibility_changed(false, window, cx); - active_editor.toggle_filtered_search_ranges(false, window, cx); + active_editor.toggle_filtered_search_ranges(None, window, cx); let handle = active_editor.item_focus_handle(cx); self.focus(&handle, window); } @@ -711,18 +713,23 @@ impl BufferSearchBar { } pub fn deploy(&mut self, deploy: &Deploy, window: &mut Window, cx: &mut Context) -> bool { + let filtered_search_range = if deploy.selection_search_enabled { + Some(FilteredSearchRange::Default) + } else { + None + }; if self.show(window, cx) { if let Some(active_item) = self.active_searchable_item.as_mut() { - active_item.toggle_filtered_search_ranges( - deploy.selection_search_enabled, - window, - cx, - ); + active_item.toggle_filtered_search_ranges(filtered_search_range, window, cx); } self.search_suggested(window, cx); self.smartcase(window, cx); self.replace_enabled = deploy.replace_enabled; - self.selection_search_enabled = deploy.selection_search_enabled; + self.selection_search_enabled = if deploy.selection_search_enabled { + Some(FilteredSearchRange::Default) + } else { + None + }; if deploy.focus { let mut handle = self.query_editor.focus_handle(cx); let mut select_query = true; @@ -923,6 +930,19 @@ impl BufferSearchBar { } } + pub fn set_search_within_selection( + &mut self, + search_within_selection: Option, + window: &mut Window, + cx: &mut Context, + ) -> Option> { + let active_item = self.active_searchable_item.as_mut()?; + self.selection_search_enabled = search_within_selection; + active_item.toggle_filtered_search_ranges(self.selection_search_enabled, window, cx); + cx.notify(); + Some(self.update_matches(false, false, window, cx)) + } + pub fn set_search_options(&mut self, search_options: SearchOptions, cx: &mut Context) { self.search_options = search_options; self.adjust_query_regex_language(cx); @@ -957,7 +977,7 @@ impl BufferSearchBar { self.select_match(Direction::Prev, 1, window, cx); } - fn select_all_matches( + pub fn select_all_matches( &mut self, _: &SelectAllMatches, window: &mut Window, @@ -1125,12 +1145,15 @@ impl BufferSearchBar { window: &mut Window, cx: &mut Context, ) { - if let Some(active_item) = self.active_searchable_item.as_mut() { - self.selection_search_enabled = !self.selection_search_enabled; - active_item.toggle_filtered_search_ranges(self.selection_search_enabled, window, cx); - drop(self.update_matches(false, false, window, cx)); - cx.notify(); - } + self.set_search_within_selection( + if let Some(_) = self.selection_search_enabled { + None + } else { + Some(FilteredSearchRange::Default) + }, + window, + cx, + ); } fn toggle_regex(&mut self, _: &ToggleRegex, window: &mut Window, cx: &mut Context) { diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index ec1618311f8b8e16b71a39fc1d29b5c60eb49c96..e2174fe1a7adee3f5b6cdac5167f2c28fb3296c1 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -5,14 +5,20 @@ mod select; use editor::display_map::DisplaySnapshot; use editor::{ - DisplayPoint, Editor, HideMouseCursorOrigin, SelectionEffects, ToOffset, ToPoint, movement, + DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, SelectionEffects, ToOffset, + ToPoint, movement, }; use gpui::actions; use gpui::{Context, Window}; use language::{CharClassifier, CharKind, Point}; +use search::{BufferSearchBar, SearchOptions}; +use settings::Settings; use text::{Bias, SelectionGoal}; +use workspace::searchable; +use workspace::searchable::FilteredSearchRange; use crate::motion; +use crate::state::SearchState; use crate::{ Vim, motion::{Motion, right}, @@ -32,6 +38,8 @@ actions!( HelixGotoLastModification, /// Select entire line or multiple lines, extending downwards. HelixSelectLine, + /// Select all matches of a given pattern within the current selection. + HelixSelectRegex, ] ); @@ -42,6 +50,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::helix_yank); Vim::action(editor, cx, Vim::helix_goto_last_modification); Vim::action(editor, cx, Vim::helix_paste); + Vim::action(editor, cx, Vim::helix_select_regex); } impl Vim { @@ -368,6 +377,64 @@ impl Vim { self.switch_mode(Mode::Insert, false, window, cx); } + fn helix_select_regex( + &mut self, + _: &HelixSelectRegex, + window: &mut Window, + cx: &mut Context, + ) { + Vim::take_forced_motion(cx); + let Some(pane) = self.pane(window, cx) else { + return; + }; + let prior_selections = self.editor_selections(window, cx); + pane.update(cx, |pane, cx| { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |search_bar, cx| { + if !search_bar.show(window, cx) { + return; + } + + search_bar.select_query(window, cx); + cx.focus_self(window); + + search_bar.set_replacement(None, cx); + let mut options = SearchOptions::NONE; + options |= SearchOptions::REGEX; + if EditorSettings::get_global(cx).search.case_sensitive { + options |= SearchOptions::CASE_SENSITIVE; + } + search_bar.set_search_options(options, cx); + if let Some(search) = search_bar.set_search_within_selection( + Some(FilteredSearchRange::Selection), + window, + cx, + ) { + cx.spawn_in(window, async move |search_bar, cx| { + if search.await.is_ok() { + search_bar.update_in(cx, |search_bar, window, cx| { + search_bar.activate_current_match(window, cx) + }) + } else { + Ok(()) + } + }) + .detach_and_log_err(cx); + } + self.search = SearchState { + direction: searchable::Direction::Next, + count: 1, + prior_selections, + prior_operator: self.operator_stack.last().cloned(), + prior_mode: self.mode, + helix_select: true, + } + }); + } + }); + self.start_recording(cx); + } + fn helix_append(&mut self, _: &HelixAppend, window: &mut Window, cx: &mut Context) { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); @@ -1121,4 +1188,28 @@ mod test { cx.simulate_keystrokes("v w"); cx.assert_state("«one ˇ»two", Mode::HelixSelect); } + + #[gpui::test] + async fn test_helix_select_regex(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + + cx.set_state("ˇone two one", Mode::HelixNormal); + cx.simulate_keystrokes("x"); + cx.assert_state("«one two oneˇ»", Mode::HelixNormal); + cx.simulate_keystrokes("s o n e"); + cx.run_until_parked(); + cx.simulate_keystrokes("enter"); + cx.assert_state("«oneˇ» two «oneˇ»", Mode::HelixNormal); + + cx.simulate_keystrokes("x"); + cx.simulate_keystrokes("s"); + cx.run_until_parked(); + cx.simulate_keystrokes("enter"); + cx.assert_state("«oneˇ» two «oneˇ»", Mode::HelixNormal); + + cx.set_state("ˇone two one", Mode::HelixNormal); + cx.simulate_keystrokes("s o n e enter"); + cx.assert_state("ˇone two one", Mode::HelixNormal); + } } diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 1023375cacc21152b27e8bf49177dc1d92f8ca91..0f7b4421ae7a19423be72be227667b2110dedf64 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -195,6 +195,7 @@ impl Vim { prior_selections, prior_operator: self.operator_stack.last().cloned(), prior_mode, + helix_select: false, } }); } @@ -218,6 +219,12 @@ impl Vim { let new_selections = self.editor_selections(window, cx); let result = pane.update(cx, |pane, cx| { let search_bar = pane.toolbar().read(cx).item_of_type::()?; + if self.search.helix_select { + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&Default::default(), window, cx) + }); + return None; + } search_bar.update(cx, |search_bar, cx| { let mut count = self.search.count; let direction = self.search.direction; diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index f81299169058b430b6ea6557f3d66762a6705a82..ee1e0eedec799630662e9bc94721ceb588ad94a3 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -988,6 +988,7 @@ pub struct SearchState { pub prior_selections: Vec>, pub prior_operator: Option, pub prior_mode: Mode, + pub helix_select: bool, } impl Operator { diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index b21ba7a4b1a2ec7cc80521e91b4e5935333615f5..310fae908dbd6864c1636ebd393e4920d0f9ad02 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -45,6 +45,16 @@ pub struct SearchOptions { pub find_in_results: bool, } +// Whether to always select the current selection (even if empty) +// or to use the default (restoring the previous search ranges if some, +// otherwise using the whole file). +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum FilteredSearchRange { + Selection, + #[default] + Default, +} + pub trait SearchableItem: Item + EventEmitter { type Match: Any + Sync + Send + Clone; @@ -73,7 +83,7 @@ pub trait SearchableItem: Item + EventEmitter { fn toggle_filtered_search_ranges( &mut self, - _enabled: bool, + _enabled: Option, _window: &mut Window, _cx: &mut Context, ) { @@ -216,7 +226,12 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App); - fn toggle_filtered_search_ranges(&mut self, enabled: bool, window: &mut Window, cx: &mut App); + fn toggle_filtered_search_ranges( + &mut self, + enabled: Option, + window: &mut Window, + cx: &mut App, + ); } impl SearchableItemHandle for Entity { @@ -362,7 +377,12 @@ impl SearchableItemHandle for Entity { }); } - fn toggle_filtered_search_ranges(&mut self, enabled: bool, window: &mut Window, cx: &mut App) { + fn toggle_filtered_search_ranges( + &mut self, + enabled: Option, + window: &mut Window, + cx: &mut App, + ) { self.update(cx, |this, cx| { this.toggle_filtered_search_ranges(enabled, window, cx) }); From b60f19f71e1378e35214f533d589e8366f541536 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:57:28 -0300 Subject: [PATCH 231/721] agent: Allow to see the whole command before running it (#38747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/zed-industries/zed/issues/38528 In the agent panel's `thread_view.rs` file, we have a `render_tool_call` function that controls what we show in the UI for most types of tools. However, for some of them—for example, terminal/execute and edit tools—we have a special rendering so we can tailor the UI for their specific needs. But... before the specific rendering function is called, all tools still go through the `render_tool_call`. Problem is that, in the case of the terminal tool, you couldn't see the full command the agent wants to run when the tool is still in its `render_tool_call` state. That's mostly because of the treatment we give to labels while in that state. A particularly bad scenario because well... seeing the _full_ command _before_ you choose to accept or reject is rather important. This PR fixes that by essentially special-casing the terminal tool display when in the `render_tool_call` rendering state, so to speak. There's still a slight UI misalignment I want to fix but it shouldn't block this fix to go out. Here's our final result: Screenshot 2025-09-23 at 6  19@2x Release Notes: - agent: Fixed terminal command not being fully displayed while in the "waiting for confirmation" state. --- crates/agent2/src/tools/terminal_tool.rs | 2 +- crates/agent_ui/src/acp/thread_view.rs | 328 +++++++++++++---------- 2 files changed, 191 insertions(+), 139 deletions(-) diff --git a/crates/agent2/src/tools/terminal_tool.rs b/crates/agent2/src/tools/terminal_tool.rs index 7acfc2455093eac0f3d15e840abce47f38a6c8b0..6d30c19152001deaef5deeacbdf266e28ac03d08 100644 --- a/crates/agent2/src/tools/terminal_tool.rs +++ b/crates/agent2/src/tools/terminal_tool.rs @@ -82,7 +82,7 @@ impl AgentTool for TerminalTool { .into(), } } else { - "Run terminal command".into() + "".into() } } diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index cf6f563ecdeb5a60e2b3faa4a5ee36d3282e1bdb..4a91a9fff7b9902bc51301752a578114159bf680 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -2079,27 +2079,6 @@ impl AcpThreadView { let has_location = tool_call.locations.len() == 1; let card_header_id = SharedString::from("inner-tool-call-header"); - let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location { - FileIcons::get_icon(&tool_call.locations[0].path, cx) - .map(Icon::from_path) - .unwrap_or(Icon::new(IconName::ToolPencil)) - } else { - Icon::new(match tool_call.kind { - acp::ToolKind::Read => IconName::ToolSearch, - acp::ToolKind::Edit => IconName::ToolPencil, - acp::ToolKind::Delete => IconName::ToolDeleteFile, - acp::ToolKind::Move => IconName::ArrowRightLeft, - acp::ToolKind::Search => IconName::ToolSearch, - acp::ToolKind::Execute => IconName::ToolTerminal, - acp::ToolKind::Think => IconName::ToolThink, - acp::ToolKind::Fetch => IconName::ToolWeb, - acp::ToolKind::SwitchMode => IconName::ArrowRightLeft, - acp::ToolKind::Other => IconName::ToolHammer, - }) - } - .size(IconSize::Small) - .color(Color::Muted); - let failed_or_canceled = match &tool_call.status { ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed => true, _ => false, @@ -2109,41 +2088,16 @@ impl AcpThreadView { tool_call.status, ToolCallStatus::WaitingForConfirmation { .. } ); + let is_terminal_tool = matches!(tool_call.kind, acp::ToolKind::Execute); 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 use_card_layout = needs_confirmation || is_edit || is_terminal_tool; let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation; let is_open = needs_confirmation || self.expanded_tool_calls.contains(&tool_call.id); - let gradient_overlay = { - div() - .absolute() - .top_0() - .right_0() - .w_12() - .h_full() - .map(|this| { - if use_card_layout { - this.bg(linear_gradient( - 90., - linear_color_stop(self.tool_card_header_bg(cx), 1.), - linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.), - )) - } else { - this.bg(linear_gradient( - 90., - linear_color_stop(cx.theme().colors().panel_background, 1.), - linear_color_stop( - cx.theme().colors().panel_background.opacity(0.2), - 0., - ), - )) - } - }) - }; - let tool_output_display = if is_open { match &tool_call.status { @@ -2228,104 +2182,202 @@ impl AcpThreadView { } }) .mr_5() - .child( - h_flex() - .group(&card_header_id) - .relative() - .w_full() - .gap_1() - .justify_between() - .when(use_card_layout, |this| { - this.p_0p5() - .rounded_t(rems_from_px(5.)) + .map(|this| { + if is_terminal_tool { + this.child( + v_flex() + .p_1p5() + .gap_0p5() + .text_ui_sm(cx) .bg(self.tool_card_header_bg(cx)) - }) - .child( + .child( + Label::new("Run Command") + .buffer_font(cx) + .size(LabelSize::XSmall) + .color(Color::Muted), + ) + .child( + MarkdownElement::new( + tool_call.label.clone(), + terminal_command_markdown_style(window, cx), + ) + .code_block_renderer( + markdown::CodeBlockRenderer::Default { + copy_button: false, + copy_button_on_hover: false, + border: false, + }, + ) + ), + ) + } else { + this.child( h_flex() + .group(&card_header_id) .relative() .w_full() - .h(window.line_height() - px(2.)) - .text_size(self.tool_name_font_size()) - .gap_1p5() - .when(has_location || use_card_layout, |this| this.px_1()) - .when(has_location, |this| { - this.cursor(CursorStyle::PointingHand) - .rounded(rems_from_px(3.)) // Concentric border radius - .hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5))) - }) - .overflow_hidden() - .child(tool_icon) - .child(if has_location { - h_flex() - .id(("open-tool-call-location", entry_ix)) - .w_full() - .map(|this| { - if use_card_layout { - this.text_color(cx.theme().colors().text) - } else { - this.text_color(cx.theme().colors().text_muted) - } - }) - .child(self.render_markdown( - tool_call.label.clone(), - MarkdownStyle { - prevent_mouse_interaction: true, - ..default_markdown_style(false, true, window, cx) - }, - )) - .tooltip(Tooltip::text("Jump to File")) - .on_click(cx.listener(move |this, _, window, cx| { - this.open_tool_call_location(entry_ix, 0, window, cx); - })) - .into_any_element() - } else { - h_flex() - .w_full() - .child(self.render_markdown( - tool_call.label.clone(), - default_markdown_style(false, true, window, cx), - )) - .into_any() + .gap_1() + .justify_between() + .when(use_card_layout, |this| { + this.p_0p5() + .rounded_t(rems_from_px(5.)) + .bg(self.tool_card_header_bg(cx)) }) - .when(!has_location, |this| this.child(gradient_overlay)), - ) - .when(is_collapsible || failed_or_canceled, |this| { - this.child( - h_flex() - .px_1() - .gap_px() - .when(is_collapsible, |this| { - this.child( - Disclosure::new(("expand", entry_ix), is_open) - .opened_icon(IconName::ChevronUp) - .closed_icon(IconName::ChevronDown) - .visible_on_hover(&card_header_id) - .on_click(cx.listener({ - let id = tool_call.id.clone(); - move |this: &mut Self, _, _, cx: &mut Context| { - if is_open { - this.expanded_tool_calls.remove(&id); - } else { - this.expanded_tool_calls.insert(id.clone()); - } - cx.notify(); - } - })), + .child(self.render_tool_call_label( + entry_ix, + tool_call, + is_edit, + use_card_layout, + window, + cx, + )) + .when(is_collapsible || failed_or_canceled, |this| { + this.child( + h_flex() + .px_1() + .gap_px() + .when(is_collapsible, |this| { + this.child( + Disclosure::new(("expand", entry_ix), is_open) + .opened_icon(IconName::ChevronUp) + .closed_icon(IconName::ChevronDown) + .visible_on_hover(&card_header_id) + .on_click(cx.listener({ + let id = tool_call.id.clone(); + move |this: &mut Self, _, _, cx: &mut Context| { + if is_open { + this.expanded_tool_calls.remove(&id); + } else { + this.expanded_tool_calls.insert(id.clone()); + } + cx.notify(); + } + })), + ) + }) + .when(failed_or_canceled, |this| { + this.child( + Icon::new(IconName::Close) + .color(Color::Error) + .size(IconSize::Small), + ) + }), ) - }) - .when(failed_or_canceled, |this| { - this.child( - Icon::new(IconName::Close) - .color(Color::Error) - .size(IconSize::Small), - ) - }), - ) - }), - ) + }), + ) + } + }) .children(tool_output_display) } + fn render_tool_call_label( + &self, + entry_ix: usize, + tool_call: &ToolCall, + is_edit: bool, + use_card_layout: bool, + window: &Window, + cx: &Context, + ) -> Div { + let has_location = tool_call.locations.len() == 1; + + let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location { + FileIcons::get_icon(&tool_call.locations[0].path, cx) + .map(Icon::from_path) + .unwrap_or(Icon::new(IconName::ToolPencil)) + } else { + Icon::new(match tool_call.kind { + acp::ToolKind::Read => IconName::ToolSearch, + acp::ToolKind::Edit => IconName::ToolPencil, + acp::ToolKind::Delete => IconName::ToolDeleteFile, + acp::ToolKind::Move => IconName::ArrowRightLeft, + acp::ToolKind::Search => IconName::ToolSearch, + acp::ToolKind::Execute => IconName::ToolTerminal, + acp::ToolKind::Think => IconName::ToolThink, + acp::ToolKind::Fetch => IconName::ToolWeb, + acp::ToolKind::SwitchMode => IconName::ArrowRightLeft, + acp::ToolKind::Other => IconName::ToolHammer, + }) + } + .size(IconSize::Small) + .color(Color::Muted); + + let gradient_overlay = { + div() + .absolute() + .top_0() + .right_0() + .w_12() + .h_full() + .map(|this| { + if use_card_layout { + this.bg(linear_gradient( + 90., + linear_color_stop(self.tool_card_header_bg(cx), 1.), + linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.), + )) + } else { + this.bg(linear_gradient( + 90., + linear_color_stop(cx.theme().colors().panel_background, 1.), + linear_color_stop( + cx.theme().colors().panel_background.opacity(0.2), + 0., + ), + )) + } + }) + }; + + h_flex() + .relative() + .w_full() + .h(window.line_height() - px(2.)) + .text_size(self.tool_name_font_size()) + .gap_1p5() + .when(has_location || use_card_layout, |this| this.px_1()) + .when(has_location, |this| { + this.cursor(CursorStyle::PointingHand) + .rounded(rems_from_px(3.)) // Concentric border radius + .hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5))) + }) + .overflow_hidden() + .child(tool_icon) + .child(if has_location { + h_flex() + .id(("open-tool-call-location", entry_ix)) + .w_full() + .map(|this| { + if use_card_layout { + this.text_color(cx.theme().colors().text) + } else { + this.text_color(cx.theme().colors().text_muted) + } + }) + .child(self.render_markdown( + tool_call.label.clone(), + MarkdownStyle { + prevent_mouse_interaction: true, + ..default_markdown_style(false, true, window, cx) + }, + )) + .tooltip(Tooltip::text("Jump to File")) + .on_click(cx.listener(move |this, _, window, cx| { + this.open_tool_call_location(entry_ix, 0, window, cx); + })) + .into_any_element() + } else { + h_flex() + .w_full() + .child(self.render_markdown( + tool_call.label.clone(), + default_markdown_style(false, true, window, cx), + )) + .into_any() + }) + .when(!is_edit, |this| this.child(gradient_overlay)) + } + fn render_tool_call_content( &self, entry_ix: usize, From 0f7dbf57f5e690780cf7b388d19fa1378e297a6e Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 24 Sep 2025 04:34:35 +0530 Subject: [PATCH 232/721] editor: Fix APCA contrast split text runs offset (#38751) Closes #38576 In case of inline element rendering, we can have multiple text runs on the same display row. There was a bug in https://github.com/zed-industries/zed/pull/37165 which doesn't consider this multiple text runs case. This PR fixes that and adds a test for it. Before: image After: image Release Notes: - Fixed an issue where text could be incorrectly highlighted during search when a line contained an inline color preview. --- crates/editor/src/element.rs | 196 +++++++++++++++++++++-------------- 1 file changed, 119 insertions(+), 77 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 28fe68e71cb4fac36f84d1161020e16ba2d0605f..72b84b4599b681088b6cc4aa5afd705d8af229f8 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7616,7 +7616,7 @@ impl LineWithInvisibles { let text_runs: &[TextRun] = if segments.is_empty() { &styles } else { - &Self::split_runs_by_bg_segments(&styles, segments, min_contrast) + &Self::split_runs_by_bg_segments(&styles, segments, min_contrast, len) }; let shaped_line = window.text_system().shape_line( line.clone().into(), @@ -7703,7 +7703,7 @@ impl LineWithInvisibles { let text_runs = if segments.is_empty() { &styles } else { - &Self::split_runs_by_bg_segments(&styles, segments, min_contrast) + &Self::split_runs_by_bg_segments(&styles, segments, min_contrast, len) }; let shaped_line = window.text_system().shape_line( line.clone().into(), @@ -7802,9 +7802,10 @@ impl LineWithInvisibles { text_runs: &[TextRun], bg_segments: &[(Range, Hsla)], min_contrast: f32, + start_col_offset: usize, ) -> Vec { let mut output_runs: Vec = Vec::with_capacity(text_runs.len()); - let mut line_col = 0usize; + let mut line_col = start_col_offset; let mut segment_ix = 0usize; for text_run in text_runs.iter() { @@ -11254,102 +11255,143 @@ mod tests { fn test_split_runs_by_bg_segments(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); + let dx = |start: u32, end: u32| { + DisplayPoint::new(DisplayRow(0), start)..DisplayPoint::new(DisplayRow(0), end) + }; + let text_color = Hsla { h: 210.0, s: 0.1, l: 0.4, a: 1.0, }; - let bg1 = Hsla { + let bg_1 = Hsla { h: 30.0, s: 0.6, l: 0.8, a: 1.0, }; - let bg2 = Hsla { + let bg_2 = Hsla { h: 200.0, s: 0.6, l: 0.2, a: 1.0, }; let min_contrast = 45.0; + let adjusted_bg1 = ensure_minimum_contrast(text_color, bg_1, min_contrast); + let adjusted_bg2 = ensure_minimum_contrast(text_color, bg_2, min_contrast); // Case A: single run; disjoint segments inside the run - let runs = vec![generate_test_run(20, text_color)]; - let segs = vec![ - ( - DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10), - bg1, - ), - ( - DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 16), - bg2, - ), - ]; - let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast); - // Expected slices: [0,5) [5,10) [10,12) [12,16) [16,20) - assert_eq!( - out.iter().map(|r| r.len).collect::>(), - vec![5, 5, 2, 4, 4] - ); - assert_eq!(out[0].color, text_color); - assert_eq!( - out[1].color, - ensure_minimum_contrast(text_color, bg1, min_contrast) - ); - assert_eq!(out[2].color, text_color); - assert_eq!( - out[3].color, - ensure_minimum_contrast(text_color, bg2, min_contrast) - ); - assert_eq!(out[4].color, text_color); + { + let runs = vec![generate_test_run(20, text_color)]; + let segs = vec![(dx(5, 10), bg_1), (dx(12, 16), bg_2)]; + let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0); + // Expected slices: [0,5) [5,10) [10,12) [12,16) [16,20) + assert_eq!( + out.iter().map(|r| r.len).collect::>(), + vec![5, 5, 2, 4, 4] + ); + assert_eq!(out[0].color, text_color); + assert_eq!(out[1].color, adjusted_bg1); + assert_eq!(out[2].color, text_color); + assert_eq!(out[3].color, adjusted_bg2); + assert_eq!(out[4].color, text_color); + } // Case B: multiple runs; segment extends to end of line (u32::MAX) - let runs = vec![ - generate_test_run(8, text_color), - generate_test_run(7, text_color), - ]; - let segs = vec![( - DisplayPoint::new(DisplayRow(0), 6)..DisplayPoint::new(DisplayRow(0), u32::MAX), - bg1, - )]; - let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast); - // Expected slices across runs: [0,6) [6,8) | [0,7) - assert_eq!(out.iter().map(|r| r.len).collect::>(), vec![6, 2, 7]); - let adjusted = ensure_minimum_contrast(text_color, bg1, min_contrast); - assert_eq!(out[0].color, text_color); - assert_eq!(out[1].color, adjusted); - assert_eq!(out[2].color, adjusted); + { + let runs = vec![ + generate_test_run(8, text_color), + generate_test_run(7, text_color), + ]; + let segs = vec![(dx(6, u32::MAX), bg_1)]; + let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0); + // Expected slices across runs: [0,6) [6,8) | [0,7) + assert_eq!(out.iter().map(|r| r.len).collect::>(), vec![6, 2, 7]); + assert_eq!(out[0].color, text_color); + assert_eq!(out[1].color, adjusted_bg1); + assert_eq!(out[2].color, adjusted_bg1); + } // Case C: multi-byte characters - // for text: "Hello 🌍 世界!" - let runs = vec![ - generate_test_run(5, text_color), // "Hello" - generate_test_run(6, text_color), // " 🌍 " - generate_test_run(6, text_color), // "世界" - generate_test_run(1, text_color), // "!" - ]; - // selecting "🌍 世" - let segs = vec![( - DisplayPoint::new(DisplayRow(0), 6)..DisplayPoint::new(DisplayRow(0), 14), - bg1, - )]; - let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast); - // "Hello" | " " | "🌍 " | "世" | "界" | "!" - assert_eq!( - out.iter().map(|r| r.len).collect::>(), - vec![5, 1, 5, 3, 3, 1] - ); - assert_eq!(out[0].color, text_color); // "Hello" - assert_eq!( - out[2].color, - ensure_minimum_contrast(text_color, bg1, min_contrast) - ); // "🌍 " - assert_eq!( - out[3].color, - ensure_minimum_contrast(text_color, bg1, min_contrast) - ); // "世" - assert_eq!(out[4].color, text_color); // "界" - assert_eq!(out[5].color, text_color); // "!" + { + // for text: "Hello 🌍 世界!" + let runs = vec![ + generate_test_run(5, text_color), // "Hello" + generate_test_run(6, text_color), // " 🌍 " + generate_test_run(6, text_color), // "世界" + generate_test_run(1, text_color), // "!" + ]; + // selecting "🌍 世" + let segs = vec![(dx(6, 14), bg_1)]; + let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0); + // "Hello" | " " | "🌍 " | "世" | "界" | "!" + assert_eq!( + out.iter().map(|r| r.len).collect::>(), + vec![5, 1, 5, 3, 3, 1] + ); + assert_eq!(out[0].color, text_color); // "Hello" + assert_eq!(out[2].color, adjusted_bg1); // "🌍 " + assert_eq!(out[3].color, adjusted_bg1); // "世" + assert_eq!(out[4].color, text_color); // "界" + assert_eq!(out[5].color, text_color); // "!" + } + + // Case D: split multiple consecutive text runs with segments + { + let segs = vec![ + (dx(2, 4), bg_1), // selecting "cd" + (dx(4, 8), bg_2), // selecting "efgh" + (dx(9, 11), bg_1), // selecting "jk" + (dx(12, 16), bg_2), // selecting "mnop" + (dx(18, 19), bg_1), // selecting "s" + ]; + + // for text: "abcdef" + let runs = vec![ + generate_test_run(2, text_color), // ab + generate_test_run(4, text_color), // cdef + ]; + let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0); + // new splits "ab", "cd", "ef" + assert_eq!(out.iter().map(|r| r.len).collect::>(), vec![2, 2, 2]); + assert_eq!(out[0].color, text_color); + assert_eq!(out[1].color, adjusted_bg1); + assert_eq!(out[2].color, adjusted_bg2); + + // for text: "ghijklmn" + let runs = vec![ + generate_test_run(3, text_color), // ghi + generate_test_run(2, text_color), // jk + generate_test_run(3, text_color), // lmn + ]; + let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 6); // 2 + 4 from first run + // new splits "gh", "i", "jk", "l", "mn" + assert_eq!( + out.iter().map(|r| r.len).collect::>(), + vec![2, 1, 2, 1, 2] + ); + assert_eq!(out[0].color, adjusted_bg2); + assert_eq!(out[1].color, text_color); + assert_eq!(out[2].color, adjusted_bg1); + assert_eq!(out[3].color, text_color); + assert_eq!(out[4].color, adjusted_bg2); + + // for text: "opqrs" + let runs = vec![ + generate_test_run(1, text_color), // o + generate_test_run(4, text_color), // pqrs + ]; + let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 14); // 6 + 3 + 2 + 3 from first two runs + // new splits "o", "p", "qr", "s" + assert_eq!( + out.iter().map(|r| r.len).collect::>(), + vec![1, 1, 2, 1] + ); + assert_eq!(out[0].color, adjusted_bg2); + assert_eq!(out[1].color, adjusted_bg2); + assert_eq!(out[2].color, text_color); + assert_eq!(out[3].color, adjusted_bg1); + } } } From 5d89b2ea262b7c00b0b517ceb8df25faef57c848 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 23 Sep 2025 22:15:30 -0600 Subject: [PATCH 233/721] Revert "Add setting to show/hide title bar (#37428)" (#38756) Closes https://github.com/zed-industries/zed/issues/38547 Release Notes: - Reverted the ability to show/hide the titlebar. This caused rendering bugs on macOS, and we're preparing for the redesign which requires the toolbar being present. --------- Co-authored-by: Kirill Bulatov --- assets/settings/default.json | 2 - crates/settings/src/settings_content.rs | 12 --- crates/settings/src/settings_store.rs | 118 ++++++++++----------- crates/title_bar/src/title_bar.rs | 49 +-------- crates/title_bar/src/title_bar_settings.rs | 3 - crates/workspace/src/workspace.rs | 5 - docs/src/configuring-zed.md | 1 - docs/src/visual-customization.md | 1 - 8 files changed, 58 insertions(+), 133 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 7730ba8cf63f94ddab0ecf6c1d989c9d66c590d4..b20bf773f82789b45564957652ea6a033e3a1c10 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -391,8 +391,6 @@ "use_system_window_tabs": false, // Titlebar related settings "title_bar": { - // When to show the title bar: "always" | "never" | "hide_in_full_screen". - "show": "always", // Whether to show the branch icon beside branch switcher in the titlebar. "show_branch_icon": false, // Whether to show the branch name button in the titlebar. diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 27c0976fb64dd4ecb473df937cf27f7a21ff3adc..ca7a550024771e21419c0f7a13f68764669833d7 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -237,10 +237,6 @@ pub enum BaseKeymapContent { #[skip_serializing_none] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct TitleBarSettingsContent { - /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen". - /// - /// Default: "always" - pub show: Option, /// Whether to show the branch icon beside branch switcher in the title bar. /// /// Default: false @@ -271,14 +267,6 @@ pub struct TitleBarSettingsContent { pub show_menus: Option, } -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] -#[serde(rename_all = "snake_case")] -pub enum TitleBarVisibility { - Always, - Never, - HideInFullScreen, -} - /// Configuration of audio in Zed. #[skip_serializing_none] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index a575182a4144d99bf3c3c7f29f649735ea8b8891..040122bc1d1fde4387e4ef0c92e1d71b0420b5c6 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1022,7 +1022,7 @@ mod tests { use std::num::NonZeroU32; use crate::{ - TitleBarSettingsContent, TitleBarVisibility, VsCodeSettingsSource, default_settings, + ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings, settings_content::LanguageSettingsContent, test_settings, }; @@ -1043,30 +1043,30 @@ mod tests { } #[derive(Debug, PartialEq)] - struct TitleBarSettings { - show: TitleBarVisibility, - show_branch_name: bool, + struct ItemSettings { + close_position: ClosePosition, + git_status: bool, } - impl Settings for TitleBarSettings { + impl Settings for ItemSettings { fn from_settings(content: &SettingsContent, _: &mut App) -> Self { - let content = content.title_bar.clone().unwrap(); - TitleBarSettings { - show: content.show.unwrap(), - show_branch_name: content.show_branch_name.unwrap(), + let content = content.tabs.clone().unwrap(); + ItemSettings { + close_position: content.close_position.unwrap(), + git_status: content.git_status.unwrap(), } } fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { let mut show = None; - vscode.enum_setting("window.titleBarStyle", &mut show, |value| match value { - "never" => Some(TitleBarVisibility::Never), - "always" => Some(TitleBarVisibility::Always), - _ => None, - }); + vscode.bool_setting("workbench.editor.decorations.colors", &mut show); if let Some(show) = show { - content.title_bar.get_or_insert_default().show.replace(show); + content + .tabs + .get_or_insert_default() + .git_status + .replace(show); } } } @@ -1103,7 +1103,7 @@ mod tests { fn test_settings_store_basic(cx: &mut App) { let mut store = SettingsStore::new(cx, &default_settings()); store.register_setting::(cx); - store.register_setting::(cx); + store.register_setting::(cx); store.register_setting::(cx); assert_eq!( @@ -1111,16 +1111,16 @@ mod tests { &AutoUpdateSetting { auto_update: true } ); assert_eq!( - store.get::(None).show, - TitleBarVisibility::Always + store.get::(None).close_position, + ClosePosition::Right ); store .set_user_settings( r#"{ "auto_update": false, - "title_bar": { - "show": "never" + "tabs": { + "close_position": "left" } }"#, cx, @@ -1132,8 +1132,8 @@ mod tests { &AutoUpdateSetting { auto_update: false } ); assert_eq!( - store.get::(None).show, - TitleBarVisibility::Never + store.get::(None).close_position, + ClosePosition::Left ); store @@ -1160,7 +1160,7 @@ mod tests { WorktreeId::from_usize(1), Path::new("/root2").into(), LocalSettingsKind::Settings, - Some(r#"{ "tab_size": 9, "title_bar": { "show_branch_name": false } }"#), + Some(r#"{ "tab_size": 9, "auto_update": true}"#), cx, ) .unwrap(); @@ -1196,14 +1196,11 @@ mod tests { } ); assert_eq!( - store.get::(Some(SettingsLocation { + store.get::(Some(SettingsLocation { worktree_id: WorktreeId::from_usize(1), path: Path::new("/root2/something") })), - &TitleBarSettings { - show: TitleBarVisibility::Never, - show_branch_name: true, - } + &AutoUpdateSetting { auto_update: false } ); } @@ -1214,16 +1211,11 @@ mod tests { .set_user_settings(r#"{ "auto_update": false }"#, cx) .unwrap(); store.register_setting::(cx); - store.register_setting::(cx); assert_eq!( store.get::(None), &AutoUpdateSetting { auto_update: false } ); - assert_eq!( - store.get::(None).show, - TitleBarVisibility::Always, - ); } #[track_caller] @@ -1346,14 +1338,14 @@ mod tests { check_settings_update( &mut store, r#"{ - "title_bar": { "show": "always", "name": "Max" } + "tabs": { "close_position": "left", "name": "Max" } }"# .unindent(), |settings| { - settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibility::Never); + settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left); }, r#"{ - "title_bar": { "show": "never", "name": "Max" } + "tabs": { "close_position": "left", "name": "Max" } }"# .unindent(), cx, @@ -1372,13 +1364,13 @@ mod tests { check_settings_update( &mut store, r#"{ - "title_bar": {} + "tabs": {} }"# .unindent(), - |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true), + |settings| settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left), r#"{ - "title_bar": { - "show_menus": true + "tabs": { + "close_position": "left" } }"# .unindent(), @@ -1390,14 +1382,14 @@ mod tests { &mut store, r#""#.unindent(), |settings| { - settings.title_bar = Some(TitleBarSettingsContent { - show_branch_name: Some(true), + settings.tabs = Some(ItemSettingsContent { + git_status: Some(true), ..Default::default() }) }, r#"{ - "title_bar": { - "show_branch_name": true + "tabs": { + "git_status": true } } "# @@ -1427,7 +1419,7 @@ mod tests { fn test_vscode_import(cx: &mut App) { let mut store = SettingsStore::new(cx, &test_settings()); store.register_setting::(cx); - store.register_setting::(cx); + store.register_setting::(cx); store.register_setting::(cx); // create settings that werent present @@ -1487,16 +1479,13 @@ mod tests { check_vscode_import( &mut store, r#"{ - "title_bar": { - "show": "always" - } } "# .unindent(), - r#"{ "window.titleBarStyle": "never" }"#.to_owned(), + r#"{ "workbench.editor.decorations.colors": true }"#.to_owned(), r#"{ - "title_bar": { - "show": "never" + "tabs": { + "git_status": true } } "# @@ -1550,14 +1539,15 @@ mod tests { #[gpui::test] fn test_global_settings(cx: &mut App) { let mut store = SettingsStore::new(cx, &test_settings()); - store.register_setting::(cx); + store.register_setting::(cx); // Set global settings - these should override defaults but not user settings store .set_global_settings( r#"{ - "title_bar": { - "show": "never", + "tabs": { + "close_position": "right", + "git_status": true, } }"#, cx, @@ -1566,10 +1556,10 @@ mod tests { // Before user settings, global settings should apply assert_eq!( - store.get::(None), - &TitleBarSettings { - show: TitleBarVisibility::Never, - show_branch_name: true, + store.get::(None), + &ItemSettings { + close_position: ClosePosition::Right, + git_status: true, } ); @@ -1577,8 +1567,8 @@ mod tests { store .set_user_settings( r#"{ - "title_bar": { - "show": "always" + "tabs": { + "close_position": "left" } }"#, cx, @@ -1587,10 +1577,10 @@ mod tests { // User settings should override global settings assert_eq!( - store.get::(None), - &TitleBarSettings { - show: TitleBarVisibility::Always, - show_branch_name: true, // Staff from global settings + store.get::(None), + &ItemSettings { + close_position: ClosePosition::Left, + git_status: true, // Staff from global settings } ); } diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 9f00b0ffeffe6b9744ffa67a0f52795e31e5737f..1a1ecdcd788bf53815021beb4fa449c753df28e5 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -4,7 +4,7 @@ mod onboarding_banner; pub mod platform_title_bar; mod platforms; mod system_window_tabs; -pub mod title_bar_settings; +mod title_bar_settings; #[cfg(feature = "stories")] mod stories; @@ -35,7 +35,7 @@ use remote::RemoteConnectionOptions; use settings::{Settings, SettingsLocation}; use std::{path::Path, sync::Arc}; use theme::ActiveTheme; -use title_bar_settings::{TitleBarSettings, TitleBarVisibility}; +use title_bar_settings::TitleBarSettings; use ui::{ Avatar, Button, ButtonLike, ButtonStyle, Chip, ContextMenu, Icon, IconName, IconSize, IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, h_flex, prelude::*, @@ -73,49 +73,8 @@ pub fn init(cx: &mut App) { let Some(window) = window else { return; }; - let should_show = match TitleBarSettings::get_global(cx).show { - TitleBarVisibility::Always => true, - TitleBarVisibility::Never => false, - TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(), - }; - if should_show { - let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); - workspace.set_titlebar_item(item.into(), window, cx); - } - - cx.observe_global_in::(window, |workspace, window, cx| { - let should_show = match TitleBarSettings::get_global(cx).show { - TitleBarVisibility::Always => true, - TitleBarVisibility::Never => false, - TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(), - }; - if should_show { - if workspace.titlebar_item().is_none() { - let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); - workspace.set_titlebar_item(item.into(), window, cx); - } - } else { - workspace.clear_titlebar_item(window, cx); - } - }) - .detach(); - - cx.observe_window_bounds(window, |workspace, window, cx| { - let should_show = match TitleBarSettings::get_global(cx).show { - TitleBarVisibility::Always => true, - TitleBarVisibility::Never => false, - TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(), - }; - if should_show { - if workspace.titlebar_item().is_none() { - let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); - workspace.set_titlebar_item(item.into(), window, cx); - } - } else { - workspace.clear_titlebar_item(window, cx); - } - }) - .detach(); + let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); + workspace.set_titlebar_item(item.into(), window, cx); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, action: &OpenApplicationMenu, window, cx| { diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 41d74de611dd8b99f82b6a1a723c85c27b4a2d19..712346abfb633f987458ed7b5978e35648569d6b 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -1,10 +1,8 @@ -pub use settings::TitleBarVisibility; use settings::{Settings, SettingsContent}; use ui::App; #[derive(Copy, Clone, Debug)] pub struct TitleBarSettings { - pub show: TitleBarVisibility, pub show_branch_icon: bool, pub show_onboarding_banner: bool, pub show_user_picture: bool, @@ -18,7 +16,6 @@ impl Settings for TitleBarSettings { fn from_settings(s: &SettingsContent, _: &mut App) -> Self { let content = s.title_bar.clone().unwrap(); TitleBarSettings { - show: content.show.unwrap(), show_branch_icon: content.show_branch_icon.unwrap(), show_onboarding_banner: content.show_onboarding_banner.unwrap(), show_user_picture: content.show_user_picture.unwrap(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f536cd09f1c3d6092d86de27f08310852eae99af..6318a47dd167ca8c65128dc44a4d407aae76c714 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2040,11 +2040,6 @@ impl Workspace { cx.notify(); } - pub fn clear_titlebar_item(&mut self, _: &mut Window, cx: &mut Context) { - self.titlebar_item = None; - cx.notify(); - } - pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) { self.on_prompt_for_new_path = Some(prompt) } diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 58cde307662febbd99826b8b0954dddf4984cd9d..cc25f166ce848723e73587ede29516ee91823176 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -4140,7 +4140,6 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a ```json "title_bar": { - "show": "always", "show_branch_icon": false, "show_branch_name": true, "show_project_items": true, diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index 6798542d14e448feec86cdd8cb8e6a8f61a4cc78..35adf484100fde2249098b8ddc0a2026603f5825 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -108,7 +108,6 @@ To disable this behavior use: ```json // Control which items are shown/hidden in the title bar "title_bar": { - "show": "always", // When to show: always | never | hide_in_full_screen "show_branch_icon": false, // Show/hide branch icon beside branch switcher "show_branch_name": true, // Show/hide branch name "show_project_items": true, // Show/hide project host and name From 5f6ae2361f77d6cfda51ae383879adbdbdf4048a Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 23 Sep 2025 22:31:06 -0600 Subject: [PATCH 234/721] Delete unused types for Mistral non-streaming requests (#38758) Confusing to have these interspersed with the streaming request types Release Notes: - N/A --- crates/mistral/src/mistral.rs | 40 ----------------------------------- 1 file changed, 40 deletions(-) diff --git a/crates/mistral/src/mistral.rs b/crates/mistral/src/mistral.rs index d6f62cfaa07bc211881817e6178a8673a9a670a6..2e79a8d59f67389c97ffff50fa30c4ca92318209 100644 --- a/crates/mistral/src/mistral.rs +++ b/crates/mistral/src/mistral.rs @@ -267,24 +267,6 @@ pub struct FunctionDefinition { pub parameters: Option, } -#[derive(Debug, Serialize, Deserialize)] -pub struct CompletionRequest { - pub model: String, - pub prompt: String, - pub max_tokens: u32, - pub temperature: f32, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub prediction: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub rewrite_speculation: Option, -} - -#[derive(Clone, Deserialize, Serialize, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum Prediction { - Content { content: String }, -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ToolChoice { @@ -397,21 +379,6 @@ pub struct FunctionContent { pub arguments: String, } -#[derive(Serialize, Deserialize, Debug)] -pub struct CompletionChoice { - pub text: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Response { - pub id: String, - pub object: String, - pub created: u64, - pub model: String, - pub choices: Vec, - pub usage: Usage, -} - #[derive(Serialize, Deserialize, Debug)] pub struct Usage { pub prompt_tokens: u64, @@ -419,13 +386,6 @@ pub struct Usage { pub total_tokens: u64, } -#[derive(Serialize, Deserialize, Debug)] -pub struct Choice { - pub index: u32, - pub message: RequestMessage, - pub finish_reason: Option, -} - #[derive(Serialize, Deserialize, Debug)] pub struct StreamResponse { pub id: String, From 880fff471c34fd0845fb6a45af713da340d83f71 Mon Sep 17 00:00:00 2001 From: Santiago Bernhardt <23391642+sbe-arg@users.noreply.github.com> Date: Wed, 24 Sep 2025 20:09:40 +1200 Subject: [PATCH 235/721] ollama: Add support for qwen3-coder (#38608) Release Notes: - N/A --- crates/ollama/src/ollama.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ollama/src/ollama.rs b/crates/ollama/src/ollama.rs index 48124f9625bf28a646ec4e9dc194bb1dd0df4c57..173e4e54a4a6304142e476200ad5f8788fb61853 100644 --- a/crates/ollama/src/ollama.rs +++ b/crates/ollama/src/ollama.rs @@ -37,6 +37,7 @@ fn get_max_tokens(name: &str) -> u64 { "cogito" | "command-r" | "deepseek-coder-v2" | "deepseek-r1" | "deepseek-v3" | "devstral" | "gemma3" | "gpt-oss" | "granite3.3" | "llama3.1" | "llama3.2" | "llama3.3" | "mistral-nemo" | "phi3" | "phi3.5" | "phi4" | "qwen3" | "yi-coder" => 128000, + "qwen3-coder" => 256000, _ => DEFAULT_TOKENS, } .clamp(1, MAXIMUM_TOKENS) From 9418a2f4bcb277b3992c2003e84e20687af7cc4b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 24 Sep 2025 10:10:56 +0200 Subject: [PATCH 236/721] editor: Prevent panics in `BlockChunks` if the block spans more than 128 lines (#38763) Not an ideal fix, but a proper one will require restructuring the iterator state (which would be easier if Rust had first class generators) Fixes ZED-1MB Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/display_map/block_map.rs | 10 ++++++---- crates/editor/src/display_map/tab_map.rs | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 2d16e6af8b469ff6e94b1b9fc7d11f7186e7b3c3..8937c95cef9eb0065790adf539bd488f8527ec1f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -26,7 +26,7 @@ use sum_tree::{Bias, Dimensions, SumTree, Summary, TreeMap}; use text::{BufferId, Edit}; use ui::ElementId; -const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; +const NEWLINES: &[u8] = &[b'\n'; u128::BITS as usize]; const BULLETS: &str = "********************************************************************************************************************************"; /// Tracks custom blocks such as diagnostics that should be displayed within buffer. @@ -1726,12 +1726,13 @@ impl<'a> Iterator for BlockChunks<'a> { let start_in_block = self.output_row - block_start; let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; - let line_count = end_in_block - start_in_block; + // todo: We need to split the chunk here? + let line_count = cmp::min(end_in_block - start_in_block, u128::BITS); self.output_row += line_count; return Some(Chunk { text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) }, - chars: (1 << line_count) - 1, + chars: 1u128.unbounded_shl(line_count) - 1, ..Default::default() }); } @@ -1746,6 +1747,7 @@ impl<'a> Iterator for BlockChunks<'a> { if self.transforms.item().is_some() { return Some(Chunk { text: "\n", + chars: 1, ..Default::default() }); } @@ -1773,7 +1775,7 @@ impl<'a> Iterator for BlockChunks<'a> { let chars_count = prefix.chars().count(); let bullet_len = chars_count; prefix = &BULLETS[..bullet_len]; - chars = (1 << bullet_len) - 1; + chars = 1u128.unbounded_shl(bullet_len as u32) - 1; tabs = 0; } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index e42d17123dfce9d0ca8c4faa84eabbaabf5707f4..6b0227ff3ccf1390ec2812687ce96255c55601ce 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -551,7 +551,7 @@ impl TabChunks<'_> { self.chunk = Chunk { text: &SPACES[0..(to_next_stop as usize)], is_tab: true, - chars: (1u128 << to_next_stop) - 1, + chars: 1u128.unbounded_shl(to_next_stop) - 1, ..Default::default() }; self.inside_leading_tab = to_next_stop > 0; @@ -623,7 +623,7 @@ impl<'a> Iterator for TabChunks<'a> { return Some(Chunk { text: &SPACES[..len as usize], is_tab: true, - chars: (1 << len) - 1, + chars: 1u128.unbounded_shl(len) - 1, tabs: 0, ..self.chunk.clone() }); From d5a99d079e6ed52aefa491160fe009e937616f31 Mon Sep 17 00:00:00 2001 From: tidely <43219534+tidely@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:19:52 +0300 Subject: [PATCH 237/721] ollama: Remove dead code (#38550) The `Duration` argument in `get_models` has been unused for over a year. The `complete` function is also unused and it has fallen behind in new feature additions such as Authorization support. This used to exist because ollama didn't support tools in streaming mode, `with_tools` also existed because of that. Now however there is no reason to keep this around. `ChatResponseDelta ` had unnecessary `#[allow(unused)]` macros since the fields are marked `pub`. Using `#[expect(unused)]` would've caught this. Release Notes: - N/A --- crates/language_models/src/provider/ollama.rs | 3 +- crates/ollama/src/ollama.rs | 46 ------------------- 2 files changed, 1 insertion(+), 48 deletions(-) diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index abaf503326f7178db8ebdb339dd3571ad8146552..9e9c88dd9914e73fd70d992090d01ae04465dbbc 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -105,8 +105,7 @@ impl State { // As a proxy for the server being "authenticated", we'll check if its up by fetching the models cx.spawn(async move |this, cx| { - let models = - get_models(http_client.as_ref(), &api_url, api_key.as_deref(), None).await?; + let models = get_models(http_client.as_ref(), &api_url, api_key.as_deref()).await?; let tasks = models .into_iter() diff --git a/crates/ollama/src/ollama.rs b/crates/ollama/src/ollama.rs index 173e4e54a4a6304142e476200ad5f8788fb61853..fe0b609646eb2e20090e55b1e6b6ed02fb571776 100644 --- a/crates/ollama/src/ollama.rs +++ b/crates/ollama/src/ollama.rs @@ -4,7 +4,6 @@ use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as Http use serde::{Deserialize, Serialize}; use serde_json::Value; pub use settings::KeepAlive; -use std::time::Duration; pub const OLLAMA_API_URL: &str = "http://localhost:11434"; @@ -138,14 +137,6 @@ pub struct ChatRequest { pub think: Option, } -impl ChatRequest { - pub fn with_tools(mut self, tools: Vec) -> Self { - self.stream = false; - self.tools = tools; - self - } -} - // https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values #[derive(Serialize, Default, Debug)] pub struct ChatOptions { @@ -158,14 +149,10 @@ pub struct ChatOptions { #[derive(Deserialize, Debug)] pub struct ChatResponseDelta { - #[allow(unused)] pub model: String, - #[allow(unused)] pub created_at: String, pub message: ChatMessage, - #[allow(unused)] pub done_reason: Option, - #[allow(unused)] pub done: bool, pub prompt_eval_count: Option, pub eval_count: Option, @@ -223,38 +210,6 @@ impl ModelShow { } } -pub async fn complete( - client: &dyn HttpClient, - api_url: &str, - request: ChatRequest, -) -> Result { - let uri = format!("{api_url}/api/chat"); - let request_builder = HttpRequest::builder() - .method(Method::POST) - .uri(uri) - .header("Content-Type", "application/json"); - - let serialized_request = serde_json::to_string(&request)?; - let request = request_builder.body(AsyncBody::from(serialized_request))?; - - let mut response = client.send(request).await?; - - let mut body = Vec::new(); - response.body_mut().read_to_end(&mut body).await?; - - if response.status().is_success() { - let response_message: ChatResponseDelta = serde_json::from_slice(&body)?; - Ok(response_message) - } else { - let body_str = std::str::from_utf8(&body)?; - anyhow::bail!( - "Failed to connect to API: {} {}", - response.status(), - body_str - ); - } -} - pub async fn stream_chat_completion( client: &dyn HttpClient, api_url: &str, @@ -297,7 +252,6 @@ pub async fn get_models( client: &dyn HttpClient, api_url: &str, api_key: Option<&str>, - _: Option, ) -> Result> { let uri = format!("{api_url}/api/tags"); let request = HttpRequest::builder() From c53e5ba3977f46779a78551f09f10107bed853d8 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 24 Sep 2025 10:30:11 +0200 Subject: [PATCH 238/721] editor: Fix invalid anchors in `hover_links::surrounding_filename` (#38766) Fixes ZED-1K3 Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/hover_links.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 2b91f8cb1ca4c515d2f09997f07b42d611b4baaf..7dad9f28e4b2767945f7f0aa4941c4862255c2a4 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -898,6 +898,7 @@ fn surrounding_filename( } else { // Otherwise, we skip the quote inside_quotes = true; + token_end += ch.len_utf8(); continue; } } @@ -1545,6 +1546,10 @@ mod tests { ("'fˇile.txt'", Some("file.txt")), ("ˇ'file.txt'", Some("file.txt")), ("ˇ'fi\\ le.txt'", Some("fi le.txt")), + // Quoted multibyte characters + (" ˇ\"常\"", Some("常")), + (" \"ˇ常\"", Some("常")), + ("ˇ\"常\"", Some("常")), ]; for (input, expected) in test_cases { From 5612a961b02522a5e3f6292873fec1f853643734 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:27:45 +0200 Subject: [PATCH 239/721] windows: Do not attempt to encrypt empty encrypted strings (#38774) Related to #38427 Release Notes: * N/A --- crates/askpass/src/encrypted_password.rs | 39 +++++++++++++----------- crates/remote/src/transport/ssh.rs | 1 + 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/askpass/src/encrypted_password.rs b/crates/askpass/src/encrypted_password.rs index f027faa2cda163e0aa5344623991e7082a0983ec..2684f76e8992fa5f33bbeb07996e834fccbf04fa 100644 --- a/crates/askpass/src/encrypted_password.rs +++ b/crates/askpass/src/encrypted_password.rs @@ -63,12 +63,14 @@ impl TryFrom<&str> for EncryptedPassword { if padded_length != len { value.resize(padded_length as usize, 0); } - unsafe { - CryptProtectMemory( - value.as_mut_ptr() as _, - len, - CRYPTPROTECTMEMORY_SAME_PROCESS, - )?; + if len != 0 { + unsafe { + CryptProtectMemory( + value.as_mut_ptr() as _, + len, + CRYPTPROTECTMEMORY_SAME_PROCESS, + )?; + } } Ok(Self(value, len)) } @@ -91,19 +93,22 @@ pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result { password.0.len(), CRYPTPROTECTMEMORY_BLOCK_SIZE ); - unsafe { - CryptUnprotectMemory( - password.0.as_mut_ptr() as _, - password.1, - CRYPTPROTECTMEMORY_SAME_PROCESS, - ) - .context("while decrypting a SSH password")? - }; + if password.1 != 0 { + unsafe { + CryptUnprotectMemory( + password.0.as_mut_ptr() as _, + password.1, + CRYPTPROTECTMEMORY_SAME_PROCESS, + ) + .context("while decrypting a SSH password")? + }; - { - // Remove padding - _ = password.0.drain(password.1 as usize..); + { + // Remove padding + _ = password.0.drain(password.1 as usize..); + } } + Ok(String::from_utf8(std::mem::take(&mut password.0))?) } #[cfg(not(windows))] diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 2afed3f900012e969135b82bae627b5e4a9ddac2..cd6e73d0a710840f4dd6740fe1521a75a797e440 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -354,6 +354,7 @@ impl SshRemoteConnection { &temp_dir, askpass .get_password() + .or_else(|| askpass::EncryptedPassword::try_from("").ok()) .context("Failed to fetch askpass password")?, )?; drop(askpass); From c5219e8fd2dfdc1655306bce524d0aeb345b5259 Mon Sep 17 00:00:00 2001 From: Oleksiy Syvokon Date: Wed, 24 Sep 2025 13:58:39 +0300 Subject: [PATCH 240/721] agent: Clean up git exclusions after emergency (#38775) In some rare cases, the auto-generated block gets stuck in `.git/info/exclude`. We now auto-clean it. Closes #38374 Release Notes: - Remove auto-generated block from git excludes if it gets stuck there. --- crates/git/src/repository.rs | 46 +++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 29e2dab240e83da8d4343a370970ec0cc2256601..88bfc41dfee4184a733e733eea77817c32f796e0 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -242,8 +242,20 @@ pub struct GitExcludeOverride { } impl GitExcludeOverride { + const START_BLOCK_MARKER: &str = "\n\n# ====== Auto-added by Zed: =======\n"; + const END_BLOCK_MARKER: &str = "\n# ====== End of auto-added by Zed =======\n"; + pub async fn new(git_exclude_path: PathBuf) -> Result { - let original_excludes = smol::fs::read_to_string(&git_exclude_path).await.ok(); + let original_excludes = + smol::fs::read_to_string(&git_exclude_path) + .await + .ok() + .map(|content| { + // Auto-generated lines are normally cleaned up in + // `restore_original()` or `drop()`, but may stuck in rare cases. + // Make sure to remove them. + Self::remove_auto_generated_block(&content) + }); Ok(GitExcludeOverride { git_exclude_path, @@ -260,9 +272,10 @@ impl GitExcludeOverride { }); let mut content = self.original_excludes.clone().unwrap_or_default(); - content.push_str("\n\n# ====== Auto-added by Zed: =======\n"); + + content.push_str(Self::START_BLOCK_MARKER); content.push_str(self.added_excludes.as_ref().unwrap()); - content.push('\n'); + content.push_str(Self::END_BLOCK_MARKER); smol::fs::write(&self.git_exclude_path, content).await?; Ok(()) @@ -279,6 +292,33 @@ impl GitExcludeOverride { Ok(()) } + + fn remove_auto_generated_block(content: &str) -> String { + let start_marker = Self::START_BLOCK_MARKER; + let end_marker = Self::END_BLOCK_MARKER; + let mut content = content.to_string(); + + let start_index = content.find(start_marker); + let end_index = content.rfind(end_marker); + + if let (Some(start), Some(end)) = (start_index, end_index) { + if end > start { + content.replace_range(start..end + end_marker.len(), ""); + } + } + + // Older versions of Zed didn't have end-of-block markers, + // so it's impossible to determine auto-generated lines. + // Conservatively remove the standard list of excludes + let standard_excludes = format!( + "{}{}", + Self::START_BLOCK_MARKER, + include_str!("./checkpoint.gitignore") + ); + content = content.replace(&standard_excludes, ""); + + content + } } impl Drop for GitExcludeOverride { From e1b57f00a060c8c28da3df28f9cd870b424bb101 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 24 Sep 2025 14:35:38 +0200 Subject: [PATCH 241/721] sum_tree: Reduce `Cursor` size for contextless summary types (#38776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces the size of cursor by a usize when the summary does not require a context making Cursor usages and constructions slightly more efficient. This change is a bit annoying though, as Rust has no means of specializing, so this uses a `ContextlessSummary` trait with a blanket impl while turning the `Context` into a GAT `Context<'a>`. This means `Summary` implies are a bit more verbose now while contextless ones are slimmer. It does come with the downside that the lifetime in the GAT is always considered invariant, so some lifetime splitting occurred due to that. ``` push/4096 time: [352.65 µs 360.87 µs 367.80 µs] thrpt: [10.621 MiB/s 10.825 MiB/s 11.077 MiB/s] change: time: [-2.6633% -1.3640% -0.0561%] (p = 0.05 < 0.05) thrpt: [+0.0561% +1.3828% +2.7361%] Change within noise threshold. Found 16 outliers among 100 measurements (16.00%) 7 (7.00%) low severe 3 (3.00%) low mild 2 (2.00%) high mild 4 (4.00%) high severe push/65536 time: [1.2917 ms 1.2949 ms 1.2979 ms] thrpt: [48.156 MiB/s 48.267 MiB/s 48.387 MiB/s] change: time: [+1.4428% +1.9844% +2.5299%] (p = 0.00 < 0.05) thrpt: [-2.4675% -1.9458% -1.4223%] Performance has regressed. Found 3 outliers among 100 measurements (3.00%) 1 (1.00%) low severe 1 (1.00%) low mild 1 (1.00%) high severe append/4096 time: [677.87 ns 678.87 ns 679.83 ns] thrpt: [5.6112 GiB/s 5.6192 GiB/s 5.6274 GiB/s] change: time: [-0.8924% -0.5017% -0.1705%] (p = 0.00 < 0.05) thrpt: [+0.1708% +0.5043% +0.9004%] Change within noise threshold. Found 2 outliers among 100 measurements (2.00%) 1 (1.00%) low mild 1 (1.00%) high mild append/65536 time: [9.3275 µs 9.3406 µs 9.3536 µs] thrpt: [6.5253 GiB/s 6.5344 GiB/s 6.5435 GiB/s] change: time: [+0.5409% +0.7215% +0.9054%] (p = 0.00 < 0.05) thrpt: [-0.8973% -0.7163% -0.5380%] Change within noise threshold. slice/4096 time: [27.673 µs 27.791 µs 27.907 µs] thrpt: [139.97 MiB/s 140.56 MiB/s 141.16 MiB/s] change: time: [-1.1065% -0.6725% -0.2429%] (p = 0.00 < 0.05) thrpt: [+0.2435% +0.6770% +1.1189%] Change within noise threshold. Found 5 outliers among 100 measurements (5.00%) 4 (4.00%) low mild 1 (1.00%) high mild slice/65536 time: [507.55 µs 517.40 µs 535.60 µs] thrpt: [116.69 MiB/s 120.80 MiB/s 123.14 MiB/s] change: time: [-1.3489% +0.0599% +2.2591%] (p = 0.96 > 0.05) thrpt: [-2.2092% -0.0598% +1.3674%] No change in performance detected. Found 8 outliers among 100 measurements (8.00%) 5 (5.00%) low mild 2 (2.00%) high mild 1 (1.00%) high severe bytes_in_range/4096 time: [3.3917 µs 3.4108 µs 3.4313 µs] thrpt: [1.1117 GiB/s 1.1184 GiB/s 1.1247 GiB/s] change: time: [-5.3466% -4.7193% -4.1262%] (p = 0.00 < 0.05) thrpt: [+4.3038% +4.9531% +5.6487%] Performance has improved. Found 6 outliers among 100 measurements (6.00%) 1 (1.00%) low mild 5 (5.00%) high mild bytes_in_range/65536 time: [88.175 µs 88.613 µs 89.111 µs] thrpt: [701.37 MiB/s 705.31 MiB/s 708.82 MiB/s] change: time: [-0.6935% +0.3769% +1.4655%] (p = 0.50 > 0.05) thrpt: [-1.4443% -0.3755% +0.6984%] No change in performance detected. Found 2 outliers among 100 measurements (2.00%) 2 (2.00%) high mild chars/4096 time: [678.70 ns 680.38 ns 682.08 ns] thrpt: [5.5927 GiB/s 5.6067 GiB/s 5.6206 GiB/s] change: time: [-0.6969% -0.2755% +0.1485%] (p = 0.20 > 0.05) thrpt: [-0.1483% +0.2763% +0.7018%] No change in performance detected. Found 9 outliers among 100 measurements (9.00%) 5 (5.00%) low mild 4 (4.00%) high mild chars/65536 time: [12.720 µs 12.775 µs 12.830 µs] thrpt: [4.7573 GiB/s 4.7778 GiB/s 4.7983 GiB/s] change: time: [-0.6172% -0.1110% +0.4179%] (p = 0.68 > 0.05) thrpt: [-0.4162% +0.1112% +0.6211%] No change in performance detected. Found 2 outliers among 100 measurements (2.00%) 1 (1.00%) low mild 1 (1.00%) high mild clip_point/4096 time: [33.240 µs 33.310 µs 33.394 µs] thrpt: [116.98 MiB/s 117.27 MiB/s 117.52 MiB/s] change: time: [-2.8892% -2.6305% -2.3438%] (p = 0.00 < 0.05) thrpt: [+2.4000% +2.7015% +2.9751%] Performance has improved. Found 12 outliers among 100 measurements (12.00%) 1 (1.00%) low mild 4 (4.00%) high mild 7 (7.00%) high severe clip_point/65536 time: [1.6531 ms 1.6586 ms 1.6640 ms] thrpt: [37.560 MiB/s 37.683 MiB/s 37.808 MiB/s] change: time: [-6.6381% -5.9395% -5.2680%] (p = 0.00 < 0.05) thrpt: [+5.5610% +6.3146% +7.1100%] Performance has improved. Found 7 outliers among 100 measurements (7.00%) 1 (1.00%) low mild 2 (2.00%) high mild 4 (4.00%) high severe point_to_offset/4096 time: [11.586 µs 11.603 µs 11.621 µs] thrpt: [336.15 MiB/s 336.67 MiB/s 337.16 MiB/s] change: time: [-14.289% -14.111% -13.939%] (p = 0.00 < 0.05) thrpt: [+16.197% +16.429% +16.672%] Performance has improved. Found 12 outliers among 100 measurements (12.00%) 3 (3.00%) low severe 5 (5.00%) low mild 4 (4.00%) high mild point_to_offset/65536 time: [527.74 µs 532.08 µs 536.51 µs] thrpt: [116.49 MiB/s 117.46 MiB/s 118.43 MiB/s] change: time: [-6.7825% -4.6235% -2.3533%] (p = 0.00 < 0.05) thrpt: [+2.4100% +4.8477% +7.2760%] Performance has improved. Found 8 outliers among 100 measurements (8.00%) 4 (4.00%) high mild 4 (4.00%) high severe cursor/4096 time: [16.154 µs 16.192 µs 16.232 µs] thrpt: [240.66 MiB/s 241.24 MiB/s 241.81 MiB/s] change: time: [-3.2536% -2.9145% -2.5526%] (p = 0.00 < 0.05) thrpt: [+2.6194% +3.0019% +3.3630%] Performance has improved. Found 5 outliers among 100 measurements (5.00%) 1 (1.00%) low mild 2 (2.00%) high mild 2 (2.00%) high severe cursor/65536 time: [509.60 µs 511.24 µs 512.93 µs] thrpt: [121.85 MiB/s 122.25 MiB/s 122.65 MiB/s] change: time: [-7.3677% -6.6017% -5.7840%] (p = 0.00 < 0.05) thrpt: [+6.1391% +7.0683% +7.9537%] Performance has improved. Found 6 outliers among 100 measurements (6.00%) 3 (3.00%) high mild 3 (3.00%) high severe ``` Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/buffer_diff/src/buffer_diff.rs | 6 +- crates/editor/src/display_map/block_map.rs | 70 +++-- crates/editor/src/display_map/crease_map.rs | 4 +- crates/editor/src/display_map/fold_map.rs | 84 +++--- crates/editor/src/display_map/inlay_map.rs | 74 +++-- crates/editor/src/display_map/wrap_map.rs | 74 +++-- crates/editor/src/git/blame.rs | 32 +-- crates/git/src/status.rs | 8 +- crates/gpui/src/elements/list.rs | 58 ++-- crates/language/src/diagnostic_set.rs | 6 +- crates/language/src/syntax_map.rs | 4 +- crates/multi_buffer/src/multi_buffer.rs | 252 ++++++++-------- .../notifications/src/notification_store.rs | 30 +- crates/project/src/git_store.rs | 14 +- crates/project/src/git_store/git_traversal.rs | 7 +- crates/rope/benches/rope_benchmark.rs | 30 +- crates/rope/src/rope.rs | 118 ++++---- crates/rope/src/unclipped.rs | 8 +- crates/sum_tree/src/cursor.rs | 133 +++++---- crates/sum_tree/src/sum_tree.rs | 272 ++++++++++-------- crates/sum_tree/src/tree_map.rs | 56 ++-- crates/text/src/locator.rs | 10 +- crates/text/src/operation_queue.rs | 18 +- crates/text/src/text.rs | 48 ++-- crates/text/src/undo_map.rs | 16 +- crates/worktree/src/worktree.rs | 160 +++++------ 26 files changed, 809 insertions(+), 783 deletions(-) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 22ee20e0db2810610dc2e7a4cae86dca90681337..1787f616ad365175de352e3eeeede3e1749dede4 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -111,13 +111,13 @@ impl sum_tree::Item for PendingHunk { } impl sum_tree::Summary for DiffHunkSummary { - type Context = text::BufferSnapshot; + type Context<'a> = &'a text::BufferSnapshot; - fn zero(_cx: &Self::Context) -> Self { + fn zero(_cx: Self::Context<'_>) -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { + fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { self.buffer_range.start = self .buffer_range .start diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 8937c95cef9eb0065790adf539bd488f8527ec1f..614b322703d732300e7679d9af3b6c737cb8e9b8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -22,7 +22,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, }, }; -use sum_tree::{Bias, Dimensions, SumTree, Summary, TreeMap}; +use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap}; use text::{BufferId, Edit}; use ui::ElementId; @@ -433,7 +433,7 @@ struct TransformSummary { } pub struct BlockChunks<'a> { - transforms: sum_tree::Cursor<'a, Transform, Dimensions>, + transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions>, input_chunks: wrap_map::WrapChunks<'a>, input_chunk: Chunk<'a>, output_row: u32, @@ -443,7 +443,7 @@ pub struct BlockChunks<'a> { #[derive(Clone)] pub struct BlockRows<'a> { - transforms: sum_tree::Cursor<'a, Transform, Dimensions>, + transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions>, input_rows: wrap_map::WrapRows<'a>, output_row: BlockRow, started: bool, @@ -527,7 +527,7 @@ impl BlockMap { let mut transforms = self.transforms.borrow_mut(); let mut new_transforms = SumTree::default(); - let mut cursor = transforms.cursor::(&()); + let mut cursor = transforms.cursor::(()); let mut last_block_ix = 0; let mut blocks_in_edit = Vec::new(); let mut edits = edits.into_iter().peekable(); @@ -541,20 +541,20 @@ impl BlockMap { // * Isomorphic transforms that end *at* the start of the edit // * 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), &()); + new_transforms.append(cursor.slice(&old_start, Bias::Left), ()); 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(), &()); + 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().is_some_and(|b| b.place_below()) { - new_transforms.push(transform.clone(), &()); + new_transforms.push(transform.clone(), ()); cursor.next(); } else { break; @@ -720,7 +720,7 @@ impl BlockMap { summary, block: Some(block), }, - &(), + (), ); } @@ -731,7 +731,7 @@ impl BlockMap { push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot); } - new_transforms.append(cursor.suffix(), &()); + new_transforms.append(cursor.suffix(), ()); debug_assert_eq!( new_transforms.summary().input_rows, wrap_snapshot.max_point().row() + 1 @@ -925,11 +925,11 @@ fn push_isomorphic(tree: &mut SumTree, rows: u32, wrap_snapshot: &Wra tree.update_last( |last_transform| { if last_transform.block.is_none() { - last_transform.summary.add_summary(&summary, &()); + last_transform.summary.add_summary(&summary); merged = true; } }, - &(), + (), ); if !merged { tree.push( @@ -937,7 +937,7 @@ fn push_isomorphic(tree: &mut SumTree, rows: u32, wrap_snapshot: &Wra summary, block: None, }, - &(), + (), ); } } @@ -997,7 +997,7 @@ impl BlockMapReader<'_> { .unwrap_or(self.wrap_snapshot.max_point().row() + 1), ); - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&start_wrap_row, Bias::Left); while let Some(transform) = cursor.item() { if cursor.start().0 > end_wrap_row { @@ -1313,7 +1313,7 @@ impl BlockSnapshot { ) -> BlockChunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&BlockRow(rows.start), Bias::Right); let transform_output_start = cursor.start().0.0; let transform_input_start = cursor.start().1.0; @@ -1345,7 +1345,7 @@ impl BlockSnapshot { } pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&start_row, Bias::Right); let Dimensions(output_start, input_start, _) = cursor.start(); let overshoot = if cursor @@ -1366,7 +1366,7 @@ impl BlockSnapshot { } pub fn blocks_in_range(&self, rows: Range) -> impl Iterator { - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&BlockRow(rows.start), Bias::Left); while cursor.start().0 < rows.start && cursor.end().0 <= rows.start { cursor.next(); @@ -1397,7 +1397,7 @@ impl BlockSnapshot { pub fn sticky_header_excerpt(&self, position: f32) -> Option> { let top_row = position as u32; - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&BlockRow(top_row), Bias::Right); while let Some(transform) = cursor.item() { @@ -1436,7 +1436,7 @@ impl BlockSnapshot { }; let wrap_row = WrapRow(wrap_point.row()); - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&wrap_row, Bias::Left); while let Some(transform) = cursor.item() { @@ -1464,7 +1464,7 @@ impl BlockSnapshot { } pub fn longest_row_in_range(&self, range: Range) -> BlockRow { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&range.start, Bias::Right); let mut longest_row = range.start; @@ -1515,7 +1515,7 @@ impl BlockSnapshot { } pub(super) fn line_len(&self, row: BlockRow) -> u32 { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&BlockRow(row.0), Bias::Right); if let Some(transform) = cursor.item() { let Dimensions(output_start, input_start, _) = cursor.start(); @@ -1533,13 +1533,13 @@ impl BlockSnapshot { } pub(super) fn is_block_line(&self, row: BlockRow) -> bool { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&row, Bias::Right); cursor.item().is_some_and(|t| t.block.is_some()) } pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&row, Bias::Right); let Some(transform) = cursor.item() else { return false; @@ -1551,7 +1551,7 @@ impl BlockSnapshot { let wrap_point = self .wrap_snapshot .make_wrap_point(Point::new(row.0, 0), Bias::Left); - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Right); cursor.item().is_some_and(|transform| { transform @@ -1562,7 +1562,7 @@ impl BlockSnapshot { } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&BlockRow(point.row), Bias::Right); let max_input_row = WrapRow(self.transforms.summary().input_rows); @@ -1621,7 +1621,7 @@ impl BlockSnapshot { } pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Right); if let Some(transform) = cursor.item() { if transform.block.is_some() { @@ -1639,7 +1639,7 @@ impl BlockSnapshot { } pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&BlockRow(block_point.row), Bias::Right); if let Some(transform) = cursor.item() { match transform.block.as_ref() { @@ -1848,19 +1848,17 @@ impl Iterator for BlockRows<'_> { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.summary.clone() } } -impl sum_tree::Summary for TransformSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for TransformSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { if summary.longest_row_chars > self.longest_row_chars { self.longest_row = self.output_rows + summary.longest_row; self.longest_row_chars = summary.longest_row_chars; @@ -1871,21 +1869,21 @@ impl sum_tree::Summary for TransformSummary { } impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += summary.input_rows; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += summary.output_rows; } } diff --git a/crates/editor/src/display_map/crease_map.rs b/crates/editor/src/display_map/crease_map.rs index bdac982fa785e7b6628352572ab143fd978938b2..a68c27886733d34a60ef0ce2ef4006b92b679db9 100644 --- a/crates/editor/src/display_map/crease_map.rs +++ b/crates/editor/src/display_map/crease_map.rs @@ -365,9 +365,9 @@ impl Default for ItemSummary { } impl sum_tree::Summary for ItemSummary { - type Context = MultiBufferSnapshot; + type Context<'a> = &'a MultiBufferSnapshot; - fn zero(_cx: &Self::Context) -> Self { + fn zero(_cx: Self::Context<'_>) -> Self { Default::default() } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 405f25219fa6d7bcef03c745aa34fec351d7abd3..56484a48f4a5521c1f962295daea4a9f70e8cfae 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -100,7 +100,7 @@ impl FoldPoint { pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { let mut cursor = snapshot .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&self, Bias::Right); let overshoot = self.0 - cursor.start().0.0; InlayPoint(cursor.start().1.0 + overshoot) @@ -109,7 +109,7 @@ impl FoldPoint { pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { let mut cursor = snapshot .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&self, Bias::Right); let overshoot = self.0 - cursor.start().1.output.lines; let mut offset = cursor.start().1.output.len; @@ -126,11 +126,11 @@ impl FoldPoint { } impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += &summary.output.lines; } } @@ -338,9 +338,9 @@ impl FoldMap { }, placeholder: None, }, - &(), + (), ), - inlay_snapshot: inlay_snapshot.clone(), + inlay_snapshot: inlay_snapshot, version: 0, fold_metadata_by_id: TreeMap::default(), }, @@ -382,7 +382,7 @@ impl FoldMap { if !transform.is_fold() && prev_transform_isomorphic { panic!( "found adjacent isomorphic transforms: {:?}", - self.snapshot.transforms.items(&()) + self.snapshot.transforms.items(()) ); } prev_transform_isomorphic = !transform.is_fold(); @@ -413,7 +413,7 @@ impl FoldMap { let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); let mut new_transforms = SumTree::::default(); - let mut cursor = self.snapshot.transforms.cursor::(&()); + let mut cursor = self.snapshot.transforms.cursor::(()); cursor.seek(&InlayOffset(0), Bias::Right); while let Some(mut edit) = inlay_edits_iter.next() { @@ -423,14 +423,14 @@ impl FoldMap { new_transforms.update_last( |transform| { if !transform.is_fold() { - transform.summary.add_summary(&item.summary, &()); + transform.summary.add_summary(&item.summary, ()); cursor.next(); } }, - &(), + (), ); } - new_transforms.append(cursor.slice(&edit.old.start, Bias::Left), &()); + new_transforms.append(cursor.slice(&edit.old.start, Bias::Left), ()); edit.new.start -= edit.old.start - *cursor.start(); edit.old.start = *cursor.start(); @@ -544,7 +544,7 @@ impl FoldMap { }, }), }, - &(), + (), ); } } @@ -557,7 +557,7 @@ impl FoldMap { } } - new_transforms.append(cursor.suffix(), &()); + new_transforms.append(cursor.suffix(), ()); if new_transforms.is_empty() { let text_summary = inlay_snapshot.text_summary(); push_isomorphic(&mut new_transforms, text_summary); @@ -570,9 +570,9 @@ impl FoldMap { let mut old_transforms = self .snapshot .transforms - .cursor::>(&()); + .cursor::>(()); let mut new_transforms = - new_transforms.cursor::>(&()); + new_transforms.cursor::>(()); for mut edit in inlay_edits { old_transforms.seek(&edit.old.start, Bias::Left); @@ -657,7 +657,7 @@ impl FoldSnapshot { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&range.start, Bias::Right); if let Some(transform) = cursor.item() { let start_in_transform = range.start.0 - cursor.start().0.0; @@ -708,7 +708,7 @@ impl FoldSnapshot { pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); if cursor.item().is_some_and(|t| t.is_fold()) { if bias == Bias::Left || point == cursor.start().0 { @@ -744,7 +744,7 @@ impl FoldSnapshot { let fold_point = FoldPoint::new(start_row, 0); let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&fold_point, Bias::Left); let overshoot = fold_point.0 - cursor.start().0.0; @@ -787,7 +787,7 @@ impl FoldSnapshot { { let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer); let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&inlay_offset, Bias::Right); cursor.item().is_some_and(|t| t.placeholder.is_some()) } @@ -796,7 +796,7 @@ impl FoldSnapshot { let mut inlay_point = self .inlay_snapshot .to_inlay_point(Point::new(buffer_row.0, 0)); - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&inlay_point, Bias::Right); loop { match cursor.item() { @@ -828,7 +828,7 @@ impl FoldSnapshot { ) -> FoldChunks<'a> { let mut transform_cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); transform_cursor.seek(&range.start, Bias::Right); let inlay_start = { @@ -893,7 +893,7 @@ impl FoldSnapshot { pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); if let Some(transform) = cursor.item() { let transform_start = cursor.start().0.0; @@ -925,7 +925,7 @@ fn push_isomorphic(transforms: &mut SumTree, summary: TextSummary) { did_merge = true; } }, - &(), + (), ); if !did_merge { transforms.push( @@ -936,7 +936,7 @@ fn push_isomorphic(transforms: &mut SumTree, summary: TextSummary) { }, placeholder: None, }, - &(), + (), ) } } @@ -946,7 +946,7 @@ fn intersecting_folds<'a>( folds: &'a SumTree, range: Range, inclusive: bool, -) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> { +) -> FilterCursor<'a, 'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> { let buffer = &inlay_snapshot.buffer; let start = buffer.anchor_before(range.start.to_offset(buffer)); let end = buffer.anchor_after(range.end.to_offset(buffer)); @@ -1062,19 +1062,17 @@ struct TransformSummary { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.summary.clone() } } -impl sum_tree::Summary for TransformSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for TransformSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, _: &()) { + fn add_summary(&mut self, other: &Self) { self.input += &other.input; self.output += &other.output; } @@ -1161,13 +1159,13 @@ impl Default for FoldSummary { } impl sum_tree::Summary for FoldSummary { - type Context = MultiBufferSnapshot; + type Context<'a> = &'a MultiBufferSnapshot; fn zero(_cx: &MultiBufferSnapshot) -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { + fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less { self.min_start = other.min_start; } @@ -1219,7 +1217,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { #[derive(Clone)] pub struct FoldRows<'a> { - cursor: Cursor<'a, Transform, Dimensions>, + cursor: Cursor<'a, 'static, Transform, Dimensions>, input_rows: InlayBufferRows<'a>, fold_point: FoldPoint, } @@ -1340,7 +1338,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> { } pub struct FoldChunks<'a> { - transform_cursor: Cursor<'a, Transform, Dimensions>, + transform_cursor: Cursor<'a, 'static, Transform, Dimensions>, inlay_chunks: InlayChunks<'a>, inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>, inlay_offset: InlayOffset, @@ -1488,7 +1486,7 @@ impl FoldOffset { pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint { let mut cursor = snapshot .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&self, Bias::Right); let overshoot = if cursor.item().is_none_or(|t| t.is_fold()) { Point::new(0, (self.0 - cursor.start().0.0) as u32) @@ -1504,7 +1502,7 @@ impl FoldOffset { pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { let mut cursor = snapshot .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&self, Bias::Right); let overshoot = self.0 - cursor.start().0.0; InlayOffset(cursor.start().1.0 + overshoot) @@ -1534,31 +1532,31 @@ impl Sub for FoldOffset { } impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += &summary.output.len; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += &summary.input.lines; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += &summary.input.len; } } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 9ceb0897d242f710353c2f7a90992b2a39f40958..2b2734009dc4e3c70e1a9b568357438f2eb9ae88 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -108,7 +108,7 @@ impl Inlay { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self, _: &()) -> Self::Summary { + fn summary(&self, _: ()) -> Self::Summary { match self { Transform::Isomorphic(summary) => TransformSummary { input: *summary, @@ -128,14 +128,12 @@ struct TransformSummary { output: TextSummary, } -impl sum_tree::Summary for TransformSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for TransformSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, _: &()) { + fn add_summary(&mut self, other: &Self) { self.input += &other.input; self.output += &other.output; } @@ -175,11 +173,11 @@ impl SubAssign for InlayOffset { } impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += &summary.output.len; } } @@ -204,45 +202,45 @@ impl Sub for InlayPoint { } impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += &summary.output.lines; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { *self += &summary.input.len; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { *self += &summary.input.lines; } } #[derive(Clone)] pub struct InlayBufferRows<'a> { - transforms: Cursor<'a, Transform, Dimensions>, + transforms: Cursor<'a, 'static, Transform, Dimensions>, buffer_rows: MultiBufferRows<'a>, inlay_row: u32, max_buffer_row: MultiBufferRow, } pub struct InlayChunks<'a> { - transforms: Cursor<'a, Transform, Dimensions>, + transforms: Cursor<'a, 'static, Transform, Dimensions>, buffer_chunks: CustomHighlightsChunks<'a>, buffer_chunk: Option>, inlay_chunks: Option>, @@ -542,7 +540,7 @@ impl InlayMap { let version = 0; let snapshot = InlaySnapshot { buffer: buffer.clone(), - transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()), + transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), ()), version, }; @@ -589,10 +587,10 @@ impl InlayMap { let mut new_transforms = SumTree::default(); let mut cursor = snapshot .transforms - .cursor::>(&()); + .cursor::>(()); 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), &()); + new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), ()); if let Some(Transform::Isomorphic(transform)) = cursor.item() && cursor.end().0 == buffer_edit.old.start { @@ -642,7 +640,7 @@ impl InlayMap { buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), ); - new_transforms.push(Transform::Inlay(inlay.clone()), &()); + new_transforms.push(Transform::Inlay(inlay.clone()), ()); } // Apply the rest of the edit. @@ -674,9 +672,9 @@ impl InlayMap { } } - new_transforms.append(cursor.suffix(), &()); + new_transforms.append(cursor.suffix(), ()); if new_transforms.is_empty() { - new_transforms.push(Transform::Isomorphic(Default::default()), &()); + new_transforms.push(Transform::Isomorphic(Default::default()), ()); } drop(cursor); @@ -812,7 +810,7 @@ impl InlaySnapshot { pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&offset, Bias::Right); let overshoot = offset.0 - cursor.start().0.0; match cursor.item() { @@ -842,7 +840,7 @@ impl InlaySnapshot { pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); let overshoot = point.0 - cursor.start().0.0; match cursor.item() { @@ -861,7 +859,7 @@ impl InlaySnapshot { } } pub fn to_buffer_point(&self, point: InlayPoint) -> Point { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&point, Bias::Right); match cursor.item() { Some(Transform::Isomorphic(_)) => { @@ -873,9 +871,7 @@ impl InlaySnapshot { } } pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { - let mut cursor = self - .transforms - .cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&offset, Bias::Right); match cursor.item() { Some(Transform::Isomorphic(_)) => { @@ -888,9 +884,7 @@ impl InlaySnapshot { } pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { - let mut cursor = self - .transforms - .cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&offset, Bias::Left); loop { match cursor.item() { @@ -923,7 +917,7 @@ impl InlaySnapshot { } } pub fn to_inlay_point(&self, point: Point) -> InlayPoint { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&point, Bias::Left); loop { match cursor.item() { @@ -957,7 +951,7 @@ impl InlaySnapshot { } pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&point, Bias::Left); loop { match cursor.item() { @@ -1054,9 +1048,7 @@ impl InlaySnapshot { pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self - .transforms - .cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&range.start, Bias::Right); let overshoot = range.start.0 - cursor.start().0.0; @@ -1104,7 +1096,7 @@ impl InlaySnapshot { } pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> { - let mut cursor = self.transforms.cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); let inlay_point = InlayPoint::new(row, 0); cursor.seek(&inlay_point, Bias::Left); @@ -1146,9 +1138,7 @@ impl InlaySnapshot { language_aware: bool, highlights: Highlights<'a>, ) -> InlayChunks<'a> { - let mut cursor = self - .transforms - .cursor::>(&()); + let mut cursor = self.transforms.cursor::>(()); cursor.seek(&range.start, Bias::Right); let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); @@ -1212,11 +1202,11 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { *transform += summary.take().unwrap(); } }, - &(), + (), ); if let Some(summary) = summary { - sum_tree.push(Transform::Isomorphic(summary), &()); + sum_tree.push(Transform::Isomorphic(summary), ()); } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index cd354d8229634956651ab74dd384332db0eb219e..1af9f419b14f14bcd8d79c9e260598388d4c6f1c 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -55,7 +55,7 @@ pub struct WrapChunks<'a> { input_chunk: Chunk<'a>, output_position: WrapPoint, max_output_row: u32, - transforms: Cursor<'a, Transform, Dimensions>, + transforms: Cursor<'a, 'static, Transform, Dimensions>, snapshot: &'a WrapSnapshot, } @@ -66,7 +66,7 @@ pub struct WrapRows<'a> { output_row: u32, soft_wrapped: bool, max_output_row: u32, - transforms: Cursor<'a, Transform, Dimensions>, + transforms: Cursor<'a, 'static, Transform, Dimensions>, } impl WrapRows<'_> { @@ -221,7 +221,7 @@ impl WrapMap { if !summary.lines.is_zero() { self.snapshot .transforms - .push(Transform::isomorphic(summary), &()); + .push(Transform::isomorphic(summary), ()); } let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; @@ -318,7 +318,7 @@ impl WrapSnapshot { let mut transforms = SumTree::default(); let extent = tab_snapshot.text_summary(); if !extent.lines.is_zero() { - transforms.push(Transform::isomorphic(extent), &()); + transforms.push(Transform::isomorphic(extent), ()); } Self { transforms, @@ -336,7 +336,7 @@ impl WrapSnapshot { if tab_edits.is_empty() { new_transforms = self.transforms.clone(); } else { - let mut old_cursor = self.transforms.cursor::(&()); + let mut old_cursor = self.transforms.cursor::(()); let mut tab_edits_iter = tab_edits.iter().peekable(); new_transforms = @@ -368,7 +368,7 @@ impl WrapSnapshot { old_cursor.next(); new_transforms - .append(old_cursor.slice(&next_edit.old.start, Bias::Right), &()); + .append(old_cursor.slice(&next_edit.old.start, Bias::Right), ()); } } else { if old_cursor.end() > edit.old.end { @@ -378,7 +378,7 @@ impl WrapSnapshot { new_transforms.push_or_extend(Transform::isomorphic(summary)); } old_cursor.next(); - new_transforms.append(old_cursor.suffix(), &()); + new_transforms.append(old_cursor.suffix(), ()); } } } @@ -434,7 +434,7 @@ impl WrapSnapshot { new_transforms = self.transforms.clone(); } else { let mut row_edits = row_edits.into_iter().peekable(); - let mut old_cursor = self.transforms.cursor::(&()); + let mut old_cursor = self.transforms.cursor::(()); new_transforms = old_cursor.slice( &TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0), @@ -511,7 +511,7 @@ impl WrapSnapshot { if let Some(transform) = edit_transforms.next() { new_transforms.push_or_extend(transform); } - new_transforms.extend(edit_transforms, &()); + new_transforms.extend(edit_transforms, ()); old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right); if let Some(next_edit) = row_edits.peek() { @@ -526,7 +526,7 @@ impl WrapSnapshot { new_transforms.append( old_cursor .slice(&TabPoint::new(next_edit.old_rows.start, 0), Bias::Right), - &(), + (), ); } } else { @@ -537,7 +537,7 @@ impl WrapSnapshot { new_transforms.push_or_extend(Transform::isomorphic(summary)); } old_cursor.next(); - new_transforms.append(old_cursor.suffix(), &()); + new_transforms.append(old_cursor.suffix(), ()); } } } @@ -556,8 +556,8 @@ impl WrapSnapshot { fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch { let mut wrap_edits = Vec::with_capacity(tab_edits.len()); - let mut old_cursor = self.transforms.cursor::(&()); - let mut new_cursor = new_snapshot.transforms.cursor::(&()); + let mut old_cursor = self.transforms.cursor::(()); + let mut new_cursor = new_snapshot.transforms.cursor::(()); for mut tab_edit in tab_edits.iter().cloned() { tab_edit.old.start.0.column = 0; tab_edit.old.end.0 += Point::new(1, 0); @@ -600,7 +600,7 @@ impl WrapSnapshot { let output_end = WrapPoint::new(rows.end, 0); let mut transforms = self .transforms - .cursor::>(&()); + .cursor::>(()); transforms.seek(&output_start, Bias::Right); let mut input_start = TabPoint(transforms.start().1.0); if transforms.item().is_some_and(|t| t.is_isomorphic()) { @@ -630,7 +630,7 @@ impl WrapSnapshot { pub fn line_len(&self, row: u32) -> u32 { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left); if cursor .item() @@ -657,7 +657,7 @@ impl WrapSnapshot { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&start, Bias::Right); if let Some(transform) = cursor.item() { let start_in_transform = start.0 - cursor.start().0.0; @@ -711,7 +711,7 @@ impl WrapSnapshot { } pub fn soft_wrap_indent(&self, row: u32) -> Option { - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right); cursor.item().and_then(|transform| { if transform.is_isomorphic() { @@ -729,7 +729,7 @@ impl WrapSnapshot { pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> { let mut transforms = self .transforms - .cursor::>(&()); + .cursor::>(()); transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = transforms.start().1.row(); if transforms.item().is_some_and(|t| t.is_isomorphic()) { @@ -751,7 +751,7 @@ impl WrapSnapshot { pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); let mut tab_point = cursor.start().1.0; if cursor.item().is_some_and(|t| t.is_isomorphic()) { @@ -771,14 +771,14 @@ impl WrapSnapshot { pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0)) } pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint { if bias == Bias::Left { - let mut cursor = self.transforms.cursor::(&()); + let mut cursor = self.transforms.cursor::(()); cursor.seek(&point, Bias::Right); if cursor.item().is_some_and(|t| !t.is_isomorphic()) { point = *cursor.start(); @@ -798,7 +798,7 @@ impl WrapSnapshot { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); if cursor.item().is_none() { cursor.prev(); @@ -820,7 +820,7 @@ impl WrapSnapshot { let mut cursor = self .transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&point, Bias::Right); while let Some(transform) = cursor.item() { if transform.is_isomorphic() && cursor.start().1.column() == 0 { @@ -857,7 +857,7 @@ impl WrapSnapshot { ); { - let mut transforms = self.transforms.cursor::<()>(&()).peekable(); + let mut transforms = self.transforms.cursor::<()>(()).peekable(); while let Some(transform) = transforms.next() { if let Some(next_transform) = transforms.peek() { assert!(transform.is_isomorphic() != next_transform.is_isomorphic()); @@ -1075,7 +1075,7 @@ impl Transform { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.summary.clone() } } @@ -1106,11 +1106,11 @@ impl SumTreeExt for SumTree { last_transform.summary.output += &transform.summary.output; } }, - &(), + (), ); if let Some(transform) = transform { - self.push(transform, &()); + self.push(transform, ()); } } } @@ -1137,41 +1137,39 @@ impl WrapPoint { } } -impl sum_tree::Summary for TransformSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for TransformSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, _: &()) { + fn add_summary(&mut self, other: &Self) { self.input += &other.input; self.output += &other.output; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += summary.input.lines; } } impl sum_tree::SeekTarget<'_, TransformSummary, TransformSummary> for TabPoint { - fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering { + fn cmp(&self, cursor_location: &TransformSummary, _: ()) -> std::cmp::Ordering { Ord::cmp(&self.0, &cursor_location.input.lines) } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { self.0 += summary.output.lines; } } @@ -1385,7 +1383,7 @@ mod tests { let mut summary = TextSummary::default(); for (ix, item) in wrapped_snapshot .transforms - .items(&()) + .items(()) .into_iter() .enumerate() { diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index 51719048ef81cf273bc58e7d810d66d454a04805..48a6da74467ea91630b4954fe9af38d34b8a7e96 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -38,29 +38,27 @@ pub struct GitBlameEntrySummary { impl sum_tree::Item for GitBlameEntry { type Summary = GitBlameEntrySummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { GitBlameEntrySummary { rows: self.rows } } } -impl sum_tree::Summary for GitBlameEntrySummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for GitBlameEntrySummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _cx: &()) { + fn add_summary(&mut self, summary: &Self) { self.rows += summary.rows; } } impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: &()) { + fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: ()) { *self += summary.rows; } } @@ -298,7 +296,7 @@ impl GitBlame { self.sync(cx, buffer_id); let buffer_row = info.buffer_row?; - let mut cursor = self.buffers.get(&buffer_id)?.entries.cursor::(&()); + let mut cursor = self.buffers.get(&buffer_id)?.entries.cursor::(()); cursor.seek_forward(&buffer_row, Bias::Right); Some((buffer_id, cursor.item()?.blame.clone()?)) }) @@ -406,7 +404,7 @@ impl GitBlame { .peekable(); let mut new_entries = SumTree::default(); - let mut cursor = blame_buffer.entries.cursor::(&()); + let mut cursor = blame_buffer.entries.cursor::(()); while let Some(mut edit) = row_edits.next() { while let Some(next_edit) = row_edits.peek() { @@ -419,7 +417,7 @@ impl GitBlame { } } - new_entries.append(cursor.slice(&edit.old.start, Bias::Right), &()); + new_entries.append(cursor.slice(&edit.old.start, Bias::Right), ()); if edit.new.start > new_entries.summary().rows { new_entries.push( @@ -427,7 +425,7 @@ impl GitBlame { rows: edit.new.start - new_entries.summary().rows, blame: cursor.item().and_then(|entry| entry.blame.clone()), }, - &(), + (), ); } @@ -438,7 +436,7 @@ impl GitBlame { rows: edit.new.len() as u32, blame: None, }, - &(), + (), ); } @@ -454,14 +452,14 @@ impl GitBlame { rows: cursor.end() - edit.old.end, blame: entry.blame.clone(), }, - &(), + (), ); } cursor.next(); } } - new_entries.append(cursor.suffix(), &()); + new_entries.append(cursor.suffix(), ()); drop(cursor); blame_buffer.buffer_snapshot = new_snapshot; @@ -632,7 +630,7 @@ fn build_blame_entry_sum_tree(entries: Vec, max_row: u32) -> SumTree current_row = entry.range.end; entries }), - &(), + (), ); if max_row >= current_row { @@ -641,7 +639,7 @@ fn build_blame_entry_sum_tree(entries: Vec, max_row: u32) -> SumTree rows: (max_row + 1) - current_row, blame: None, }, - &(), + (), ); } diff --git a/crates/git/src/status.rs b/crates/git/src/status.rs index 71ca14c5b2c4b82ae7dc21e832a2a07c55de8fc3..d0399a137aa9af7fc400a13105119b897a9dec1c 100644 --- a/crates/git/src/status.rs +++ b/crates/git/src/status.rs @@ -378,14 +378,12 @@ impl From for GitSummary { } } -impl sum_tree::Summary for GitSummary { - type Context = (); - - fn zero(_: &Self::Context) -> Self { +impl sum_tree::ContextLessSummary for GitSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, rhs: &Self, _: &Self::Context) { + fn add_summary(&mut self, rhs: &Self) { *self += *rhs; } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index ed4ca64e83513531b9176f05c4c00b0af71aea74..d82d7a67a12190a19acde5715378d295c2eb9bc8 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -245,7 +245,7 @@ impl ListState { ) { let state = &mut *self.0.borrow_mut(); - let mut old_items = state.items.cursor::(&()); + let mut old_items = state.items.cursor::(()); let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right); old_items.seek_forward(&Count(old_range.end), Bias::Right); @@ -255,9 +255,9 @@ impl ListState { spliced_count += 1; ListItem::Unmeasured { focus_handle } }), - &(), + (), ); - new_items.append(old_items.suffix(), &()); + new_items.append(old_items.suffix(), ()); drop(old_items); state.items = new_items; @@ -296,7 +296,7 @@ impl ListState { let current_offset = self.logical_scroll_top(); let state = &mut *self.0.borrow_mut(); - let mut cursor = state.items.cursor::(&()); + let mut cursor = state.items.cursor::(()); cursor.seek(&Count(current_offset.item_ix), Bias::Right); let start_pixel_offset = cursor.start().height + current_offset.offset_in_item; @@ -339,7 +339,7 @@ impl ListState { scroll_top.item_ix = ix; scroll_top.offset_in_item = px(0.); } else { - let mut cursor = state.items.cursor::(&()); + let mut cursor = state.items.cursor::(()); cursor.seek(&Count(ix + 1), Bias::Right); let bottom = cursor.start().height + padding.top; let goal_top = px(0.).max(bottom - height + padding.bottom); @@ -368,7 +368,7 @@ impl ListState { return None; } - let mut cursor = state.items.cursor::>(&()); + let mut cursor = state.items.cursor::>(()); cursor.seek(&Count(scroll_top.item_ix), Bias::Right); let scroll_top = cursor.start().1.0 + scroll_top.offset_in_item; @@ -426,7 +426,7 @@ impl ListState { let state = &self.0.borrow(); let logical_scroll_top = state.logical_scroll_top(); - let mut cursor = state.items.cursor::(&()); + let mut cursor = state.items.cursor::(()); let summary: ListItemSummary = cursor.summary(&Count(logical_scroll_top.item_ix), Bias::Right); let content_height = state.items.summary().height; @@ -446,7 +446,7 @@ impl ListState { impl StateInner { fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range { - let mut cursor = self.items.cursor::(&()); + let mut cursor = self.items.cursor::(()); cursor.seek(&Count(scroll_top.item_ix), Bias::Right); let start_y = cursor.start().height + scroll_top.offset_in_item; cursor.seek_forward(&Height(start_y + height), Bias::Left); @@ -478,7 +478,7 @@ impl StateInner { if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max { self.logical_scroll_top = None; } else { - let mut cursor = self.items.cursor::(&()); + let mut cursor = self.items.cursor::(()); cursor.seek(&Height(new_scroll_top), Bias::Right); let item_ix = cursor.start().count; let offset_in_item = new_scroll_top - cursor.start().height; @@ -519,7 +519,7 @@ impl StateInner { } fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels { - let mut cursor = self.items.cursor::(&()); + let mut cursor = self.items.cursor::(()); cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right); cursor.start().height + logical_scroll_top.offset_in_item } @@ -548,7 +548,7 @@ impl StateInner { AvailableSpace::MinContent, ); - let mut cursor = old_items.cursor::(&()); + let mut cursor = old_items.cursor::(()); // Render items after the scroll top, including those in the trailing overdraw cursor.seek(&Count(scroll_top.item_ix), Bias::Right); @@ -663,11 +663,11 @@ impl StateInner { } let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); - let mut cursor = old_items.cursor::(&()); + let mut cursor = old_items.cursor::(()); let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right); - new_items.extend(measured_items, &()); + new_items.extend(measured_items, ()); cursor.seek(&Count(measured_range.end), Bias::Right); - new_items.append(cursor.suffix(), &()); + new_items.append(cursor.suffix(), ()); self.items = new_items; // If none of the visible items are focused, check if an off-screen item is focused @@ -676,7 +676,7 @@ impl StateInner { if !rendered_focused_item { let mut cursor = self .items - .filter::<_, Count>(&(), |summary| summary.has_focus_handles); + .filter::<_, Count>((), |summary| summary.has_focus_handles); cursor.next(); while let Some(item) = cursor.item() { if item.contains_focused(window, cx) { @@ -741,7 +741,7 @@ impl StateInner { offset_in_item: autoscroll_bounds.top() - item_origin.y, }); } else if autoscroll_bounds.bottom() > bounds.bottom() { - let mut cursor = self.items.cursor::(&()); + let mut cursor = self.items.cursor::(()); cursor.seek(&Count(item.index), Bias::Right); let mut height = bounds.size.height - padding.top - padding.bottom; @@ -802,7 +802,7 @@ impl StateInner { if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max { self.logical_scroll_top = None; } else { - let mut cursor = self.items.cursor::(&()); + let mut cursor = self.items.cursor::(()); cursor.seek(&Height(new_scroll_top), Bias::Right); let item_ix = cursor.start().count; @@ -946,7 +946,7 @@ impl Element for List { state.items.iter().map(|item| ListItem::Unmeasured { focus_handle: item.focus_handle(), }), - &(), + (), ); state.items = new_items; @@ -1027,7 +1027,7 @@ impl Styled for List { impl sum_tree::Item for ListItem { type Summary = ListItemSummary; - fn summary(&self, _: &()) -> Self::Summary { + fn summary(&self, _: ()) -> Self::Summary { match self { ListItem::Unmeasured { focus_handle } => ListItemSummary { count: 1, @@ -1049,14 +1049,12 @@ impl sum_tree::Item for ListItem { } } -impl sum_tree::Summary for ListItemSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for ListItemSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { self.count += summary.count; self.rendered_count += summary.rendered_count; self.unrendered_count += summary.unrendered_count; @@ -1066,33 +1064,33 @@ impl sum_tree::Summary for ListItemSummary { } impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: ()) { self.0 += summary.count; } } impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: ()) { self.0 += summary.height; } } impl sum_tree::SeekTarget<'_, ListItemSummary, ListItemSummary> for Count { - fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering { + fn cmp(&self, other: &ListItemSummary, _: ()) -> std::cmp::Ordering { self.0.partial_cmp(&other.count).unwrap() } } impl sum_tree::SeekTarget<'_, ListItemSummary, ListItemSummary> for Height { - fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering { + fn cmp(&self, other: &ListItemSummary, _: ()) -> std::cmp::Ordering { self.0.partial_cmp(&other.height).unwrap() } } diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index 613c445652fbcfe87232afec559480ce943b15e3..40e745cf9037f8f1d74b6cff22d3f38aa1b8cc65 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -269,13 +269,13 @@ impl Default for Summary { } impl sum_tree::Summary for Summary { - type Context = text::BufferSnapshot; + type Context<'a> = &'a text::BufferSnapshot; - fn zero(_cx: &Self::Context) -> Self { + fn zero(_cx: Self::Context<'_>) -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { + fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { if other.min_start.cmp(&self.min_start, buffer).is_lt() { self.min_start = other.min_start; } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 38aad007fe16c655a3802bd70c9b709cbe83ea68..528b25d47193ebdbbf0dbe12cd18c66e31ad37d0 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1775,13 +1775,13 @@ impl Default for SyntaxLayerSummary { } impl sum_tree::Summary for SyntaxLayerSummary { - type Context = BufferSnapshot; + type Context<'a> = &'a BufferSnapshot; fn zero(_cx: &BufferSnapshot) -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { + fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { if other.max_depth > self.max_depth { self.max_depth = other.max_depth; self.range = other.range.clone(); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index d5f22019235f74e32d839123cf5f8e8a3cae87d1..cec1297e6fd52a8f2be273fb1b375b6610190416 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -412,7 +412,7 @@ struct Excerpt { #[derive(Clone)] pub struct MultiBufferExcerpt<'a> { excerpt: &'a Excerpt, - diff_transforms: sum_tree::Cursor<'a, DiffTransform, DiffTransforms>, + diff_transforms: sum_tree::Cursor<'a, 'static, DiffTransform, DiffTransforms>, offset: usize, excerpt_offset: ExcerptDimension, buffer_offset: usize, @@ -468,8 +468,8 @@ pub struct MultiBufferRows<'a> { } pub struct MultiBufferChunks<'a> { - excerpts: Cursor<'a, Excerpt, ExcerptOffset>, - diff_transforms: Cursor<'a, DiffTransform, Dimensions>, + excerpts: Cursor<'a, 'static, Excerpt, ExcerptOffset>, + diff_transforms: Cursor<'a, 'static, DiffTransform, Dimensions>, diffs: &'a TreeMap, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, buffer_chunk: Option>, @@ -507,7 +507,7 @@ struct DiffTransforms { } impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransforms { - fn zero(cx: &::Context) -> Self { + fn zero(cx: ::Context<'_>) -> Self { Self { output_dimension: OutputDimension::zero(cx), excerpt_dimension: as Dimension<'a, DiffTransformSummary>>::zero( @@ -519,7 +519,7 @@ impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransform fn add_summary( &mut self, summary: &'a DiffTransformSummary, - cx: &::Context, + cx: ::Context<'_>, ) { self.output_dimension.add_summary(summary, cx); self.excerpt_dimension.add_summary(summary, cx); @@ -528,8 +528,8 @@ impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransform #[derive(Clone)] struct MultiBufferCursor<'a, D: TextDimension> { - excerpts: Cursor<'a, Excerpt, ExcerptDimension>, - diff_transforms: Cursor<'a, DiffTransform, DiffTransforms>, + excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension>, + diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms>, diffs: &'a TreeMap, cached_region: Option>, } @@ -1196,7 +1196,7 @@ impl MultiBuffer { let mut ranges = Vec::new(); let snapshot = self.read(cx); let buffers = self.buffers.borrow(); - let mut cursor = snapshot.excerpts.cursor::(&()); + let mut cursor = snapshot.excerpts.cursor::(()); for (buffer_id, buffer_transaction) in &transaction.buffer_transactions { let Some(buffer_state) = buffers.get(buffer_id) else { @@ -1311,7 +1311,7 @@ impl MultiBuffer { let mut selections_by_buffer: HashMap>> = Default::default(); let snapshot = self.read(cx); - let mut cursor = snapshot.excerpts.cursor::>(&()); + let mut cursor = snapshot.excerpts.cursor::>(()); for selection in selections { let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id); let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id); @@ -1762,7 +1762,7 @@ impl MultiBuffer { let mut next_excerpt_id = move || ExcerptId(post_inc(&mut next_excerpt_id)); - let mut excerpts_cursor = snapshot.excerpts.cursor::>(&()); + let mut excerpts_cursor = snapshot.excerpts.cursor::>(()); excerpts_cursor.next(); loop { @@ -1963,7 +1963,7 @@ impl MultiBuffer { let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone(); let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids); - let mut cursor = snapshot.excerpts.cursor::>(&()); + let mut cursor = snapshot.excerpts.cursor::>(()); let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right); prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone(); @@ -1972,7 +1972,7 @@ impl MultiBuffer { |excerpt| { excerpt.has_trailing_newline = true; }, - &(), + (), ); let next_locator = if let Some(excerpt) = cursor.item() { @@ -2002,20 +2002,20 @@ impl MultiBuffer { range, ranges.peek().is_some() || cursor.item().is_some(), ); - new_excerpts.push(excerpt, &()); + new_excerpts.push(excerpt, ()); prev_locator = locator.clone(); if let Some(last_mapping_entry) = new_excerpt_ids.last() { assert!(id > last_mapping_entry.id, "excerpt ids must be increasing"); } - new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &()); + new_excerpt_ids.push(ExcerptIdMapping { id, locator }, ()); } let edit_end = ExcerptOffset::new(new_excerpts.summary().text.len); let suffix = cursor.suffix(); let changed_trailing_excerpt = suffix.is_empty(); - new_excerpts.append(suffix, &()); + new_excerpts.append(suffix, ()); drop(cursor); snapshot.excerpts = new_excerpts; snapshot.excerpt_ids = new_excerpt_ids; @@ -2091,7 +2091,7 @@ impl MultiBuffer { let mut excerpts = Vec::new(); let snapshot = self.read(cx); let buffers = self.buffers.borrow(); - let mut cursor = snapshot.excerpts.cursor::>(&()); + let mut cursor = snapshot.excerpts.cursor::>(()); if let Some(locators) = buffers.get(&buffer_id).map(|state| &state.excerpts) { for locator in locators { cursor.seek_forward(&Some(locator), Bias::Left); @@ -2111,10 +2111,10 @@ impl MultiBuffer { let buffers = self.buffers.borrow(); let mut excerpts = snapshot .excerpts - .cursor::, ExcerptDimension>>(&()); + .cursor::, ExcerptDimension>>(()); let mut diff_transforms = snapshot .diff_transforms - .cursor::, OutputDimension>>(&()); + .cursor::, OutputDimension>>(()); diff_transforms.next(); let locators = buffers .get(&buffer_id) @@ -2280,7 +2280,7 @@ impl MultiBuffer { let mut new_excerpts = SumTree::default(); let mut cursor = snapshot .excerpts - .cursor::, ExcerptOffset>>(&()); + .cursor::, ExcerptOffset>>(()); let mut edits = Vec::new(); let mut excerpt_ids = ids.iter().copied().peekable(); let mut removed_buffer_ids = Vec::new(); @@ -2289,7 +2289,7 @@ impl MultiBuffer { self.paths_by_excerpt.remove(&excerpt_id); // Seek to the next excerpt to remove, preserving any preceding excerpts. let locator = snapshot.excerpt_locator_for_id(excerpt_id); - new_excerpts.append(cursor.slice(&Some(locator), Bias::Left), &()); + new_excerpts.append(cursor.slice(&Some(locator), Bias::Left), ()); if let Some(mut excerpt) = cursor.item() { if excerpt.id != excerpt_id { @@ -2331,7 +2331,7 @@ impl MultiBuffer { // the previous excerpt. if cursor.item().is_none() && old_start.value > 0 { old_start.value -= 1; - new_excerpts.update_last(|e| e.has_trailing_newline = false, &()); + new_excerpts.update_last(|e| e.has_trailing_newline = false, ()); } // Push an edit for the removal of this run of excerpts. @@ -2345,7 +2345,7 @@ impl MultiBuffer { } let suffix = cursor.suffix(); let changed_trailing_excerpt = suffix.is_empty(); - new_excerpts.append(suffix, &()); + new_excerpts.append(suffix, ()); drop(cursor); snapshot.excerpts = new_excerpts; for buffer_id in &removed_buffer_ids { @@ -2487,7 +2487,7 @@ impl MultiBuffer { for locator in &buffer_state.excerpts { let mut cursor = snapshot .excerpts - .cursor::, ExcerptOffset>>(&()); + .cursor::, ExcerptOffset>>(()); cursor.seek_forward(&Some(locator), Bias::Left); if let Some(excerpt) = cursor.item() && excerpt.locator == *locator @@ -2721,7 +2721,7 @@ impl MultiBuffer { pub fn single_hunk_is_expanded(&self, range: Range, cx: &App) -> bool { let snapshot = self.read(cx); - let mut cursor = snapshot.diff_transforms.cursor::(&()); + let mut cursor = snapshot.diff_transforms.cursor::(()); let offset_range = range.to_offset(&snapshot); cursor.seek(&offset_range.start, Bias::Left); while let Some(item) = cursor.item() { @@ -2738,7 +2738,7 @@ impl MultiBuffer { pub fn has_expanded_diff_hunks_in_ranges(&self, ranges: &[Range], cx: &App) -> bool { let snapshot = self.read(cx); - let mut cursor = snapshot.diff_transforms.cursor::(&()); + let mut cursor = snapshot.diff_transforms.cursor::(()); for range in ranges { let range = range.to_point(&snapshot); let start = snapshot.point_to_offset(Point::new(range.start.row, 0)); @@ -2844,11 +2844,11 @@ impl MultiBuffer { let mut new_excerpts = SumTree::default(); let mut cursor = snapshot .excerpts - .cursor::, ExcerptOffset>>(&()); + .cursor::, ExcerptOffset>>(()); let mut edits = Vec::>::new(); let prefix = cursor.slice(&Some(locator), Bias::Left); - new_excerpts.append(prefix, &()); + new_excerpts.append(prefix, ()); let mut excerpt = cursor.item().unwrap().clone(); let old_text_len = ExcerptOffset::new(excerpt.text_summary.len); @@ -2880,11 +2880,11 @@ impl MultiBuffer { edits.push(edit); } - new_excerpts.push(excerpt, &()); + new_excerpts.push(excerpt, ()); cursor.next(); - new_excerpts.append(cursor.suffix(), &()); + new_excerpts.append(cursor.suffix(), ()); drop(cursor); snapshot.excerpts = new_excerpts; @@ -2920,12 +2920,12 @@ impl MultiBuffer { let mut new_excerpts = SumTree::default(); let mut cursor = snapshot .excerpts - .cursor::, ExcerptOffset>>(&()); + .cursor::, ExcerptOffset>>(()); let mut edits = Vec::>::new(); for locator in &locators { let prefix = cursor.slice(&Some(locator), Bias::Left); - new_excerpts.append(prefix, &()); + new_excerpts.append(prefix, ()); let mut excerpt = cursor.item().unwrap().clone(); let old_text_len = ExcerptOffset::new(excerpt.text_summary.len); @@ -2984,12 +2984,12 @@ impl MultiBuffer { edits.push(edit); } - new_excerpts.push(excerpt, &()); + new_excerpts.push(excerpt, ()); cursor.next(); } - new_excerpts.append(cursor.suffix(), &()); + new_excerpts.append(cursor.suffix(), ()); drop(cursor); snapshot.excerpts = new_excerpts; @@ -3066,10 +3066,10 @@ impl MultiBuffer { let mut new_excerpts = SumTree::default(); let mut cursor = snapshot .excerpts - .cursor::, ExcerptOffset>>(&()); + .cursor::, ExcerptOffset>>(()); for (locator, buffer, buffer_edited) in excerpts_to_edit { - new_excerpts.append(cursor.slice(&Some(locator), Bias::Left), &()); + new_excerpts.append(cursor.slice(&Some(locator), Bias::Left), ()); let old_excerpt = cursor.item().unwrap(); let buffer = buffer.read(cx); let buffer_id = buffer.remote_id(); @@ -3110,10 +3110,10 @@ impl MultiBuffer { new_excerpt.buffer = buffer.snapshot(); } - new_excerpts.push(new_excerpt, &()); + new_excerpts.push(new_excerpt, ()); cursor.next(); } - new_excerpts.append(cursor.suffix(), &()); + new_excerpts.append(cursor.suffix(), ()); drop(cursor); snapshot.excerpts = new_excerpts; @@ -3131,10 +3131,10 @@ impl MultiBuffer { return; } - let mut excerpts = snapshot.excerpts.cursor::(&()); + let mut excerpts = snapshot.excerpts.cursor::(()); let mut old_diff_transforms = snapshot .diff_transforms - .cursor::>(&()); + .cursor::>(()); let mut new_diff_transforms = SumTree::default(); let mut old_expanded_hunks = HashSet::default(); let mut output_edits = Vec::new(); @@ -3241,7 +3241,7 @@ impl MultiBuffer { summary: Default::default(), inserted_hunk_info: None, }, - &(), + (), ); } @@ -3399,7 +3399,7 @@ impl MultiBuffer { hunk_info, has_trailing_newline, }, - &(), + (), ); } @@ -3436,13 +3436,13 @@ impl MultiBuffer { *summary, ) { - let mut cursor = subtree.cursor::<()>(&()); + let mut cursor = subtree.cursor::<()>(()); cursor.next(); cursor.next(); - new_transforms.append(cursor.suffix(), &()); + new_transforms.append(cursor.suffix(), ()); return; } - new_transforms.append(subtree, &()); + new_transforms.append(subtree, ()); } fn push_diff_transform( @@ -3462,7 +3462,7 @@ impl MultiBuffer { { return; } - new_transforms.push(transform, &()); + new_transforms.push(transform, ()); } fn push_buffer_content_transform( @@ -3496,7 +3496,7 @@ impl MultiBuffer { summary: summary_to_add, inserted_hunk_info, }, - &(), + (), ) } } @@ -3521,7 +3521,7 @@ impl MultiBuffer { did_extend = true; } }, - &(), + (), ); did_extend } @@ -4033,7 +4033,7 @@ impl MultiBufferSnapshot { cursor.seek(&query_range.start); if let Some(region) = cursor.region().filter(|region| !region.is_main_buffer) - && region.range.start > D::zero(&()) + && region.range.start > D::zero(()) { cursor.prev() } @@ -4368,8 +4368,8 @@ impl MultiBufferSnapshot { let mut chunks = MultiBufferChunks { excerpt_offset_range: ExcerptOffset::new(0)..ExcerptOffset::new(0), range: 0..0, - excerpts: self.excerpts.cursor(&()), - diff_transforms: self.diff_transforms.cursor(&()), + excerpts: self.excerpts.cursor(()), + diff_transforms: self.diff_transforms.cursor(()), diffs: &self.diffs, diff_base_chunks: None, excerpt_chunks: None, @@ -4706,7 +4706,7 @@ impl MultiBufferSnapshot { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut cursor = self .diff_transforms - .cursor::>(&()); + .cursor::>(()); cursor.seek(&range.start, Bias::Right); let Some(first_transform) = cursor.item() else { @@ -4801,8 +4801,8 @@ impl MultiBufferSnapshot { D: TextDimension, { // let mut range = range.start..range.end; - let mut summary = D::zero(&()); - let mut cursor = self.excerpts.cursor::(&()); + let mut summary = D::zero(()); + let mut cursor = self.excerpts.cursor::(()); cursor.seek(&range.start, Bias::Right); if let Some(excerpt) = cursor.item() { let mut end_before_newline = cursor.end(); @@ -4920,7 +4920,7 @@ impl MultiBufferSnapshot { fn excerpt_offset_for_anchor(&self, anchor: &Anchor) -> ExcerptOffset { let mut cursor = self .excerpts - .cursor::, ExcerptOffset>>(&()); + .cursor::, ExcerptOffset>>(()); let locator = self.excerpt_locator_for_id(anchor.excerpt_id); cursor.seek(&Some(locator), Bias::Left); @@ -4960,10 +4960,10 @@ impl MultiBufferSnapshot { I: 'a + IntoIterator, { let mut anchors = anchors.into_iter().peekable(); - let mut cursor = self.excerpts.cursor::(&()); + let mut cursor = self.excerpts.cursor::(()); let mut diff_transforms_cursor = self .diff_transforms - .cursor::, OutputDimension>>(&()); + .cursor::, OutputDimension>>(()); diff_transforms_cursor.next(); let mut summaries = Vec::new(); @@ -5081,7 +5081,7 @@ impl MultiBufferSnapshot { I: 'a + IntoIterator, { let mut anchors = anchors.into_iter().enumerate().peekable(); - let mut cursor = self.excerpts.cursor::>(&()); + let mut cursor = self.excerpts.cursor::>(()); cursor.next(); let mut result = Vec::new(); @@ -5195,7 +5195,7 @@ impl MultiBufferSnapshot { // offset in the excerpts, and whether the position is within a deleted hunk. let mut diff_transforms = self .diff_transforms - .cursor::>(&()); + .cursor::>(()); diff_transforms.seek(&offset, Bias::Right); if offset == diff_transforms.start().0 @@ -5232,7 +5232,7 @@ impl MultiBufferSnapshot { let mut excerpts = self .excerpts - .cursor::>>(&()); + .cursor::>>(()); excerpts.seek(&excerpt_offset, Bias::Right); if excerpts.item().is_none() && excerpt_offset == excerpts.start().0 && bias == Bias::Left { excerpts.prev(); @@ -5277,7 +5277,7 @@ impl MultiBufferSnapshot { ) -> Option { let excerpt_id = self.latest_excerpt_id(excerpt_id); let locator = self.excerpt_locator_for_id(excerpt_id); - let mut cursor = self.excerpts.cursor::>(&()); + let mut cursor = self.excerpts.cursor::>(()); cursor.seek(locator, Bias::Left); if let Some(excerpt) = cursor.item() && excerpt.id == excerpt_id @@ -5317,8 +5317,8 @@ impl MultiBufferSnapshot { } fn cursor(&self) -> MultiBufferCursor<'_, D> { - let excerpts = self.excerpts.cursor(&()); - let diff_transforms = self.diff_transforms.cursor(&()); + let excerpts = self.excerpts.cursor(()); + let diff_transforms = self.diff_transforms.cursor(()); MultiBufferCursor { excerpts, diff_transforms, @@ -5331,11 +5331,11 @@ impl MultiBufferSnapshot { let start_locator = self.excerpt_locator_for_id(id); let mut excerpts = self .excerpts - .cursor::, ExcerptDimension>>(&()); + .cursor::, ExcerptDimension>>(()); excerpts.seek(&Some(start_locator), Bias::Left); excerpts.prev(); - let mut diff_transforms = self.diff_transforms.cursor::>(&()); + let mut diff_transforms = self.diff_transforms.cursor::>(()); diff_transforms.seek(&excerpts.start().1, Bias::Left); if diff_transforms.end().excerpt_dimension < excerpts.start().1 { diff_transforms.next(); @@ -6188,7 +6188,7 @@ impl MultiBufferSnapshot { } else if id == ExcerptId::max() { Locator::max_ref() } else { - let mut cursor = self.excerpt_ids.cursor::(&()); + let mut cursor = self.excerpt_ids.cursor::(()); cursor.seek(&id, Bias::Left); if let Some(entry) = cursor.item() && entry.id == id @@ -6223,7 +6223,7 @@ impl MultiBufferSnapshot { } } - let mut cursor = self.excerpt_ids.cursor::(&()); + let mut cursor = self.excerpt_ids.cursor::(()); for id in sorted_ids { if cursor.seek_forward(&id, Bias::Left) { locators.push(cursor.item().unwrap().locator.clone()); @@ -6247,14 +6247,14 @@ impl MultiBufferSnapshot { pub fn range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option> { let mut cursor = self .excerpts - .cursor::, ExcerptDimension>>(&()); + .cursor::, ExcerptDimension>>(()); let locator = self.excerpt_locator_for_id(excerpt_id); if cursor.seek(&Some(locator), Bias::Left) { let start = cursor.start().1.clone(); let end = cursor.end().1; let mut diff_transforms = self .diff_transforms - .cursor::, OutputDimension>>(&()); + .cursor::, OutputDimension>>(()); diff_transforms.seek(&start, Bias::Left); let overshoot = start.0 - diff_transforms.start().0.0; let start = diff_transforms.start().1.0 + overshoot; @@ -6268,7 +6268,7 @@ impl MultiBufferSnapshot { } pub fn buffer_range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option> { - let mut cursor = self.excerpts.cursor::>(&()); + let mut cursor = self.excerpts.cursor::>(()); let locator = self.excerpt_locator_for_id(excerpt_id); if cursor.seek(&Some(locator), Bias::Left) && let Some(excerpt) = cursor.item() @@ -6279,7 +6279,7 @@ impl MultiBufferSnapshot { } fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> { - let mut cursor = self.excerpts.cursor::>(&()); + let mut cursor = self.excerpts.cursor::>(()); let locator = self.excerpt_locator_for_id(excerpt_id); cursor.seek(&Some(locator), Bias::Left); if let Some(excerpt) = cursor.item() @@ -6334,7 +6334,7 @@ impl MultiBufferSnapshot { range: &'a Range, include_local: bool, ) -> impl 'a + Iterator)> { - let mut cursor = self.excerpts.cursor::(&()); + let mut cursor = self.excerpts.cursor::(()); let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id); let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id); cursor.seek(start_locator, Bias::Left); @@ -6448,8 +6448,8 @@ impl MultiBufferSnapshot { #[cfg(any(test, feature = "test-support"))] fn check_invariants(&self) { - let excerpts = self.excerpts.items(&()); - let excerpt_ids = self.excerpt_ids.items(&()); + let excerpts = self.excerpts.items(()); + let excerpt_ids = self.excerpt_ids.items(()); for (ix, excerpt) in excerpts.iter().enumerate() { if ix == 0 { @@ -6476,7 +6476,7 @@ impl MultiBufferSnapshot { "incorrect input summary. expected {:?}, got {:?}. transforms: {:+?}", self.excerpts.summary().text.len, self.diff_transforms.summary().input, - self.diff_transforms.items(&()), + self.diff_transforms.items(()), ); } @@ -6495,7 +6495,7 @@ impl MultiBufferSnapshot { { panic!( "multiple adjacent buffer content transforms with is_inserted_hunk = {inserted_hunk_info:?}. transforms: {:+?}", - self.diff_transforms.items(&()) + self.diff_transforms.items(()) ); } if summary.len == 0 && !self.is_empty() { @@ -7243,7 +7243,7 @@ impl fmt::Debug for Excerpt { impl sum_tree::Item for Excerpt { type Summary = ExcerptSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { let mut text = self.text_summary; if self.has_trailing_newline { text += TextSummary::from("\n"); @@ -7260,7 +7260,7 @@ impl sum_tree::Item for Excerpt { impl sum_tree::Item for ExcerptIdMapping { type Summary = ExcerptId; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.id } } @@ -7287,7 +7287,7 @@ impl DiffTransform { impl sum_tree::Item for DiffTransform { type Summary = DiffTransformSummary; - fn summary(&self, _: &::Context) -> Self::Summary { + fn summary(&self, _: ::Context<'_>) -> Self::Summary { match self { DiffTransform::BufferContent { summary, .. } => DiffTransformSummary { input: *summary, @@ -7307,83 +7307,77 @@ impl DiffTransformSummary { } } -impl sum_tree::Summary for DiffTransformSummary { - type Context = (); - - fn zero(_: &Self::Context) -> Self { +impl sum_tree::ContextLessSummary for DiffTransformSummary { + fn zero() -> Self { DiffTransformSummary { input: TextSummary::default(), output: TextSummary::default(), } } - fn add_summary(&mut self, summary: &Self, _: &Self::Context) { - self.input += &summary.input; - self.output += &summary.output; + fn add_summary(&mut self, other: &Self) { + self.input += other.input; + self.output += other.output; } } -impl sum_tree::Summary for ExcerptId { - type Context = (); - - fn zero(_cx: &()) -> Self { - Default::default() +impl sum_tree::ContextLessSummary for ExcerptId { + fn zero() -> Self { + Self(0) } - fn add_summary(&mut self, other: &Self, _: &()) { - *self = *other; + fn add_summary(&mut self, summary: &Self) { + *self = cmp::max(*self, *summary); } } -impl sum_tree::Summary for ExcerptSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { - Default::default() +impl sum_tree::ContextLessSummary for ExcerptSummary { + fn zero() -> Self { + Self::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { debug_assert!(summary.excerpt_locator > self.excerpt_locator); self.excerpt_locator = summary.excerpt_locator.clone(); - Summary::add_summary(&mut self.text, &summary.text, &()); + Summary::add_summary(&mut self.text, &summary.text, ()); self.widest_line_number = cmp::max(self.widest_line_number, summary.widest_line_number); } } impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for ExcerptOffset { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { self.value += summary.text.len; } } impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for ExcerptOffset { - fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + fn cmp(&self, cursor_location: &ExcerptSummary, _: ()) -> cmp::Ordering { Ord::cmp(&self.value, &cursor_location.text.len) } } impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator { - fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering { + fn cmp(&self, cursor_location: &Option<&'a Locator>, _: ()) -> cmp::Ordering { Ord::cmp(&Some(self), cursor_location) } } impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for Locator { - fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + fn cmp(&self, cursor_location: &ExcerptSummary, _: ()) -> cmp::Ordering { Ord::cmp(self, &cursor_location.excerpt_locator) } } impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for ExcerptPoint { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { self.value += summary.text.lines; } } @@ -7391,31 +7385,31 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for ExcerptPoint { impl<'a, D: TextDimension + Default> sum_tree::Dimension<'a, ExcerptSummary> for ExcerptDimension { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { ExcerptDimension(D::default()) } - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { self.0.add_assign(&D::from_text_summary(&summary.text)) } } impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { *self = Some(&summary.excerpt_locator); } } impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { *self = Some(summary.excerpt_id); } } @@ -7427,21 +7421,21 @@ struct ExcerptDimension(T); struct OutputDimension(T); impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for ExcerptOffset { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { ExcerptOffset::new(0) } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { self.value += summary.input.len; } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for ExcerptPoint { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { ExcerptPoint::new(0, 0) } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { self.value += summary.input.lines; } } @@ -7449,7 +7443,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for ExcerptPoint { impl sum_tree::SeekTarget<'_, DiffTransformSummary, DiffTransformSummary> for ExcerptDimension { - fn cmp(&self, cursor_location: &DiffTransformSummary, _: &()) -> cmp::Ordering { + fn cmp(&self, cursor_location: &DiffTransformSummary, _: ()) -> cmp::Ordering { Ord::cmp(&self.0, &D::from_text_summary(&cursor_location.input)) } } @@ -7457,17 +7451,17 @@ impl sum_tree::SeekTarget<'_, DiffTransformSummary, Diff impl sum_tree::SeekTarget<'_, DiffTransformSummary, DiffTransforms> for ExcerptDimension { - fn cmp(&self, cursor_location: &DiffTransforms, _: &()) -> cmp::Ordering { + fn cmp(&self, cursor_location: &DiffTransforms, _: ()) -> cmp::Ordering { Ord::cmp(&self.0, &cursor_location.excerpt_dimension.0) } } impl<'a, D: TextDimension> sum_tree::Dimension<'a, DiffTransformSummary> for ExcerptDimension { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { ExcerptDimension(D::default()) } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { self.0.add_assign(&D::from_text_summary(&summary.input)) } } @@ -7475,47 +7469,47 @@ impl<'a, D: TextDimension> sum_tree::Dimension<'a, DiffTransformSummary> for Exc impl sum_tree::SeekTarget<'_, DiffTransformSummary, DiffTransforms> for OutputDimension { - fn cmp(&self, cursor_location: &DiffTransforms, _: &()) -> cmp::Ordering { + fn cmp(&self, cursor_location: &DiffTransforms, _: ()) -> cmp::Ordering { Ord::cmp(&self.0, &cursor_location.output_dimension.0) } } impl<'a, D: TextDimension> sum_tree::Dimension<'a, DiffTransformSummary> for OutputDimension { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { OutputDimension(D::default()) } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { self.0.add_assign(&D::from_text_summary(&summary.output)) } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for TextSummary { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { TextSummary::default() } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { *self += summary.output } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for usize { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { 0 } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { *self += summary.output.len } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for Point { - fn zero(_: &()) -> Self { + fn zero(_: ()) -> Self { Point::new(0, 0) } - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) { *self += summary.output.lines } } diff --git a/crates/notifications/src/notification_store.rs b/crates/notifications/src/notification_store.rs index 7db17da9ff92bce492cc8414be8db28c219d61e7..0964a648b0bead5d46fe7d63113d6bc966673116 100644 --- a/crates/notifications/src/notification_store.rs +++ b/crates/notifications/src/notification_store.rs @@ -123,12 +123,12 @@ impl NotificationStore { return None; } let ix = count - 1 - ix; - let mut cursor = self.notifications.cursor::(&()); + let mut cursor = self.notifications.cursor::(()); cursor.seek(&Count(ix), Bias::Right); cursor.item() } pub fn notification_for_id(&self, id: u64) -> Option<&NotificationEntry> { - let mut cursor = self.notifications.cursor::(&()); + let mut cursor = self.notifications.cursor::(()); cursor.seek(&NotificationId(id), Bias::Left); if let Some(item) = cursor.item() && item.id == id @@ -297,12 +297,12 @@ impl NotificationStore { ) { let mut cursor = self .notifications - .cursor::>(&()); + .cursor::>(()); let mut new_notifications = SumTree::default(); let mut old_range = 0..0; for (i, (id, new_notification)) in notifications.into_iter().enumerate() { - new_notifications.append(cursor.slice(&NotificationId(id), Bias::Left), &()); + new_notifications.append(cursor.slice(&NotificationId(id), Bias::Left), ()); if i == 0 { old_range.start = cursor.start().1.0; @@ -334,13 +334,13 @@ impl NotificationStore { } if let Some(notification) = new_notification { - new_notifications.push(notification, &()); + new_notifications.push(notification, ()); } } old_range.end = cursor.start().1.0; let new_count = new_notifications.summary().count - old_range.start; - new_notifications.append(cursor.suffix(), &()); + new_notifications.append(cursor.suffix(), ()); drop(cursor); self.notifications = new_notifications; @@ -381,7 +381,7 @@ impl EventEmitter for NotificationStore {} impl sum_tree::Item for NotificationEntry { type Summary = NotificationSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { NotificationSummary { max_id: self.id, count: 1, @@ -390,14 +390,12 @@ impl sum_tree::Item for NotificationEntry { } } -impl sum_tree::Summary for NotificationSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for NotificationSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { self.max_id = self.max_id.max(summary.max_id); self.count += summary.count; self.unread_count += summary.unread_count; @@ -405,22 +403,22 @@ impl sum_tree::Summary for NotificationSummary { } impl sum_tree::Dimension<'_, NotificationSummary> for NotificationId { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &NotificationSummary, _: &()) { + fn add_summary(&mut self, summary: &NotificationSummary, _: ()) { debug_assert!(summary.max_id > self.0); self.0 = summary.max_id; } } impl sum_tree::Dimension<'_, NotificationSummary> for Count { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &NotificationSummary, _: &()) { + fn add_summary(&mut self, summary: &NotificationSummary, _: ()) { self.0 += summary.count; } } diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 401a610ba0a2d0e17657547553ed03d8b9fca38f..aefdd8acb18841eecc9ec7aa5613126b44d7a429 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -209,7 +209,7 @@ impl TryFrom for StatusEntry { impl sum_tree::Item for StatusEntry { type Summary = PathSummary; - fn summary(&self, _: &::Context) -> Self::Summary { + fn summary(&self, _: ::Context<'_>) -> Self::Summary { PathSummary { max_path: self.repo_path.0.clone(), item_summary: self.status.summary(), @@ -2890,7 +2890,7 @@ impl RepositorySnapshot { pub fn status_for_path(&self, path: &RepoPath) -> Option { self.statuses_by_path - .get(&PathKey(path.0.clone()), &()) + .get(&PathKey(path.0.clone()), ()) .cloned() } @@ -4470,7 +4470,7 @@ impl Repository { }), ) .collect::>(); - self.snapshot.statuses_by_path.edit(edits, &()); + self.snapshot.statuses_by_path.edit(edits, ()); if update.is_last_update { self.snapshot.scan_id = update.scan_id; } @@ -4771,7 +4771,7 @@ impl Repository { .background_spawn(async move { let mut changed_path_statuses = Vec::new(); let prev_statuses = prev_snapshot.statuses_by_path.clone(); - let mut cursor = prev_statuses.cursor::(&()); + let mut cursor = prev_statuses.cursor::(()); for (repo_path, status) in &*statuses.entries { changed_paths.remove(repo_path); @@ -4786,7 +4786,7 @@ impl Repository { status: *status, })); } - let mut cursor = prev_statuses.cursor::(&()); + let mut cursor = prev_statuses.cursor::(()); for path in changed_paths.into_iter() { if cursor.seek_forward(&PathTarget::Path(&path), Bias::Left) { changed_path_statuses.push(Edit::Remove(PathKey(path.0))); @@ -4803,7 +4803,7 @@ impl Repository { if !changed_path_statuses.is_empty() { this.snapshot .statuses_by_path - .edit(changed_path_statuses, &()); + .edit(changed_path_statuses, ()); this.snapshot.scan_id += 1; } @@ -5072,7 +5072,7 @@ async fn compute_snapshot( repo_path: repo_path.clone(), status: *status, }), - &(), + (), ); let (merge_details, merge_heads_changed) = MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?; diff --git a/crates/project/src/git_store/git_traversal.rs b/crates/project/src/git_store/git_traversal.rs index eee492e482daf746c60836cab172f84b2834b468..93f5d3e24c883073eca657c1b724da64648f88ae 100644 --- a/crates/project/src/git_store/git_traversal.rs +++ b/crates/project/src/git_store/git_traversal.rs @@ -12,7 +12,10 @@ pub struct GitTraversal<'a> { traversal: Traversal<'a>, current_entry_summary: Option, repo_root_to_snapshot: BTreeMap<&'a Path, &'a RepositorySnapshot>, - repo_location: Option<(RepositoryId, Cursor<'a, StatusEntry, PathProgress<'a>>)>, + repo_location: Option<( + RepositoryId, + Cursor<'a, 'static, StatusEntry, PathProgress<'a>>, + )>, } impl<'a> GitTraversal<'a> { @@ -85,7 +88,7 @@ impl<'a> GitTraversal<'a> { .map(|(prev_repo_id, _)| *prev_repo_id) != Some(repo.id) { - self.repo_location = Some((repo.id, repo.statuses_by_path.cursor::(&()))); + self.repo_location = Some((repo.id, repo.statuses_by_path.cursor::(()))); } let Some((_, statuses)) = &mut self.repo_location else { diff --git a/crates/rope/benches/rope_benchmark.rs b/crates/rope/benches/rope_benchmark.rs index cb741fc78481e7d03a7c18dbf0d8919359b06436..bf891a4f837c3e63975aea1233b15c913758c175 100644 --- a/crates/rope/benches/rope_benchmark.rs +++ b/crates/rope/benches/rope_benchmark.rs @@ -144,7 +144,7 @@ fn rope_benchmarks(c: &mut Criterion) { group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { let rope = generate_random_rope(rng.clone(), *size); - b.iter_with_large_drop(|| { + b.iter(|| { let chars = rope.chars().count(); assert!(chars > 0); }); @@ -190,6 +190,34 @@ fn rope_benchmarks(c: &mut Criterion) { }); } group.finish(); + + let mut group = c.benchmark_group("cursor"); + for size in sizes.iter() { + group.throughput(Throughput::Bytes(*size as u64)); + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { + let rope = generate_random_rope(rng.clone(), *size); + + b.iter_batched( + || { + let mut rng = rng.clone(); + let num_points = rope.len() / 10; + + let mut points = Vec::new(); + for _ in 0..num_points { + points.push(rng.random_range(0..rope.len())); + } + points + }, + |offsets| { + for offset in offsets.iter() { + black_box(rope.cursor(*offset)); + } + }, + BatchSize::SmallInput, + ); + }); + } + group.finish(); } criterion_group!(benches, rope_benchmarks); diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index cb2e103f76fa636571526c71afcbb3358542b083..508e7569c3b5e872aeeacd0ae86e9c9ff1521e7a 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -34,7 +34,7 @@ impl Rope { if self.chunks.is_empty() { return offset == 0; } - let mut cursor = self.chunks.cursor::(&()); + let mut cursor = self.chunks.cursor::(()); cursor.seek(&offset, Bias::Left); let chunk_offset = offset - cursor.start(); cursor @@ -53,7 +53,7 @@ impl Rope { (u8 as i8) >= -0x40 } - let mut cursor = self.chunks.cursor::(&()); + let mut cursor = self.chunks.cursor::(()); cursor.seek(&index, Bias::Left); let chunk_offset = index - cursor.start(); let lower_idx = cursor.item().map(|chunk| { @@ -85,7 +85,7 @@ impl Rope { (u8 as i8) >= -0x40 } - let mut cursor = self.chunks.cursor::(&()); + let mut cursor = self.chunks.cursor::(()); cursor.seek(&index, Bias::Left); let chunk_offset = index - cursor.start(); let upper_idx = cursor.item().map(|chunk| { @@ -110,15 +110,15 @@ impl Rope { { self.push_chunk(chunk.as_slice()); - let mut chunks = rope.chunks.cursor::<()>(&()); + let mut chunks = rope.chunks.cursor::<()>(()); chunks.next(); chunks.next(); - self.chunks.append(chunks.suffix(), &()); + self.chunks.append(chunks.suffix(), ()); self.check_invariants(); return; } - self.chunks.append(rope.chunks.clone(), &()); + self.chunks.append(rope.chunks.clone(), ()); self.check_invariants(); } @@ -165,7 +165,7 @@ impl Rope { last_chunk.push_str(suffix); text = remainder; }, - &(), + (), ); if text.len() > 2048 { @@ -190,10 +190,10 @@ impl Rope { if new_chunks.len() >= PARALLEL_THRESHOLD { self.chunks - .par_extend(new_chunks.into_vec().into_par_iter().map(Chunk::new), &()); + .par_extend(new_chunks.into_vec().into_par_iter().map(Chunk::new), ()); } else { self.chunks - .extend(new_chunks.into_iter().map(Chunk::new), &()); + .extend(new_chunks.into_iter().map(Chunk::new), ()); } self.check_invariants(); @@ -234,10 +234,10 @@ impl Rope { if new_chunks.len() >= PARALLEL_THRESHOLD { self.chunks - .par_extend(new_chunks.into_par_iter().map(Chunk::new), &()); + .par_extend(new_chunks.into_par_iter().map(Chunk::new), ()); } else { self.chunks - .extend(new_chunks.into_iter().map(Chunk::new), &()); + .extend(new_chunks.into_iter().map(Chunk::new), ()); } self.check_invariants(); @@ -263,11 +263,11 @@ impl Rope { last_chunk.append(suffix); chunk = remainder; }, - &(), + (), ); if !chunk.is_empty() { - self.chunks.push(chunk.into(), &()); + self.chunks.push(chunk.into(), ()); } } @@ -281,7 +281,7 @@ impl Rope { { // Ensure all chunks except maybe the last one are not underflowing. // Allow some wiggle room for multibyte characters at chunk boundaries. - let mut chunks = self.chunks.cursor::<()>(&()).peekable(); + let mut chunks = self.chunks.cursor::<()>(()).peekable(); while let Some(chunk) = chunks.next() { if chunks.peek().is_some() { assert!(chunk.text.len() + 3 >= chunk::MIN_BASE); @@ -295,7 +295,7 @@ impl Rope { } pub fn len(&self) -> usize { - self.chunks.extent(&()) + self.chunks.extent(()) } pub fn is_empty(&self) -> bool { @@ -303,11 +303,11 @@ impl Rope { } pub fn max_point(&self) -> Point { - self.chunks.extent(&()) + self.chunks.extent(()) } pub fn max_point_utf16(&self) -> PointUtf16 { - self.chunks.extent(&()) + self.chunks.extent(()) } pub fn cursor(&self, offset: usize) -> Cursor<'_> { @@ -351,7 +351,7 @@ impl Rope { if offset >= self.summary().len { return self.summary().len_utf16; } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&offset, Bias::Left); let overshoot = offset - cursor.start().0; cursor.start().1 @@ -364,7 +364,7 @@ impl Rope { if offset >= self.summary().len_utf16 { return self.summary().len; } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&offset, Bias::Left); let overshoot = offset - cursor.start().0; cursor.start().1 @@ -377,7 +377,7 @@ impl Rope { if offset >= self.summary().len { return self.summary().lines; } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&offset, Bias::Left); let overshoot = offset - cursor.start().0; cursor.start().1 @@ -390,7 +390,7 @@ impl Rope { if offset >= self.summary().len { return self.summary().lines_utf16(); } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&offset, Bias::Left); let overshoot = offset - cursor.start().0; cursor.start().1 @@ -403,7 +403,7 @@ impl Rope { if point >= self.summary().lines { return self.summary().lines_utf16(); } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&point, Bias::Left); let overshoot = point - cursor.start().0; cursor.start().1 @@ -416,7 +416,7 @@ impl Rope { if point >= self.summary().lines { return self.summary().len; } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&point, Bias::Left); let overshoot = point - cursor.start().0; cursor.start().1 @@ -437,7 +437,7 @@ impl Rope { if point >= self.summary().lines_utf16() { return self.summary().len; } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&point, Bias::Left); let overshoot = point - cursor.start().0; cursor.start().1 @@ -450,7 +450,7 @@ impl Rope { if point.0 >= self.summary().lines_utf16() { return self.summary().lines; } - let mut cursor = self.chunks.cursor::>(&()); + let mut cursor = self.chunks.cursor::>(()); cursor.seek(&point.0, Bias::Left); let overshoot = Unclipped(point.0 - cursor.start().0); cursor.start().1 @@ -467,7 +467,7 @@ impl Rope { } pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { - let mut cursor = self.chunks.cursor::(&()); + let mut cursor = self.chunks.cursor::(()); cursor.seek(&offset, Bias::Right); if let Some(chunk) = cursor.item() { let overshoot = offset - cursor.start(); @@ -478,7 +478,7 @@ impl Rope { } pub fn clip_point(&self, point: Point, bias: Bias) -> Point { - let mut cursor = self.chunks.cursor::(&()); + let mut cursor = self.chunks.cursor::(()); cursor.seek(&point, Bias::Right); if let Some(chunk) = cursor.item() { let overshoot = point - cursor.start(); @@ -489,7 +489,7 @@ impl Rope { } pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { - let mut cursor = self.chunks.cursor::(&()); + let mut cursor = self.chunks.cursor::(()); cursor.seek(&point.0, Bias::Right); if let Some(chunk) = cursor.item() { let overshoot = Unclipped(point.0 - cursor.start()); @@ -564,13 +564,13 @@ impl fmt::Debug for Rope { pub struct Cursor<'a> { rope: &'a Rope, - chunks: sum_tree::Cursor<'a, Chunk, usize>, + chunks: sum_tree::Cursor<'a, 'static, Chunk, usize>, offset: usize, } impl<'a> Cursor<'a> { pub fn new(rope: &'a Rope, offset: usize) -> Self { - let mut chunks = rope.chunks.cursor(&()); + let mut chunks = rope.chunks.cursor(()); chunks.seek(&offset, Bias::Right); Self { rope, @@ -619,7 +619,7 @@ impl<'a> Cursor<'a> { pub fn summary(&mut self, end_offset: usize) -> D { debug_assert!(end_offset >= self.offset); - let mut summary = D::zero(&()); + let mut summary = D::zero(()); if let Some(start_chunk) = self.chunks.item() { let start_ix = self.offset - self.chunks.start(); let end_ix = cmp::min(end_offset, self.chunks.end()) - self.chunks.start(); @@ -640,7 +640,7 @@ impl<'a> Cursor<'a> { } pub fn suffix(mut self) -> Rope { - self.slice(self.rope.chunks.extent(&())) + self.slice(self.rope.chunks.extent(())) } pub fn offset(&self) -> usize { @@ -659,7 +659,7 @@ pub struct ChunkBitmaps<'a> { #[derive(Clone)] pub struct Chunks<'a> { - chunks: sum_tree::Cursor<'a, Chunk, usize>, + chunks: sum_tree::Cursor<'a, 'static, Chunk, usize>, range: Range, offset: usize, reversed: bool, @@ -667,7 +667,7 @@ pub struct Chunks<'a> { impl<'a> Chunks<'a> { pub fn new(rope: &'a Rope, range: Range, reversed: bool) -> Self { - let mut chunks = rope.chunks.cursor(&()); + let mut chunks = rope.chunks.cursor(()); let offset = if reversed { chunks.seek(&range.end, Bias::Left); range.end @@ -994,14 +994,14 @@ impl<'a> Iterator for Chunks<'a> { } pub struct Bytes<'a> { - chunks: sum_tree::Cursor<'a, Chunk, usize>, + chunks: sum_tree::Cursor<'a, 'static, Chunk, usize>, range: Range, reversed: bool, } impl<'a> Bytes<'a> { pub fn new(rope: &'a Rope, range: Range, reversed: bool) -> Self { - let mut chunks = rope.chunks.cursor(&()); + let mut chunks = rope.chunks.cursor(()); if reversed { chunks.seek(&range.end, Bias::Left); } else { @@ -1079,7 +1079,7 @@ pub struct Lines<'a> { reversed: bool, } -impl Lines<'_> { +impl<'a> Lines<'a> { pub fn next(&mut self) -> Option<&str> { if self.done { return None; @@ -1144,7 +1144,7 @@ impl Lines<'_> { impl sum_tree::Item for Chunk { type Summary = ChunkSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { ChunkSummary { text: self.as_slice().text_summary(), } @@ -1156,14 +1156,12 @@ pub struct ChunkSummary { text: TextSummary, } -impl sum_tree::Summary for ChunkSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for ChunkSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { self.text += &summary.text; } } @@ -1273,14 +1271,12 @@ impl<'a> From<&'a str> for TextSummary { } } -impl sum_tree::Summary for TextSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for TextSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + fn add_summary(&mut self, summary: &Self) { *self += summary; } } @@ -1359,11 +1355,11 @@ impl TextDimension for Dimensions sum_tree::Dimension<'a, ChunkSummary> for TextSummary { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ChunkSummary, _: ()) { *self += &summary.text; } } @@ -1383,11 +1379,11 @@ impl TextDimension for TextSummary { } impl<'a> sum_tree::Dimension<'a, ChunkSummary> for usize { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ChunkSummary, _: ()) { *self += summary.text.len; } } @@ -1407,11 +1403,11 @@ impl TextDimension for usize { } impl<'a> sum_tree::Dimension<'a, ChunkSummary> for OffsetUtf16 { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ChunkSummary, _: ()) { *self += summary.text.len_utf16; } } @@ -1431,11 +1427,11 @@ impl TextDimension for OffsetUtf16 { } impl<'a> sum_tree::Dimension<'a, ChunkSummary> for Point { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ChunkSummary, _: ()) { *self += summary.text.lines; } } @@ -1455,11 +1451,11 @@ impl TextDimension for Point { } impl<'a> sum_tree::Dimension<'a, ChunkSummary> for PointUtf16 { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + fn add_summary(&mut self, summary: &'a ChunkSummary, _: ()) { *self += summary.text.lines_utf16(); } } @@ -1547,14 +1543,14 @@ where K: sum_tree::Dimension<'a, ChunkSummary>, V: sum_tree::Dimension<'a, ChunkSummary>, { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Self { key: K::zero(_cx), value: Some(V::zero(_cx)), } } - fn add_summary(&mut self, summary: &'a ChunkSummary, _cx: &()) { + fn add_summary(&mut self, summary: &'a ChunkSummary, _cx: ()) { self.key.add_summary(summary, _cx); if let Some(value) = &mut self.value { value.add_summary(summary, _cx); @@ -2233,7 +2229,7 @@ mod tests { impl Rope { fn text(&self) -> String { let mut text = String::new(); - for chunk in self.chunks.cursor::<()>(&()) { + for chunk in self.chunks.cursor::<()>(()) { text.push_str(&chunk.text); } text diff --git a/crates/rope/src/unclipped.rs b/crates/rope/src/unclipped.rs index 051293bfc72ebe22b5e3e9bbabe207d41323543a..abf82504ea8db74cc78bdb55f32406f63125b465 100644 --- a/crates/rope/src/unclipped.rs +++ b/crates/rope/src/unclipped.rs @@ -13,12 +13,12 @@ impl From for Unclipped { impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary> for Unclipped { - fn zero(_: &()) -> Self { - Self(T::zero(&())) + fn zero(_: ()) -> Self { + Self(T::zero(())) } - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { - self.0.add_summary(summary, &()); + fn add_summary(&mut self, summary: &'a ChunkSummary, _: ()) { + self.0.add_summary(summary, ()); } } diff --git a/crates/sum_tree/src/cursor.rs b/crates/sum_tree/src/cursor.rs index 53458b65ec313ca56c5decfcd4650825285d0733..6df1d3da41556dc7eb93f7460b960ccddbe52de6 100644 --- a/crates/sum_tree/src/cursor.rs +++ b/crates/sum_tree/src/cursor.rs @@ -5,10 +5,17 @@ use std::{cmp::Ordering, mem, sync::Arc}; #[derive(Clone)] struct StackEntry<'a, T: Item, D> { tree: &'a SumTree, - index: usize, + index: u32, position: D, } +impl<'a, T: Item, D> StackEntry<'a, T, D> { + #[inline] + fn index(&self) -> usize { + self.index as usize + } +} + impl fmt::Debug for StackEntry<'_, T, D> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StackEntry") @@ -19,16 +26,16 @@ impl fmt::Debug for StackEntry<'_, T, D> { } #[derive(Clone)] -pub struct Cursor<'a, T: Item, D> { +pub struct Cursor<'a, 'b, T: Item, D> { tree: &'a SumTree, stack: ArrayVec, 16>, position: D, did_seek: bool, at_end: bool, - cx: &'a ::Context, + cx: ::Context<'b>, } -impl fmt::Debug for Cursor<'_, T, D> +impl fmt::Debug for Cursor<'_, '_, T, D> where T::Summary: fmt::Debug, { @@ -48,12 +55,12 @@ pub struct Iter<'a, T: Item> { stack: ArrayVec, 16>, } -impl<'a, T, D> Cursor<'a, T, D> +impl<'a, 'b, T, D> Cursor<'a, 'b, T, D> where T: Item, D: Dimension<'a, T::Summary>, { - pub fn new(tree: &'a SumTree, cx: &'a ::Context) -> Self { + pub fn new(tree: &'a SumTree, cx: ::Context<'b>) -> Self { Self { tree, stack: ArrayVec::new(), @@ -93,10 +100,10 @@ where if let Some(entry) = self.stack.last() { match *entry.tree.0 { Node::Leaf { ref items, .. } => { - if entry.index == items.len() { + if entry.index() == items.len() { None } else { - Some(&items[entry.index]) + Some(&items[entry.index()]) } } _ => unreachable!(), @@ -114,10 +121,10 @@ where Node::Leaf { ref item_summaries, .. } => { - if entry.index == item_summaries.len() { + if entry.index() == item_summaries.len() { None } else { - Some(&item_summaries[entry.index]) + Some(&item_summaries[entry.index()]) } } _ => unreachable!(), @@ -131,7 +138,7 @@ where pub fn next_item(&self) -> Option<&'a T> { self.assert_did_seek(); if let Some(entry) = self.stack.last() { - if entry.index == entry.tree.0.items().len() - 1 { + if entry.index() == entry.tree.0.items().len() - 1 { if let Some(next_leaf) = self.next_leaf() { Some(next_leaf.0.items().first().unwrap()) } else { @@ -139,7 +146,7 @@ where } } else { match *entry.tree.0 { - Node::Leaf { ref items, .. } => Some(&items[entry.index + 1]), + Node::Leaf { ref items, .. } => Some(&items[entry.index() + 1]), _ => unreachable!(), } } @@ -153,11 +160,11 @@ where #[track_caller] fn next_leaf(&self) -> Option<&'a SumTree> { for entry in self.stack.iter().rev().skip(1) { - if entry.index < entry.tree.0.child_trees().len() - 1 { + if entry.index() < entry.tree.0.child_trees().len() - 1 { match *entry.tree.0 { Node::Internal { ref child_trees, .. - } => return Some(child_trees[entry.index + 1].leftmost_leaf()), + } => return Some(child_trees[entry.index() + 1].leftmost_leaf()), Node::Leaf { .. } => unreachable!(), }; } @@ -169,7 +176,7 @@ where pub fn prev_item(&self) -> Option<&'a T> { self.assert_did_seek(); if let Some(entry) = self.stack.last() { - if entry.index == 0 { + if entry.index() == 0 { if let Some(prev_leaf) = self.prev_leaf() { Some(prev_leaf.0.items().last().unwrap()) } else { @@ -177,7 +184,7 @@ where } } else { match *entry.tree.0 { - Node::Leaf { ref items, .. } => Some(&items[entry.index - 1]), + Node::Leaf { ref items, .. } => Some(&items[entry.index() - 1]), _ => unreachable!(), } } @@ -191,11 +198,11 @@ where #[track_caller] fn prev_leaf(&self) -> Option<&'a SumTree> { for entry in self.stack.iter().rev().skip(1) { - if entry.index != 0 { + if entry.index() != 0 { match *entry.tree.0 { Node::Internal { ref child_trees, .. - } => return Some(child_trees[entry.index - 1].rightmost_leaf()), + } => return Some(child_trees[entry.index() - 1].rightmost_leaf()), Node::Leaf { .. } => unreachable!(), }; } @@ -224,7 +231,7 @@ where if !self.tree.is_empty() { self.stack.push(StackEntry { tree: self.tree, - index: self.tree.0.child_summaries().len(), + index: self.tree.0.child_summaries().len() as u32, position: D::from_summary(self.tree.summary(), self.cx), }); } @@ -240,7 +247,7 @@ where let entry = self.stack.last_mut().unwrap(); if !descending { - if entry.index == 0 { + if entry.index() == 0 { self.stack.pop(); continue; } else { @@ -248,20 +255,20 @@ where } } - for summary in &entry.tree.0.child_summaries()[..entry.index] { + for summary in &entry.tree.0.child_summaries()[..entry.index()] { self.position.add_summary(summary, self.cx); } entry.position = self.position.clone(); - descending = filter_node(&entry.tree.0.child_summaries()[entry.index]); + descending = filter_node(&entry.tree.0.child_summaries()[entry.index()]); match entry.tree.0.as_ref() { Node::Internal { child_trees, .. } => { if descending { - let tree = &child_trees[entry.index]; + let tree = &child_trees[entry.index()]; self.stack.push(StackEntry { position: D::zero(self.cx), tree, - index: tree.0.child_summaries().len() - 1, + index: tree.0.child_summaries().len() as u32 - 1, }) } } @@ -312,8 +319,8 @@ where entry.position = self.position.clone(); } - while entry.index < child_summaries.len() { - let next_summary = &child_summaries[entry.index]; + while entry.index() < child_summaries.len() { + let next_summary = &child_summaries[entry.index()]; if filter_node(next_summary) { break; } else { @@ -323,18 +330,18 @@ where } } - child_trees.get(entry.index) + child_trees.get(entry.index()) } Node::Leaf { item_summaries, .. } => { if !descend { - let item_summary = &item_summaries[entry.index]; + let item_summary = &item_summaries[entry.index()]; entry.index += 1; entry.position.add_summary(item_summary, self.cx); self.position.add_summary(item_summary, self.cx); } loop { - if let Some(next_item_summary) = item_summaries.get(entry.index) { + if let Some(next_item_summary) = item_summaries.get(entry.index()) { if filter_node(next_item_summary) { return; } else { @@ -376,7 +383,7 @@ where } } -impl<'a, T, D> Cursor<'a, T, D> +impl<'a, 'b, T, D> Cursor<'a, 'b, T, D> where T: Item, D: Dimension<'a, T::Summary>, @@ -465,9 +472,9 @@ where entry.position = self.position.clone(); } - for (child_tree, child_summary) in child_trees[entry.index..] + for (child_tree, child_summary) in child_trees[entry.index()..] .iter() - .zip(&child_summaries[entry.index..]) + .zip(&child_summaries[entry.index()..]) { let mut child_end = self.position.clone(); child_end.add_summary(child_summary, self.cx); @@ -498,9 +505,9 @@ where } => { aggregate.begin_leaf(); - for (item, item_summary) in items[entry.index..] + for (item, item_summary) in items[entry.index()..] .iter() - .zip(&item_summaries[entry.index..]) + .zip(&item_summaries[entry.index()..]) { let mut child_end = self.position.clone(); child_end.add_summary(item_summary, self.cx); @@ -572,14 +579,14 @@ impl<'a, T: Item> Iterator for Iter<'a, T> { if !descend { entry.index += 1; } - child_trees.get(entry.index) + child_trees.get(entry.index()) } Node::Leaf { items, .. } => { if !descend { entry.index += 1; } - if let Some(next_item) = items.get(entry.index) { + if let Some(next_item) = items.get(entry.index()) { return Some(next_item); } else { None @@ -605,7 +612,7 @@ impl<'a, T: Item> Iterator for Iter<'a, T> { } } -impl<'a, T: Item, D> Iterator for Cursor<'a, T, D> +impl<'a, 'b, T: Item, D> Iterator for Cursor<'a, 'b, T, D> where D: Dimension<'a, T::Summary>, { @@ -625,12 +632,12 @@ where } } -pub struct FilterCursor<'a, F, T: Item, D> { - cursor: Cursor<'a, T, D>, +pub struct FilterCursor<'a, 'b, F, T: Item, D> { + cursor: Cursor<'a, 'b, T, D>, filter_node: F, } -impl<'a, F, T: Item, D> FilterCursor<'a, F, T, D> +impl<'a, 'b, F, T: Item, D> FilterCursor<'a, 'b, F, T, D> where F: FnMut(&T::Summary) -> bool, T: Item, @@ -638,7 +645,7 @@ where { pub fn new( tree: &'a SumTree, - cx: &'a ::Context, + cx: ::Context<'b>, filter_node: F, ) -> Self { let cursor = tree.cursor::(cx); @@ -673,7 +680,7 @@ where } } -impl<'a, F, T: Item, U> Iterator for FilterCursor<'a, F, T, U> +impl<'a, 'b, F, T: Item, U> Iterator for FilterCursor<'a, 'b, F, T, U> where F: FnMut(&T::Summary) -> bool, U: Dimension<'a, T::Summary>, @@ -696,18 +703,18 @@ where trait SeekAggregate<'a, T: Item> { fn begin_leaf(&mut self); - fn end_leaf(&mut self, cx: &::Context); + fn end_leaf(&mut self, cx: ::Context<'_>); fn push_item( &mut self, item: &'a T, summary: &'a T::Summary, - cx: &::Context, + cx: ::Context<'_>, ); fn push_tree( &mut self, tree: &'a SumTree, summary: &'a T::Summary, - cx: &::Context, + cx: ::Context<'_>, ); } @@ -722,14 +729,20 @@ struct SummarySeekAggregate(D); impl SeekAggregate<'_, T> for () { fn begin_leaf(&mut self) {} - fn end_leaf(&mut self, _: &::Context) {} - fn push_item(&mut self, _: &T, _: &T::Summary, _: &::Context) {} - fn push_tree(&mut self, _: &SumTree, _: &T::Summary, _: &::Context) {} + fn end_leaf(&mut self, _: ::Context<'_>) {} + fn push_item(&mut self, _: &T, _: &T::Summary, _: ::Context<'_>) {} + fn push_tree( + &mut self, + _: &SumTree, + _: &T::Summary, + _: ::Context<'_>, + ) { + } } impl SeekAggregate<'_, T> for SliceSeekAggregate { fn begin_leaf(&mut self) {} - fn end_leaf(&mut self, cx: &::Context) { + fn end_leaf(&mut self, cx: ::Context<'_>) { self.tree.append( SumTree(Arc::new(Node::Leaf { summary: mem::replace(&mut self.leaf_summary, ::zero(cx)), @@ -739,7 +752,12 @@ impl SeekAggregate<'_, T> for SliceSeekAggregate { cx, ); } - fn push_item(&mut self, item: &T, summary: &T::Summary, cx: &::Context) { + fn push_item( + &mut self, + item: &T, + summary: &T::Summary, + cx: ::Context<'_>, + ) { self.leaf_items.push(item.clone()); self.leaf_item_summaries.push(summary.clone()); Summary::add_summary(&mut self.leaf_summary, summary, cx); @@ -748,7 +766,7 @@ impl SeekAggregate<'_, T> for SliceSeekAggregate { &mut self, tree: &SumTree, _: &T::Summary, - cx: &::Context, + cx: ::Context<'_>, ) { self.tree.append(tree.clone(), cx); } @@ -759,15 +777,20 @@ where D: Dimension<'a, T::Summary>, { fn begin_leaf(&mut self) {} - fn end_leaf(&mut self, _: &::Context) {} - fn push_item(&mut self, _: &T, summary: &'a T::Summary, cx: &::Context) { + fn end_leaf(&mut self, _: ::Context<'_>) {} + fn push_item( + &mut self, + _: &T, + summary: &'a T::Summary, + cx: ::Context<'_>, + ) { self.0.add_summary(summary, cx); } fn push_tree( &mut self, _: &SumTree, summary: &'a T::Summary, - cx: &::Context, + cx: ::Context<'_>, ) { self.0.add_summary(summary, cx); } @@ -782,7 +805,7 @@ impl End { } impl<'a, S: Summary, D: Dimension<'a, S>> SeekTarget<'a, S, D> for End { - fn cmp(&self, _: &D, _: &S::Context) -> Ordering { + fn cmp(&self, _: &D, _: S::Context<'_>) -> Ordering { Ordering::Greater } } diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 64814ad09148cc0eb318c306132f2e296fcb3cab..bfd2423c9230ea4246509e164d7a03f4890cbf4a 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -20,7 +20,7 @@ pub const TREE_BASE: usize = 6; pub trait Item: Clone { type Summary: Summary; - fn summary(&self, cx: &::Context) -> Self::Summary; + fn summary(&self, cx: ::Context<'_>) -> Self::Summary; } /// An [`Item`] whose summary has a specific key that can be used to identify it @@ -35,23 +35,40 @@ pub trait KeyedItem: Item { /// Each Summary type can have multiple [`Dimension`]s that it measures, /// which can be used to navigate the tree pub trait Summary: Clone { - type Context; + type Context<'a>: Copy; + fn zero<'a>(cx: Self::Context<'a>) -> Self; + fn add_summary<'a>(&mut self, summary: &Self, cx: Self::Context<'a>); +} - fn zero(cx: &Self::Context) -> Self; - fn add_summary(&mut self, summary: &Self, cx: &Self::Context); +pub trait ContextLessSummary: Clone { + fn zero() -> Self; + fn add_summary(&mut self, summary: &Self); } +impl Summary for T { + type Context<'a> = (); + + fn zero<'a>((): ()) -> Self { + T::zero() + } + + fn add_summary<'a>(&mut self, summary: &Self, (): ()) { + T::add_summary(self, summary) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct NoSummary; + /// Catch-all implementation for when you need something that implements [`Summary`] without a specific type. -/// We implement it on a &'static, as that avoids blanket impl collisions with `impl Dimension for T` +/// We implement it on a `NoSummary` instead of re-using `()`, as that avoids blanket impl collisions with `impl Dimension for T` /// (as we also need unit type to be a fill-in dimension) -impl Summary for &'static () { - type Context = (); - - fn zero(_: &()) -> Self { - &() +impl ContextLessSummary for NoSummary { + fn zero() -> Self { + NoSummary } - fn add_summary(&mut self, _: &Self, _: &()) {} + fn add_summary(&mut self, _: &Self) {} } /// Each [`Summary`] type can have more than one [`Dimension`] type that it measures. @@ -62,11 +79,11 @@ impl Summary for &'static () { /// Zed's rope has a `TextSummary` type that summarizes lines, characters, and bytes. /// Each of these are different dimensions we may want to seek to pub trait Dimension<'a, S: Summary>: Clone { - fn zero(cx: &S::Context) -> Self; + fn zero(cx: S::Context<'_>) -> Self; - fn add_summary(&mut self, summary: &'a S, cx: &S::Context); + fn add_summary(&mut self, summary: &'a S, cx: S::Context<'_>); - fn from_summary(summary: &'a S, cx: &S::Context) -> Self { + fn from_summary(summary: &'a S, cx: S::Context<'_>) -> Self { let mut dimension = Self::zero(cx); dimension.add_summary(summary, cx); dimension @@ -74,29 +91,29 @@ pub trait Dimension<'a, S: Summary>: Clone { } impl<'a, T: Summary> Dimension<'a, T> for T { - fn zero(cx: &T::Context) -> Self { + fn zero(cx: T::Context<'_>) -> Self { Summary::zero(cx) } - fn add_summary(&mut self, summary: &'a T, cx: &T::Context) { + fn add_summary(&mut self, summary: &'a T, cx: T::Context<'_>) { Summary::add_summary(self, summary, cx); } } pub trait SeekTarget<'a, S: Summary, D: Dimension<'a, S>> { - fn cmp(&self, cursor_location: &D, cx: &S::Context) -> Ordering; + fn cmp(&self, cursor_location: &D, cx: S::Context<'_>) -> Ordering; } impl<'a, S: Summary, D: Dimension<'a, S> + Ord> SeekTarget<'a, S, D> for D { - fn cmp(&self, cursor_location: &Self, _: &S::Context) -> Ordering { + fn cmp(&self, cursor_location: &Self, _: S::Context<'_>) -> Ordering { Ord::cmp(self, cursor_location) } } 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) {} + fn add_summary(&mut self, _: &'a T, _: T::Context<'_>) {} } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] @@ -105,11 +122,11 @@ pub struct Dimensions(pub D1, pub D2, pub D3); impl<'a, T: Summary, D1: Dimension<'a, T>, D2: Dimension<'a, T>, D3: Dimension<'a, T>> Dimension<'a, T> for Dimensions { - fn zero(cx: &T::Context) -> Self { + fn zero(cx: T::Context<'_>) -> Self { Dimensions(D1::zero(cx), D2::zero(cx), D3::zero(cx)) } - fn add_summary(&mut self, summary: &'a T, cx: &T::Context) { + fn add_summary(&mut self, summary: &'a T, cx: T::Context<'_>) { self.0.add_summary(summary, cx); self.1.add_summary(summary, cx); self.2.add_summary(summary, cx); @@ -123,7 +140,7 @@ where D2: Dimension<'a, S>, D3: Dimension<'a, S>, { - fn cmp(&self, cursor_location: &Dimensions, cx: &S::Context) -> Ordering { + fn cmp(&self, cursor_location: &Dimensions, cx: S::Context<'_>) -> Ordering { self.cmp(&cursor_location.0, cx) } } @@ -187,7 +204,7 @@ where } impl SumTree { - pub fn new(cx: &::Context) -> Self { + pub fn new(cx: ::Context<'_>) -> Self { SumTree(Arc::new(Node::Leaf { summary: ::zero(cx), items: ArrayVec::new(), @@ -204,7 +221,7 @@ impl SumTree { })) } - pub fn from_item(item: T, cx: &::Context) -> Self { + pub fn from_item(item: T, cx: ::Context<'_>) -> Self { let mut tree = Self::new(cx); tree.push(item, cx); tree @@ -212,7 +229,7 @@ impl SumTree { pub fn from_iter>( iter: I, - cx: &::Context, + cx: ::Context<'_>, ) -> Self { let mut nodes = Vec::new(); @@ -276,13 +293,13 @@ impl SumTree { } } - pub fn from_par_iter(iter: I, cx: &::Context) -> Self + pub fn from_par_iter(iter: I, cx: ::Context<'_>) -> Self where I: IntoParallelIterator, Iter: IndexedParallelIterator, T: Send + Sync, T::Summary: Send + Sync, - ::Context: Sync, + for<'a> ::Context<'a>: Sync, { let mut nodes = iter .into_par_iter() @@ -339,7 +356,7 @@ impl SumTree { } #[allow(unused)] - pub fn items(&self, cx: &::Context) -> Vec { + pub fn items<'a>(&'a self, cx: ::Context<'a>) -> Vec { let mut items = Vec::new(); let mut cursor = self.cursor::<()>(cx); cursor.next(); @@ -354,7 +371,10 @@ impl SumTree { Iter::new(self) } - pub fn cursor<'a, S>(&'a self, cx: &'a ::Context) -> Cursor<'a, T, S> + pub fn cursor<'a, 'b, S>( + &'a self, + cx: ::Context<'b>, + ) -> Cursor<'a, 'b, T, S> where S: Dimension<'a, T::Summary>, { @@ -363,11 +383,11 @@ impl SumTree { /// Note: If the summary type requires a non `()` context, then the filter cursor /// that is returned cannot be used with Rust's iterators. - pub fn filter<'a, F, U>( + pub fn filter<'a, 'b, F, U>( &'a self, - cx: &'a ::Context, + cx: ::Context<'b>, filter_node: F, - ) -> FilterCursor<'a, F, T, U> + ) -> FilterCursor<'a, 'b, F, T, U> where F: FnMut(&T::Summary) -> bool, U: Dimension<'a, T::Summary>, @@ -384,14 +404,18 @@ impl SumTree { self.rightmost_leaf().0.items().last() } - pub fn update_last(&mut self, f: impl FnOnce(&mut T), cx: &::Context) { + pub fn update_last( + &mut self, + f: impl FnOnce(&mut T), + cx: ::Context<'_>, + ) { self.update_last_recursive(f, cx); } fn update_last_recursive( &mut self, f: impl FnOnce(&mut T), - cx: &::Context, + cx: ::Context<'_>, ) -> Option { match Arc::make_mut(&mut self.0) { Node::Internal { @@ -426,7 +450,7 @@ impl SumTree { pub fn extent<'a, D: Dimension<'a, T::Summary>>( &'a self, - cx: &::Context, + cx: ::Context<'_>, ) -> D { let mut extent = D::zero(cx); match self.0.as_ref() { @@ -451,25 +475,25 @@ impl SumTree { } } - pub fn extend(&mut self, iter: I, cx: &::Context) + pub fn extend(&mut self, iter: I, cx: ::Context<'_>) where I: IntoIterator, { self.append(Self::from_iter(iter, cx), cx); } - pub fn par_extend(&mut self, iter: I, cx: &::Context) + pub fn par_extend(&mut self, iter: I, cx: ::Context<'_>) where I: IntoParallelIterator, Iter: IndexedParallelIterator, T: Send + Sync, T::Summary: Send + Sync, - ::Context: Sync, + for<'a> ::Context<'a>: Sync, { self.append(Self::from_par_iter(iter, cx), cx); } - pub fn push(&mut self, item: T, cx: &::Context) { + pub fn push(&mut self, item: T, cx: ::Context<'_>) { let summary = item.summary(cx); self.append( SumTree(Arc::new(Node::Leaf { @@ -481,7 +505,7 @@ impl SumTree { ); } - pub fn append(&mut self, other: Self, cx: &::Context) { + pub fn append(&mut self, other: Self, cx: ::Context<'_>) { if self.is_empty() { *self = other; } else if !other.0.is_leaf() || !other.0.items().is_empty() { @@ -498,7 +522,7 @@ impl SumTree { fn push_tree_recursive( &mut self, other: SumTree, - cx: &::Context, + cx: ::Context<'_>, ) -> Option> { match Arc::make_mut(&mut self.0) { Node::Internal { @@ -618,7 +642,7 @@ impl SumTree { fn from_child_trees( left: SumTree, right: SumTree, - cx: &::Context, + cx: ::Context<'_>, ) -> Self { let height = left.0.height() + 1; let mut child_summaries = ArrayVec::new(); @@ -663,13 +687,13 @@ impl PartialEq for SumTree { impl Eq for SumTree {} impl SumTree { - pub fn insert_or_replace( - &mut self, + pub fn insert_or_replace<'a, 'b>( + &'a mut self, item: T, - cx: &::Context, + cx: ::Context<'b>, ) -> Option { let mut replaced = None; - *self = { + { let mut cursor = self.cursor::(cx); let mut new_tree = cursor.slice(&item.key(), Bias::Left); if let Some(cursor_item) = cursor.item() @@ -680,12 +704,13 @@ impl SumTree { } new_tree.push(item, cx); new_tree.append(cursor.suffix(), cx); - new_tree + drop(cursor); + *self = new_tree }; replaced } - pub fn remove(&mut self, key: &T::Key, cx: &::Context) -> Option { + pub fn remove(&mut self, key: &T::Key, cx: ::Context<'_>) -> Option { let mut removed = None; *self = { let mut cursor = self.cursor::(cx); @@ -705,7 +730,7 @@ impl SumTree { pub fn edit( &mut self, mut edits: Vec>, - cx: &::Context, + cx: ::Context<'_>, ) -> Vec { if edits.is_empty() { return Vec::new(); @@ -760,7 +785,7 @@ impl SumTree { pub fn get<'a>( &'a self, key: &T::Key, - cx: &'a ::Context, + cx: ::Context<'a>, ) -> Option<&'a T> { let mut cursor = self.cursor::(cx); if cursor.seek(key, Bias::Left) { @@ -774,10 +799,10 @@ impl SumTree { impl Default for SumTree where T: Item, - S: Summary, + S: for<'a> Summary = ()>, { fn default() -> Self { - Self::new(&()) + Self::new(()) } } @@ -894,7 +919,7 @@ impl Edit { } } -fn sum<'a, T, I>(iter: I, cx: &T::Context) -> T +fn sum<'a, T, I>(iter: I, cx: T::Context<'_>) -> T where T: 'a + Summary, I: Iterator, @@ -920,16 +945,13 @@ mod tests { #[test] fn test_extend_and_push_tree() { let mut tree1 = SumTree::default(); - tree1.extend(0..20, &()); + tree1.extend(0..20, ()); let mut tree2 = SumTree::default(); - tree2.extend(50..100, &()); + tree2.extend(50..100, ()); - tree1.append(tree2, &()); - assert_eq!( - tree1.items(&()), - (0..20).chain(50..100).collect::>() - ); + tree1.append(tree2, ()); + assert_eq!(tree1.items(()), (0..20).chain(50..100).collect::>()); } #[test] @@ -953,53 +975,53 @@ mod tests { let mut tree = SumTree::::default(); let count = rng.random_range(0..10); if rng.random() { - tree.extend(rng.sample_iter(StandardUniform).take(count), &()); + tree.extend(rng.sample_iter(StandardUniform).take(count), ()); } else { let items = rng .sample_iter(StandardUniform) .take(count) .collect::>(); - tree.par_extend(items, &()); + tree.par_extend(items, ()); } for _ in 0..num_operations { - let splice_end = rng.random_range(0..tree.extent::(&()).0 + 1); + let splice_end = rng.random_range(0..tree.extent::(()).0 + 1); let splice_start = rng.random_range(0..splice_end + 1); let count = rng.random_range(0..10); - let tree_end = tree.extent::(&()); + let tree_end = tree.extent::(()); let new_items = rng .sample_iter(StandardUniform) .take(count) .collect::>(); - let mut reference_items = tree.items(&()); + let mut reference_items = tree.items(()); reference_items.splice(splice_start..splice_end, new_items.clone()); tree = { - let mut cursor = tree.cursor::(&()); + let mut cursor = tree.cursor::(()); let mut new_tree = cursor.slice(&Count(splice_start), Bias::Right); if rng.random() { - new_tree.extend(new_items, &()); + new_tree.extend(new_items, ()); } else { - new_tree.par_extend(new_items, &()); + new_tree.par_extend(new_items, ()); } cursor.seek(&Count(splice_end), Bias::Right); - new_tree.append(cursor.slice(&tree_end, Bias::Right), &()); + new_tree.append(cursor.slice(&tree_end, Bias::Right), ()); new_tree }; - assert_eq!(tree.items(&()), reference_items); + assert_eq!(tree.items(()), reference_items); assert_eq!( tree.iter().collect::>(), - tree.cursor::<()>(&()).collect::>() + tree.cursor::<()>(()).collect::>() ); - log::info!("tree items: {:?}", tree.items(&())); + log::info!("tree items: {:?}", tree.items(())); let mut filter_cursor = - tree.filter::<_, Count>(&(), |summary| summary.contains_even); + tree.filter::<_, Count>((), |summary| summary.contains_even); let expected_filtered_items = tree - .items(&()) + .items(()) .into_iter() .enumerate() .filter(|(_, item)| (item & 1) == 0) @@ -1038,7 +1060,7 @@ mod tests { assert_eq!(filter_cursor.item(), None); let mut before_start = false; - let mut cursor = tree.cursor::(&()); + let mut cursor = tree.cursor::(()); let start_pos = rng.random_range(0..=reference_items.len()); cursor.seek(&Count(start_pos), Bias::Right); let mut pos = rng.random_range(start_pos..=reference_items.len()); @@ -1084,7 +1106,7 @@ mod tests { } for _ in 0..10 { - let end = rng.random_range(0..tree.extent::(&()).0 + 1); + let end = rng.random_range(0..tree.extent::(()).0 + 1); let start = rng.random_range(0..end + 1); let start_bias = if rng.random() { Bias::Left @@ -1097,7 +1119,7 @@ mod tests { Bias::Right }; - let mut cursor = tree.cursor::(&()); + let mut cursor = tree.cursor::(()); cursor.seek(&Count(start), start_bias); let slice = cursor.slice(&Count(end), end_bias); @@ -1113,9 +1135,9 @@ mod tests { fn test_cursor() { // Empty tree let tree = SumTree::::default(); - let mut cursor = tree.cursor::(&()); + let mut cursor = tree.cursor::(()); assert_eq!( - cursor.slice(&Count(0), Bias::Right).items(&()), + cursor.slice(&Count(0), Bias::Right).items(()), Vec::::new() ); assert_eq!(cursor.item(), None); @@ -1135,10 +1157,10 @@ mod tests { // Single-element tree let mut tree = SumTree::::default(); - tree.extend(vec![1], &()); - let mut cursor = tree.cursor::(&()); + tree.extend(vec![1], ()); + let mut cursor = tree.cursor::(()); assert_eq!( - cursor.slice(&Count(0), Bias::Right).items(&()), + cursor.slice(&Count(0), Bias::Right).items(()), Vec::::new() ); assert_eq!(cursor.item(), Some(&1)); @@ -1158,8 +1180,8 @@ mod tests { assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); - let mut cursor = tree.cursor::(&()); - assert_eq!(cursor.slice(&Count(1), Bias::Right).items(&()), [1]); + let mut cursor = tree.cursor::(()); + assert_eq!(cursor.slice(&Count(1), Bias::Right).items(()), [1]); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); assert_eq!(cursor.next_item(), None); @@ -1168,8 +1190,8 @@ mod tests { cursor.seek(&Count(0), Bias::Right); assert_eq!( cursor - .slice(&tree.extent::(&()), Bias::Right) - .items(&()), + .slice(&tree.extent::(()), Bias::Right) + .items(()), [1] ); assert_eq!(cursor.item(), None); @@ -1179,10 +1201,10 @@ mod tests { // Multiple-element tree let mut tree = SumTree::default(); - tree.extend(vec![1, 2, 3, 4, 5, 6], &()); - let mut cursor = tree.cursor::(&()); + tree.extend(vec![1, 2, 3, 4, 5, 6], ()); + let mut cursor = tree.cursor::(()); - assert_eq!(cursor.slice(&Count(2), Bias::Right).items(&()), [1, 2]); + assert_eq!(cursor.slice(&Count(2), Bias::Right).items(()), [1, 2]); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); assert_eq!(cursor.next_item(), Some(&4)); @@ -1261,12 +1283,12 @@ mod tests { assert_eq!(cursor.next_item(), Some(&2)); assert_eq!(cursor.start().sum, 0); - let mut cursor = tree.cursor::(&()); + let mut cursor = tree.cursor::(()); assert_eq!( cursor - .slice(&tree.extent::(&()), Bias::Right) - .items(&()), - tree.items(&()) + .slice(&tree.extent::(()), Bias::Right) + .items(()), + tree.items(()) ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); @@ -1276,8 +1298,8 @@ mod tests { cursor.seek(&Count(3), Bias::Right); assert_eq!( cursor - .slice(&tree.extent::(&()), Bias::Right) - .items(&()), + .slice(&tree.extent::(()), Bias::Right) + .items(()), [4, 5, 6] ); assert_eq!(cursor.item(), None); @@ -1293,36 +1315,36 @@ mod tests { // Slicing without resetting starts from where the cursor is parked at. cursor.seek(&Count(1), Bias::Right); - assert_eq!(cursor.slice(&Count(3), Bias::Right).items(&()), vec![2, 3]); - assert_eq!(cursor.slice(&Count(6), Bias::Left).items(&()), vec![4, 5]); - assert_eq!(cursor.slice(&Count(6), Bias::Right).items(&()), vec![6]); + assert_eq!(cursor.slice(&Count(3), Bias::Right).items(()), vec![2, 3]); + assert_eq!(cursor.slice(&Count(6), Bias::Left).items(()), vec![4, 5]); + assert_eq!(cursor.slice(&Count(6), Bias::Right).items(()), vec![6]); } #[test] fn test_edit() { let mut tree = SumTree::::default(); - let removed = tree.edit(vec![Edit::Insert(1), Edit::Insert(2), Edit::Insert(0)], &()); - assert_eq!(tree.items(&()), vec![0, 1, 2]); + let removed = tree.edit(vec![Edit::Insert(1), Edit::Insert(2), Edit::Insert(0)], ()); + assert_eq!(tree.items(()), vec![0, 1, 2]); assert_eq!(removed, Vec::::new()); - assert_eq!(tree.get(&0, &()), Some(&0)); - assert_eq!(tree.get(&1, &()), Some(&1)); - assert_eq!(tree.get(&2, &()), Some(&2)); - assert_eq!(tree.get(&4, &()), None); + assert_eq!(tree.get(&0, ()), Some(&0)); + assert_eq!(tree.get(&1, ()), Some(&1)); + assert_eq!(tree.get(&2, ()), Some(&2)); + assert_eq!(tree.get(&4, ()), None); - let removed = tree.edit(vec![Edit::Insert(2), Edit::Insert(4), Edit::Remove(0)], &()); - assert_eq!(tree.items(&()), vec![1, 2, 4]); + let removed = tree.edit(vec![Edit::Insert(2), Edit::Insert(4), Edit::Remove(0)], ()); + assert_eq!(tree.items(()), vec![1, 2, 4]); assert_eq!(removed, vec![0, 2]); - assert_eq!(tree.get(&0, &()), None); - assert_eq!(tree.get(&1, &()), Some(&1)); - assert_eq!(tree.get(&2, &()), Some(&2)); - assert_eq!(tree.get(&4, &()), Some(&4)); + assert_eq!(tree.get(&0, ()), None); + assert_eq!(tree.get(&1, ()), Some(&1)); + assert_eq!(tree.get(&2, ()), Some(&2)); + assert_eq!(tree.get(&4, ()), Some(&4)); } #[test] fn test_from_iter() { assert_eq!( - SumTree::from_iter(0..100, &()).items(&()), + SumTree::from_iter(0..100, ()).items(()), (0..100).collect::>() ); @@ -1333,7 +1355,7 @@ mod tests { ix = (ix + 1) % 2; if ix == 1 { Some(1) } else { None } }); - assert_eq!(SumTree::from_iter(iterator, &()).items(&()), vec![1]); + assert_eq!(SumTree::from_iter(iterator, ()).items(()), vec![1]); } #[derive(Clone, Default, Debug)] @@ -1353,7 +1375,7 @@ mod tests { impl Item for u8 { type Summary = IntegersSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { IntegersSummary { count: 1, sum: *self as usize, @@ -1371,14 +1393,12 @@ mod tests { } } - impl Summary for IntegersSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { + impl ContextLessSummary for IntegersSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, _: &()) { + fn add_summary(&mut self, other: &Self) { self.count += other.count; self.sum += other.sum; self.contains_even |= other.contains_even; @@ -1387,37 +1407,37 @@ mod tests { } impl Dimension<'_, IntegersSummary> for u8 { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &IntegersSummary, _: &()) { + fn add_summary(&mut self, summary: &IntegersSummary, _: ()) { *self = summary.max; } } impl Dimension<'_, IntegersSummary> for Count { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &IntegersSummary, _: &()) { + fn add_summary(&mut self, summary: &IntegersSummary, _: ()) { self.0 += summary.count; } } impl SeekTarget<'_, IntegersSummary, IntegersSummary> for Count { - fn cmp(&self, cursor_location: &IntegersSummary, _: &()) -> Ordering { + fn cmp(&self, cursor_location: &IntegersSummary, _: ()) -> Ordering { self.0.cmp(&cursor_location.count) } } impl Dimension<'_, IntegersSummary> for Sum { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &IntegersSummary, _: &()) { + fn add_summary(&mut self, summary: &IntegersSummary, _: ()) { self.0 += summary.sum; } } diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 818214e4024497e20f1f7cc208421ffbbb1d0401..62630407083fc34d74fef0ebf5e17a44ce40f68e 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -1,6 +1,6 @@ use std::{cmp::Ordering, fmt::Debug}; -use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary}; +use crate::{Bias, ContextLessSummary, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree}; /// A cheaply-cloneable ordered map based on a [SumTree](crate::SumTree). #[derive(Clone, PartialEq, Eq)] @@ -44,7 +44,7 @@ impl TreeMap { entries .into_iter() .map(|(key, value)| MapEntry { key, value }), - &(), + (), ); Self(tree) } @@ -54,7 +54,7 @@ impl TreeMap { } pub fn get(&self, key: &K) -> Option<&V> { - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); cursor.seek(&MapKeyRef(Some(key)), Bias::Left); if let Some(item) = cursor.item() { if Some(key) == item.key().0.as_ref() { @@ -68,7 +68,7 @@ impl TreeMap { } pub fn insert(&mut self, key: K, value: V) { - self.0.insert_or_replace(MapEntry { key, value }, &()); + self.0.insert_or_replace(MapEntry { key, value }, ()); } pub fn extend(&mut self, iter: impl IntoIterator) { @@ -76,7 +76,7 @@ impl TreeMap { .into_iter() .map(|(key, value)| Edit::Insert(MapEntry { key, value })) .collect(); - self.0.edit(edits, &()); + self.0.edit(edits, ()); } pub fn clear(&mut self) { @@ -85,14 +85,14 @@ impl TreeMap { pub fn remove(&mut self, key: &K) -> Option { let mut removed = None; - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); let key = MapKeyRef(Some(key)); let mut new_tree = cursor.slice(&key, Bias::Left); - if key.cmp(&cursor.end(), &()) == Ordering::Equal { + if key.cmp(&cursor.end(), ()) == Ordering::Equal { removed = Some(cursor.item().unwrap().value.clone()); cursor.next(); } - new_tree.append(cursor.suffix(), &()); + new_tree.append(cursor.suffix(), ()); drop(cursor); self.0 = new_tree; removed @@ -101,17 +101,17 @@ impl TreeMap { pub fn remove_range(&mut self, start: &impl MapSeekTarget, end: &impl MapSeekTarget) { let start = MapSeekTargetAdaptor(start); let end = MapSeekTargetAdaptor(end); - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); let mut new_tree = cursor.slice(&start, Bias::Left); cursor.seek(&end, Bias::Left); - new_tree.append(cursor.suffix(), &()); + new_tree.append(cursor.suffix(), ()); drop(cursor); self.0 = new_tree; } /// Returns the key-value pair with the greatest key less than or equal to the given key. pub fn closest(&self, key: &K) -> Option<(&K, &V)> { - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); let key = MapKeyRef(Some(key)); cursor.seek(&key, Bias::Right); cursor.prev(); @@ -119,7 +119,7 @@ impl TreeMap { } pub fn iter_from<'a>(&'a self, from: &K) -> impl Iterator + 'a { - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); let from_key = MapKeyRef(Some(from)); cursor.seek(&from_key, Bias::Left); @@ -130,17 +130,17 @@ impl TreeMap { where F: FnOnce(&mut V) -> T, { - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); let key = MapKeyRef(Some(key)); let mut new_tree = cursor.slice(&key, Bias::Left); let mut result = None; - if key.cmp(&cursor.end(), &()) == Ordering::Equal { + if key.cmp(&cursor.end(), ()) == Ordering::Equal { let mut updated = cursor.item().unwrap().clone(); result = Some(f(&mut updated.value)); - new_tree.push(updated, &()); + new_tree.push(updated, ()); cursor.next(); } - new_tree.append(cursor.suffix(), &()); + new_tree.append(cursor.suffix(), ()); drop(cursor); self.0 = new_tree; result @@ -149,11 +149,11 @@ impl TreeMap { pub fn retain bool>(&mut self, mut predicate: F) { let mut new_map = SumTree::>::default(); - let mut cursor = self.0.cursor::>(&()); + let mut cursor = self.0.cursor::>(()); cursor.next(); while let Some(item) = cursor.item() { if predicate(&item.key, &item.value) { - new_map.push(item.clone(), &()); + new_map.push(item.clone(), ()); } cursor.next(); } @@ -189,7 +189,7 @@ impl TreeMap { }) .collect(); - self.0.edit(edits, &()); + self.0.edit(edits, ()); } } @@ -209,7 +209,7 @@ struct MapSeekTargetAdaptor<'a, T>(&'a T); impl<'a, K: Clone + Ord, T: MapSeekTarget> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> for MapSeekTargetAdaptor<'_, T> { - fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { + fn cmp(&self, cursor_location: &MapKeyRef, _: ()) -> Ordering { if let Some(key) = &cursor_location.0 { MapSeekTarget::cmp_cursor(self.0, key) } else { @@ -245,7 +245,7 @@ where { type Summary = MapKey; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.key() } } @@ -262,17 +262,15 @@ where } } -impl Summary for MapKey +impl ContextLessSummary for MapKey where K: Clone, { - type Context = (); - - fn zero(_cx: &()) -> Self { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { *self = summary.clone() } } @@ -281,11 +279,11 @@ impl<'a, K> Dimension<'a, MapKey> for MapKeyRef<'a, K> where K: Clone + Ord, { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a MapKey, _: &()) { + fn add_summary(&mut self, summary: &'a MapKey, _: ()) { self.0 = summary.0.as_ref(); } } @@ -294,7 +292,7 @@ impl<'a, K> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> for MapKeyRef<'_, K> where K: Clone + Ord, { - fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { + fn cmp(&self, cursor_location: &MapKeyRef, _: ()) -> Ordering { Ord::cmp(&self.0, &cursor_location.0) } } diff --git a/crates/text/src/locator.rs b/crates/text/src/locator.rs index 7363d5bdd3424b202bd92b4e6d98e2d678f0829f..cc94441a3d1ea2654875cd286d91b9dc2334ab53 100644 --- a/crates/text/src/locator.rs +++ b/crates/text/src/locator.rs @@ -67,7 +67,7 @@ impl Default for Locator { impl sum_tree::Item for Locator { type Summary = Locator; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.clone() } } @@ -80,14 +80,12 @@ impl sum_tree::KeyedItem for Locator { } } -impl sum_tree::Summary for Locator { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for Locator { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { self.assign(summary); } } diff --git a/crates/text/src/operation_queue.rs b/crates/text/src/operation_queue.rs index 38864e66c8751806aa78e68a4d8663a8e18a11f4..6604817edfe2dcc243ba837a770b361bd505a7ef 100644 --- a/crates/text/src/operation_queue.rs +++ b/crates/text/src/operation_queue.rs @@ -1,5 +1,5 @@ use std::{fmt::Debug, ops::Add}; -use sum_tree::{Dimension, Edit, Item, KeyedItem, SumTree, Summary}; +use sum_tree::{ContextLessSummary, Dimension, Edit, Item, KeyedItem, SumTree}; pub trait Operation: Clone + Debug { fn lamport_timestamp(&self) -> clock::Lamport; @@ -52,7 +52,7 @@ impl OperationQueue { ops.into_iter() .map(|op| Edit::Insert(OperationItem(op))) .collect(), - &(), + (), ); } @@ -67,14 +67,12 @@ impl OperationQueue { } } -impl Summary for OperationSummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl ContextLessSummary for OperationSummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, _: &()) { + fn add_summary(&mut self, other: &Self) { assert!(self.key < other.key); self.key = other.key; self.len += other.len; @@ -94,11 +92,11 @@ impl Add<&Self> for OperationSummary { } impl Dimension<'_, OperationSummary> for OperationKey { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &OperationSummary, _: &()) { + fn add_summary(&mut self, summary: &OperationSummary, _: ()) { assert!(*self <= summary.key); *self = summary.key; } @@ -107,7 +105,7 @@ impl Dimension<'_, OperationSummary> for OperationKey { impl Item for OperationItem { type Summary = OperationSummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { OperationSummary { key: OperationKey::new(self.0.lamport_timestamp()), len: 1, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index a38693ed934855acd0e0c6ff726c7835a1aa057e..d61038d746fa1bebbf0b92a99b0a59f650bc5704 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -482,7 +482,7 @@ impl History { struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> { visible_cursor: rope::Cursor<'a>, deleted_cursor: rope::Cursor<'a>, - fragments_cursor: Option>, + fragments_cursor: Option>, undos: &'a UndoMap, since: &'a clock::Global, old_end: D, @@ -747,7 +747,7 @@ impl Buffer { deletions: Default::default(), max_undos: Default::default(), }; - insertions.push(InsertionFragment::new(&fragment), &()); + insertions.push(InsertionFragment::new(&fragment), ()); fragments.push(fragment, &None); } @@ -992,7 +992,7 @@ impl Buffer { drop(old_fragments); self.snapshot.fragments = new_fragments; - self.snapshot.insertions.edit(new_insertions, &()); + self.snapshot.insertions.edit(new_insertions, ()); self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; self.subscriptions.publish_mut(&edits_patch); @@ -1240,7 +1240,7 @@ impl Buffer { self.snapshot.fragments = new_fragments; self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; - self.snapshot.insertions.edit(new_insertions, &()); + self.snapshot.insertions.edit(new_insertions, ()); self.snapshot.insertion_slices.extend(insertion_slices); self.subscriptions.publish_mut(&edits_patch) } @@ -1269,7 +1269,7 @@ impl Buffer { // Get all of the fragments corresponding to these insertion slices. let mut fragment_ids = Vec::new(); - let mut insertions_cursor = self.insertions.cursor::(&()); + let mut insertions_cursor = self.insertions.cursor::(()); for insertion_slice in &insertion_slices { if insertion_slice.insertion_id != insertions_cursor.start().timestamp || insertion_slice.range.start > insertions_cursor.start().split_offset @@ -1597,7 +1597,7 @@ impl Buffer { }); // convert to the desired text dimension. - let mut position = D::zero(&()); + let mut position = D::zero(()); let mut rope_cursor = self.visible_text.cursor(0); disjoint_ranges.map(move |range| { position.add_assign(&rope_cursor.summary(range.start)); @@ -1782,7 +1782,7 @@ impl Buffer { timestamp: fragment.timestamp, split_offset: fragment.insertion_offset, }, - &(), + (), ) .unwrap(); assert_eq!( @@ -1793,7 +1793,7 @@ impl Buffer { } let mut cursor = self.snapshot.fragments.cursor::>(&None); - for insertion_fragment in self.snapshot.insertions.cursor::<()>(&()) { + for insertion_fragment in self.snapshot.insertions.cursor::<()>(()) { cursor.seek(&Some(&insertion_fragment.fragment_id), Bias::Left); let fragment = cursor.item().unwrap(); assert_eq!(insertion_fragment.fragment_id, fragment.id); @@ -2237,16 +2237,16 @@ impl BufferSnapshot { A: 'a + IntoIterator, { let anchors = anchors.into_iter(); - let mut insertion_cursor = self.insertions.cursor::(&()); + let mut insertion_cursor = self.insertions.cursor::(()); let mut fragment_cursor = self .fragments .cursor::, usize>>(&None); let mut text_cursor = self.visible_text.cursor(0); - let mut position = D::zero(&()); + let mut position = D::zero(()); anchors.map(move |(anchor, payload)| { if *anchor == Anchor::MIN { - return (D::zero(&()), payload); + return (D::zero(()), payload); } else if *anchor == Anchor::MAX { return (D::from_text_summary(&self.visible_text.summary()), payload); } @@ -2301,7 +2301,7 @@ impl BufferSnapshot { timestamp: anchor.timestamp, split_offset: anchor.offset, }; - let mut insertion_cursor = self.insertions.cursor::(&()); + let mut insertion_cursor = self.insertions.cursor::(()); insertion_cursor.seek(&anchor_key, anchor.bias); if let Some(insertion) = insertion_cursor.item() { let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); @@ -2358,7 +2358,7 @@ impl BufferSnapshot { timestamp: anchor.timestamp, split_offset: anchor.offset, }; - let mut insertion_cursor = self.insertions.cursor::(&()); + let mut insertion_cursor = self.insertions.cursor::(()); insertion_cursor.seek(&anchor_key, anchor.bias); if let Some(insertion) = insertion_cursor.item() { let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); @@ -2519,8 +2519,8 @@ impl BufferSnapshot { fragments_cursor, undos: &self.undo_map, since, - old_end: D::zero(&()), - new_end: D::zero(&()), + old_end: D::zero(()), + new_end: D::zero(()), range: (start_fragment_id, range.start.offset)..(end_fragment_id, range.end.offset), buffer_id: self.remote_id, } @@ -2851,13 +2851,13 @@ impl sum_tree::Item for Fragment { } impl sum_tree::Summary for FragmentSummary { - type Context = Option; + type Context<'a> = &'a Option; - fn zero(_cx: &Self::Context) -> Self { + fn zero(_cx: Self::Context<'_>) -> Self { Default::default() } - fn add_summary(&mut self, other: &Self, _: &Self::Context) { + fn add_summary(&mut self, other: &Self, _: Self::Context<'_>) { self.max_id.assign(&other.max_id); self.text.visible += &other.text.visible; self.text.deleted += &other.text.deleted; @@ -2884,7 +2884,7 @@ impl Default for FragmentSummary { impl sum_tree::Item for InsertionFragment { type Summary = InsertionFragmentKey; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { InsertionFragmentKey { timestamp: self.timestamp, split_offset: self.split_offset, @@ -2896,7 +2896,7 @@ impl sum_tree::KeyedItem for InsertionFragment { type Key = InsertionFragmentKey; fn key(&self) -> Self::Key { - sum_tree::Item::summary(self, &()) + sum_tree::Item::summary(self, ()) } } @@ -2914,14 +2914,12 @@ impl InsertionFragment { } } -impl sum_tree::Summary for InsertionFragmentKey { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for InsertionFragmentKey { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &()) { + fn add_summary(&mut self, summary: &Self) { *self = *summary; } } diff --git a/crates/text/src/undo_map.rs b/crates/text/src/undo_map.rs index 6a409189fa8d2a9bd3bc821e37b9923b5ed884dd..60b22a9edba70b65d30c60a0b9ca15b8f286cc85 100644 --- a/crates/text/src/undo_map.rs +++ b/crates/text/src/undo_map.rs @@ -11,7 +11,7 @@ struct UndoMapEntry { impl sum_tree::Item for UndoMapEntry { type Summary = UndoMapKey; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { self.key } } @@ -30,14 +30,12 @@ struct UndoMapKey { undo_id: clock::Lamport, } -impl sum_tree::Summary for UndoMapKey { - type Context = (); - - fn zero(_cx: &Self::Context) -> Self { +impl sum_tree::ContextLessSummary for UndoMapKey { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + fn add_summary(&mut self, summary: &Self) { *self = cmp::max(*self, *summary); } } @@ -60,14 +58,14 @@ impl UndoMap { }) }) .collect::>(); - self.0.edit(edits, &()); + self.0.edit(edits, ()); } pub fn is_undone(&self, edit_id: clock::Lamport) -> bool { self.undo_count(edit_id) % 2 == 1 } pub fn was_undone(&self, edit_id: clock::Lamport, version: &clock::Global) -> bool { - let mut cursor = self.0.cursor::(&()); + let mut cursor = self.0.cursor::(()); cursor.seek( &UndoMapKey { edit_id, @@ -91,7 +89,7 @@ impl UndoMap { } pub fn undo_count(&self, edit_id: clock::Lamport) -> u32 { - let mut cursor = self.0.cursor::(&()); + let mut cursor = self.0.cursor::(()); cursor.seek( &UndoMapKey { edit_id, diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index d3f1976500fb19d31a2d2f44c7d63933552eec15..1e108c22f2506938796dcea1e5d44ceda9f83ed3 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -391,12 +391,12 @@ struct LocalRepositoryEntry { } impl sum_tree::Item for LocalRepositoryEntry { - type Summary = PathSummary<&'static ()>; + type Summary = PathSummary; - fn summary(&self, _: &::Context) -> Self::Summary { + fn summary(&self, _: ::Context<'_>) -> Self::Summary { PathSummary { max_path: self.work_directory.path_key().0, - item_summary: &(), + item_summary: sum_tree::NoSummary, } } } @@ -2429,7 +2429,7 @@ impl Snapshot { } pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool { - self.entries_by_id.get(&entry_id, &()).is_some() + self.entries_by_id.get(&entry_id, ()).is_some() } fn insert_entry( @@ -2445,30 +2445,30 @@ impl Snapshot { is_ignored: entry.is_ignored, scan_id: 0, }, - &(), + (), ); if let Some(old_entry) = old_entry { - self.entries_by_path.remove(&PathKey(old_entry.path), &()); + self.entries_by_path.remove(&PathKey(old_entry.path), ()); } - self.entries_by_path.insert_or_replace(entry.clone(), &()); + self.entries_by_path.insert_or_replace(entry.clone(), ()); Ok(entry) } fn delete_entry(&mut self, entry_id: ProjectEntryId) -> Option> { - let removed_entry = self.entries_by_id.remove(&entry_id, &())?; + let removed_entry = self.entries_by_id.remove(&entry_id, ())?; self.entries_by_path = { - let mut cursor = self.entries_by_path.cursor::(&()); + let mut cursor = self.entries_by_path.cursor::(()); let mut new_entries_by_path = cursor.slice(&TraversalTarget::path(&removed_entry.path), Bias::Left); while let Some(entry) = cursor.item() { if entry.path.starts_with(&removed_entry.path) { - self.entries_by_id.remove(&entry.id, &()); + self.entries_by_id.remove(&entry.id, ()); cursor.next(); } else { break; } } - new_entries_by_path.append(cursor.suffix(), &()); + new_entries_by_path.append(cursor.suffix(), ()); new_entries_by_path }; @@ -2511,10 +2511,10 @@ impl Snapshot { for entry in update.updated_entries { let entry = Entry::from((&self.root_char_bag, always_included_paths, entry)); - if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) { + 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 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)); @@ -2528,8 +2528,8 @@ impl Snapshot { entries_by_path_edits.push(Edit::Insert(entry)); } - self.entries_by_path.edit(entries_by_path_edits, &()); - self.entries_by_id.edit(entries_by_id_edits, &()); + self.entries_by_path.edit(entries_by_path_edits, ()); + self.entries_by_id.edit(entries_by_id_edits, ()); self.scan_id = update.scan_id as usize; if update.is_last_update { @@ -2570,7 +2570,7 @@ impl Snapshot { include_ignored: bool, start_offset: usize, ) -> Traversal<'_> { - let mut cursor = self.entries_by_path.cursor(&()); + let mut cursor = self.entries_by_path.cursor(()); cursor.seek( &TraversalTarget::Count { count: start_offset, @@ -2614,7 +2614,7 @@ impl Snapshot { pub fn paths(&self) -> impl Iterator> { let empty_path = Path::new(""); self.entries_by_path - .cursor::<()>(&()) + .cursor::<()>(()) .filter(move |entry| entry.path.as_ref() != empty_path) .map(|entry| &entry.path) } @@ -2633,7 +2633,7 @@ impl Snapshot { parent_path: &'a Path, options: ChildEntriesOptions, ) -> ChildEntriesIter<'a> { - let mut cursor = self.entries_by_path.cursor(&()); + let mut cursor = self.entries_by_path.cursor(()); cursor.seek(&TraversalTarget::path(parent_path), Bias::Right); let traversal = Traversal { snapshot: self, @@ -2683,7 +2683,7 @@ impl Snapshot { } pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> { - let entry = self.entries_by_id.get(&id, &())?; + let entry = self.entries_by_id.get(&id, ())?; self.entry_for_path(&entry.path) } @@ -2757,18 +2757,17 @@ impl LocalSnapshot { } if entry.kind == EntryKind::PendingDir - && let Some(existing_entry) = - self.entries_by_path.get(&PathKey(entry.path.clone()), &()) + && let Some(existing_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), ()) { entry.kind = existing_entry.kind; } let scan_id = self.scan_id; - let removed = self.entries_by_path.insert_or_replace(entry.clone(), &()); + let removed = self.entries_by_path.insert_or_replace(entry.clone(), ()); if let Some(removed) = removed && removed.id != entry.id { - self.entries_by_id.remove(&removed.id, &()); + self.entries_by_id.remove(&removed.id, ()); } self.entries_by_id.insert_or_replace( PathEntry { @@ -2777,7 +2776,7 @@ impl LocalSnapshot { is_ignored: entry.is_ignored, scan_id, }, - &(), + (), ); entry @@ -2838,7 +2837,7 @@ impl LocalSnapshot { #[cfg(test)] fn expanded_entries(&self) -> impl Iterator { self.entries_by_path - .cursor::<()>(&()) + .cursor::<()>(()) .filter(|entry| entry.kind == EntryKind::Dir && (entry.is_external || entry.is_ignored)) } @@ -2848,11 +2847,11 @@ impl LocalSnapshot { assert_eq!( self.entries_by_path - .cursor::<()>(&()) + .cursor::<()>(()) .map(|e| (&e.path, e.id)) .collect::>(), self.entries_by_id - .cursor::<()>(&()) + .cursor::<()>(()) .map(|e| (&e.path, e.id)) .collect::>() .into_iter() @@ -2862,7 +2861,7 @@ impl LocalSnapshot { let mut files = self.files(true, 0); let mut visible_files = self.files(false, 0); - for entry in self.entries_by_path.cursor::<()>(&()) { + for entry in self.entries_by_path.cursor::<()>(()) { if entry.is_file() { assert_eq!(files.next().unwrap().inode, entry.inode); if (!entry.is_ignored && !entry.is_external) || entry.is_always_included { @@ -2890,7 +2889,7 @@ impl LocalSnapshot { let dfs_paths_via_iter = self .entries_by_path - .cursor::<()>(&()) + .cursor::<()>(()) .map(|e| e.path.as_ref()) .collect::>(); assert_eq!(bfs_paths, dfs_paths_via_iter); @@ -2918,7 +2917,7 @@ impl LocalSnapshot { #[cfg(test)] pub fn entries_without_ids(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> { let mut paths = Vec::new(); - for entry in self.entries_by_path.cursor::<()>(&()) { + for entry in self.entries_by_path.cursor::<()>(()) { if include_ignored || !entry.is_ignored { paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored)); } @@ -3012,7 +3011,7 @@ impl BackgroundScannerState { let mut parent_entry = if let Some(parent_entry) = self .snapshot .entries_by_path - .get(&PathKey(parent_path.clone()), &()) + .get(&PathKey(parent_path.clone()), ()) { parent_entry.clone() } else { @@ -3053,8 +3052,8 @@ impl BackgroundScannerState { self.snapshot .entries_by_path - .edit(entries_by_path_edits, &()); - self.snapshot.entries_by_id.edit(entries_by_id_edits, &()); + .edit(entries_by_path_edits, ()); + self.snapshot.entries_by_id.edit(entries_by_id_edits, ()); if let Err(ix) = self.changed_paths.binary_search(parent_path) { self.changed_paths.insert(ix, parent_path.clone()); @@ -3072,15 +3071,15 @@ impl BackgroundScannerState { let mut cursor = self .snapshot .entries_by_path - .cursor::(&()); + .cursor::(()); new_entries = cursor.slice(&TraversalTarget::path(path), Bias::Left); removed_entries = cursor.slice(&TraversalTarget::successor(path), Bias::Left); - new_entries.append(cursor.suffix(), &()); + new_entries.append(cursor.suffix(), ()); } self.snapshot.entries_by_path = new_entries; let mut removed_ids = Vec::with_capacity(removed_entries.summary().count); - for entry in removed_entries.cursor::<()>(&()) { + for entry in removed_entries.cursor::<()>(()) { match self.removed_entries.entry(entry.inode) { hash_map::Entry::Occupied(mut e) => { let prev_removed_entry = e.get_mut(); @@ -3113,10 +3112,9 @@ impl BackgroundScannerState { } } - self.snapshot.entries_by_id.edit( - removed_ids.iter().map(|&id| Edit::Remove(id)).collect(), - &(), - ); + self.snapshot + .entries_by_id + .edit(removed_ids.iter().map(|&id| Edit::Remove(id)).collect(), ()); self.snapshot .git_repositories .retain(|id, _| removed_ids.binary_search(id).is_err()); @@ -3560,23 +3558,23 @@ pub struct PathSummary { } impl Summary for PathSummary { - type Context = S::Context; + type Context<'a> = S::Context<'a>; - fn zero(cx: &Self::Context) -> Self { + fn zero(cx: Self::Context<'_>) -> Self { Self { max_path: Path::new("").into(), item_summary: S::zero(cx), } } - fn add_summary(&mut self, rhs: &Self, cx: &Self::Context) { + fn add_summary(&mut self, rhs: &Self, cx: Self::Context<'_>) { self.max_path = rhs.max_path.clone(); self.item_summary.add_summary(&rhs.item_summary, cx); } } impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary> for PathProgress<'a> { - fn zero(_: & as Summary>::Context) -> Self { + fn zero(_: as Summary>::Context<'_>) -> Self { Self { max_path: Path::new(""), } @@ -3585,18 +3583,18 @@ impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary> for PathProgress<'a fn add_summary( &mut self, summary: &'a PathSummary, - _: & as Summary>::Context, + _: as Summary>::Context<'_>, ) { self.max_path = summary.max_path.as_ref() } } impl<'a> sum_tree::Dimension<'a, PathSummary> for GitSummary { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a PathSummary, _: &()) { + fn add_summary(&mut self, summary: &'a PathSummary, _: ()) { *self += summary.item_summary } } @@ -3608,28 +3606,28 @@ impl<'a> fn cmp( &self, cursor_location: &Dimensions, GitSummary>, - _: &(), + _: (), ) -> Ordering { self.cmp_path(cursor_location.0.max_path) } } impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary> for PathKey { - fn zero(_: &S::Context) -> Self { + fn zero(_: S::Context<'_>) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a PathSummary, _: &S::Context) { + fn add_summary(&mut self, summary: &'a PathSummary, _: S::Context<'_>) { self.0 = summary.max_path.clone(); } } impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary> for TraversalProgress<'a> { - fn zero(_cx: &S::Context) -> Self { + fn zero(_cx: S::Context<'_>) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a PathSummary, _: &S::Context) { + fn add_summary(&mut self, summary: &'a PathSummary, _: S::Context<'_>) { self.max_path = summary.max_path.as_ref(); } } @@ -3697,7 +3695,7 @@ impl EntryKind { impl sum_tree::Item for Entry { type Summary = EntrySummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { let non_ignored_count = if (self.is_ignored || self.is_external) && !self.is_always_included { 0 @@ -3753,14 +3751,12 @@ impl Default for EntrySummary { } } -impl sum_tree::Summary for EntrySummary { - type Context = (); - - fn zero(_cx: &()) -> Self { +impl sum_tree::ContextLessSummary for EntrySummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, rhs: &Self, _: &()) { + fn add_summary(&mut self, rhs: &Self) { self.max_path = rhs.max_path.clone(); self.count += rhs.count; self.non_ignored_count += rhs.non_ignored_count; @@ -3780,7 +3776,7 @@ struct PathEntry { impl sum_tree::Item for PathEntry { type Summary = PathEntrySummary; - fn summary(&self, _cx: &()) -> Self::Summary { + fn summary(&self, _cx: ()) -> Self::Summary { PathEntrySummary { max_id: self.id } } } @@ -3798,24 +3794,22 @@ struct PathEntrySummary { max_id: ProjectEntryId, } -impl sum_tree::Summary for PathEntrySummary { - type Context = (); - - fn zero(_cx: &Self::Context) -> Self { +impl sum_tree::ContextLessSummary for PathEntrySummary { + fn zero() -> Self { Default::default() } - fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + fn add_summary(&mut self, summary: &Self) { self.max_id = summary.max_id; } } impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) { + fn add_summary(&mut self, summary: &'a PathEntrySummary, _: ()) { *self = summary.max_id; } } @@ -3830,11 +3824,11 @@ impl Default for PathKey { } impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) { + fn add_summary(&mut self, summary: &'a EntrySummary, _: ()) { self.0 = summary.max_path.clone(); } } @@ -4889,7 +4883,7 @@ impl BackgroundScanner { } if entry.is_ignored != was_ignored { - let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone(); + let mut path_entry = snapshot.entries_by_id.get(&entry.id, ()).unwrap().clone(); path_entry.scan_id = snapshot.scan_id; path_entry.is_ignored = entry.is_ignored; entries_by_id_edits.push(Edit::Insert(path_entry)); @@ -4909,8 +4903,8 @@ impl BackgroundScanner { state .snapshot .entries_by_path - .edit(entries_by_path_edits, &()); - state.snapshot.entries_by_id.edit(entries_by_id_edits, &()); + .edit(entries_by_path_edits, ()); + state.snapshot.entries_by_id.edit(entries_by_id_edits, ()); } fn update_git_repositories(&self, dot_git_paths: Vec) -> Vec> { @@ -5094,8 +5088,8 @@ fn build_diff( // Identify which paths have changed. Use the known set of changed // parent paths to optimize the search. let mut changes = Vec::new(); - let mut old_paths = old_snapshot.entries_by_path.cursor::(&()); - let mut new_paths = new_snapshot.entries_by_path.cursor::(&()); + let mut old_paths = old_snapshot.entries_by_path.cursor::(()); + let mut new_paths = new_snapshot.entries_by_path.cursor::(()); let mut last_newly_loaded_dir_path = None; old_paths.next(); new_paths.next(); @@ -5384,11 +5378,11 @@ impl TraversalProgress<'_> { } impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> { - fn zero(_cx: &()) -> Self { + fn zero(_cx: ()) -> Self { Default::default() } - fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) { + fn add_summary(&mut self, summary: &'a EntrySummary, _: ()) { self.max_path = summary.max_path.as_ref(); self.count += summary.count; self.non_ignored_count += summary.non_ignored_count; @@ -5412,7 +5406,7 @@ impl Default for TraversalProgress<'_> { #[derive(Debug)] pub struct Traversal<'a> { snapshot: &'a Snapshot, - cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>, + cursor: sum_tree::Cursor<'a, 'static, Entry, TraversalProgress<'a>>, include_ignored: bool, include_files: bool, include_dirs: bool, @@ -5426,7 +5420,7 @@ impl<'a> Traversal<'a> { include_ignored: bool, start_path: &Path, ) -> Self { - let mut cursor = snapshot.entries_by_path.cursor(&()); + let mut cursor = snapshot.entries_by_path.cursor(()); cursor.seek(&TraversalTarget::path(start_path), Bias::Left); let mut traversal = Self { snapshot, @@ -5536,13 +5530,13 @@ impl PathTarget<'_> { } impl<'a, S: Summary> SeekTarget<'a, PathSummary, PathProgress<'a>> for PathTarget<'_> { - fn cmp(&self, cursor_location: &PathProgress<'a>, _: &S::Context) -> Ordering { + fn cmp(&self, cursor_location: &PathProgress<'a>, _: S::Context<'_>) -> Ordering { self.cmp_path(cursor_location.max_path) } } impl<'a, S: Summary> SeekTarget<'a, PathSummary, TraversalProgress<'a>> for PathTarget<'_> { - fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &S::Context) -> Ordering { + fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: S::Context<'_>) -> Ordering { self.cmp_path(cursor_location.max_path) } } @@ -5584,13 +5578,15 @@ impl<'a> TraversalTarget<'a> { } impl<'a> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'_> { - fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering { + fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: ()) -> Ordering { self.cmp_progress(cursor_location) } } -impl<'a> SeekTarget<'a, PathSummary<&'static ()>, TraversalProgress<'a>> for TraversalTarget<'_> { - fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering { +impl<'a> SeekTarget<'a, PathSummary, TraversalProgress<'a>> + for TraversalTarget<'_> +{ + fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: ()) -> Ordering { self.cmp_progress(cursor_location) } } From 4353b61155dbb7e0ea3513278b0e61dfcefa6b28 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Wed, 24 Sep 2025 10:10:52 -0300 Subject: [PATCH 242/721] zeta2: Compute smaller edits (#38786) The new cloud endpoint returns structured edits, but they may include more of the input excerpt than what we want to display in the preview, so we compute a smaller diff on the client side against the snapshot. Release Notes: - N/A --- Cargo.lock | 1 + crates/zeta2/Cargo.toml | 1 + crates/zeta2/src/zeta2.rs | 139 ++++++++++++++++++++++++++++++-------- 3 files changed, 114 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 793c092e71a9e3717841832452e8758fde24064f..46de7e804b2b13ff775ac87953bb7c9593c39120 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20587,6 +20587,7 @@ dependencies = [ "edit_prediction_context", "futures 0.3.31", "gpui", + "indoc", "language", "language_model", "log", diff --git a/crates/zeta2/Cargo.toml b/crates/zeta2/Cargo.toml index 11ca5fcfddd3e7a7c1085401e7575bc599aea91e..8ef1c5a64f7b64a6af6a0ce984e2f76e14eb5e77 100644 --- a/crates/zeta2/Cargo.toml +++ b/crates/zeta2/Cargo.toml @@ -22,6 +22,7 @@ edit_prediction.workspace = true edit_prediction_context.workspace = true futures.workspace = true gpui.workspace = true +indoc.workspace = true language.workspace = true language_model.workspace = true log.workspace = true diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 47afc797d5d1d4dafca71f39ba15fbe6fd621c20..8986fb20740327f5598611d981b53569edcb559e 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -21,11 +21,13 @@ use gpui::{ }; use language::{ Anchor, Buffer, DiagnosticSet, LanguageServerId, OffsetRangeExt as _, ToOffset as _, ToPoint, + text_diff, }; use language::{BufferSnapshot, EditPreview}; use language_model::{LlmApiToken, RefreshLlmTokenListener}; use project::Project; use release_channel::AppVersion; +use std::borrow::Cow; use std::cmp; use std::collections::{HashMap, VecDeque, hash_map}; use std::path::PathBuf; @@ -438,7 +440,10 @@ impl Zeta { .ok(); } - anyhow::Ok(Some(response?)) + let (response, usage) = response?; + let edits = Self::compute_edits(&response.edits, &snapshot); + + anyhow::Ok(Some((response.request_id, edits, usage))) } }); @@ -446,9 +451,7 @@ impl Zeta { cx.spawn(async move |this, cx| { match request_task.await { - Ok(Some((response, usage))) => { - log::debug!("predicted edits: {:?}", &response.edits); - + Ok(Some((id, edits, usage))) => { if let Some(usage) = usage { this.update(cx, |this, cx| { this.user_store.update(cx, |user_store, cx| { @@ -459,28 +462,6 @@ impl Zeta { } // TODO telemetry: duration, etc - - // TODO produce smaller edits by diffing against snapshot first - // - // Cloud returns entire snippets/excerpts ranges as they were included - // in the request, but we should display smaller edits to the user. - // - // We can do this by computing a diff of each one against the snapshot. - // Similar to zeta::Zeta::compute_edits, but per edit. - let edits = response - .edits - .into_iter() - .map(|edit| { - // TODO edits to different files - ( - snapshot.anchor_before(edit.range.start) - ..snapshot.anchor_before(edit.range.end), - edit.content, - ) - }) - .collect::>() - .into(); - let Some((edits, snapshot, edit_preview_task)) = buffer.read_with(cx, |buffer, cx| { let new_snapshot = buffer.snapshot(); @@ -493,7 +474,7 @@ impl Zeta { }; Ok(Some(EditPrediction { - id: EditPredictionId(response.request_id), + id: EditPredictionId(id), edits, snapshot, edit_preview: edit_preview_task.await, @@ -604,6 +585,62 @@ impl Zeta { } } + fn compute_edits( + edits: &[predict_edits_v3::Edit], + snapshot: &BufferSnapshot, + ) -> Arc<[(Range, String)]> { + edits + .iter() + .flat_map(|edit| { + // TODO multi-file edits + let old_text = snapshot.text_for_range(edit.range.clone()); + + Self::compute_excerpt_edits( + old_text.collect::>(), + &edit.content, + edit.range.start, + &snapshot, + ) + }) + .collect::>() + .into() + } + + fn compute_excerpt_edits( + old_text: Cow, + new_text: &str, + offset: usize, + snapshot: &BufferSnapshot, + ) -> impl Iterator, String)> { + text_diff(&old_text, new_text) + .into_iter() + .map(move |(mut old_range, new_text)| { + old_range.start += offset; + old_range.end += offset; + + let prefix_len = common_prefix( + snapshot.chars_for_range(old_range.clone()), + new_text.chars(), + ); + old_range.start += prefix_len; + + let suffix_len = common_prefix( + snapshot.reversed_chars_for_range(old_range.clone()), + new_text[prefix_len..].chars().rev(), + ); + old_range.end = old_range.end.saturating_sub(suffix_len); + + let new_text = new_text[prefix_len..new_text.len() - suffix_len].to_string(); + let range = if old_range.is_empty() { + let anchor = snapshot.anchor_after(old_range.start); + anchor..anchor + } else { + snapshot.anchor_after(old_range.start)..snapshot.anchor_before(old_range.end) + }; + (range, new_text) + }) + } + fn gather_nearby_diagnostics( cursor_offset: usize, diagnostic_sets: &[(LanguageServerId, DiagnosticSet)], @@ -713,6 +750,13 @@ impl Zeta { } } +fn common_prefix, T2: Iterator>(a: T1, b: T2) -> usize { + a.zip(b) + .take_while(|(a, b)| a == b) + .map(|(a, _)| a.len_utf8()) + .sum() +} + #[derive(Error, Debug)] #[error( "You must update to Zed version {minimum_version} or higher to continue using edit predictions." @@ -1222,6 +1266,47 @@ fn interpolate( mod tests { use super::*; use gpui::TestAppContext; + use indoc::indoc; + + #[gpui::test] + async fn test_compute_edits(cx: &mut TestAppContext) { + let old = indoc! {r#" + fn main() { + let args = + println!("{}", args[1]) + } + "#}; + + let new = indoc! {r#" + fn main() { + let args = std::env::args(); + println!("{}", args[1]); + } + "#}; + + let buffer = cx.new(|cx| Buffer::local(old, cx)); + let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot()); + + // TODO cover more cases when multi-file is supported + let big_edits = vec![predict_edits_v3::Edit { + path: PathBuf::from("test.txt"), + range: 0..old.len(), + content: new.into(), + }]; + + let edits = Zeta::compute_edits(&big_edits, &snapshot); + assert_eq!(edits.len(), 2); + assert_eq!( + edits[0].0.to_point(&snapshot).start, + language::Point::new(1, 14) + ); + assert_eq!(edits[0].1, " std::env::args();"); + assert_eq!( + edits[1].0.to_point(&snapshot).start, + language::Point::new(2, 27) + ); + assert_eq!(edits[1].1, ";"); + } #[gpui::test] async fn test_edit_prediction_basic_interpolation(cx: &mut TestAppContext) { From b3f9be6e9cc93eedd4537a4d29eb9bc7c6b3b344 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Wed, 24 Sep 2025 10:40:29 -0300 Subject: [PATCH 243/721] zeta2: Split up crate into modules (#38788) Split up provider, prediction, and global into modules. Release Notes: - N/A --- crates/zeta2/src/prediction.rs | 345 +++++++++++++++++ crates/zeta2/src/provider.rs | 322 ++++++++++++++++ crates/zeta2/src/zeta2.rs | 661 +-------------------------------- 3 files changed, 680 insertions(+), 648 deletions(-) create mode 100644 crates/zeta2/src/prediction.rs create mode 100644 crates/zeta2/src/provider.rs diff --git a/crates/zeta2/src/prediction.rs b/crates/zeta2/src/prediction.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7b3c584a0324869921bff2868838a7ba09585ac --- /dev/null +++ b/crates/zeta2/src/prediction.rs @@ -0,0 +1,345 @@ +use std::{borrow::Cow, ops::Range, sync::Arc}; + +use cloud_llm_client::predict_edits_v3; +use language::{Anchor, BufferSnapshot, EditPreview, OffsetRangeExt, text_diff}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct EditPrediction { + pub id: EditPredictionId, + pub edits: Arc<[(Range, String)]>, + pub snapshot: BufferSnapshot, + pub edit_preview: EditPreview, +} + +impl EditPrediction { + pub fn interpolate( + &self, + new_snapshot: &BufferSnapshot, + ) -> Option, String)>> { + interpolate_edits(&self.snapshot, new_snapshot, self.edits.clone()) + } +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] +pub struct EditPredictionId(Uuid); + +impl From for EditPredictionId { + fn from(value: Uuid) -> Self { + EditPredictionId(value) + } +} + +impl From for gpui::ElementId { + fn from(value: EditPredictionId) -> Self { + gpui::ElementId::Uuid(value.0) + } +} + +impl std::fmt::Display for EditPredictionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +pub fn interpolate_edits( + old_snapshot: &BufferSnapshot, + new_snapshot: &BufferSnapshot, + current_edits: Arc<[(Range, String)]>, +) -> Option, String)>> { + let mut edits = Vec::new(); + + let mut model_edits = current_edits.iter().peekable(); + for user_edit in new_snapshot.edits_since::(&old_snapshot.version) { + while let Some((model_old_range, _)) = model_edits.peek() { + let model_old_range = model_old_range.to_offset(old_snapshot); + if model_old_range.end < user_edit.old.start { + let (model_old_range, model_new_text) = model_edits.next().unwrap(); + edits.push((model_old_range.clone(), model_new_text.clone())); + } else { + break; + } + } + + if let Some((model_old_range, model_new_text)) = model_edits.peek() { + let model_old_offset_range = model_old_range.to_offset(old_snapshot); + if user_edit.old == model_old_offset_range { + let user_new_text = new_snapshot + .text_for_range(user_edit.new.clone()) + .collect::(); + + if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) { + if !model_suffix.is_empty() { + let anchor = old_snapshot.anchor_after(user_edit.old.end); + edits.push((anchor..anchor, model_suffix.to_string())); + } + + model_edits.next(); + continue; + } + } + } + + return None; + } + + edits.extend(model_edits.cloned()); + + if edits.is_empty() { None } else { Some(edits) } +} + +pub fn edits_from_response( + edits: &[predict_edits_v3::Edit], + snapshot: &BufferSnapshot, +) -> Arc<[(Range, String)]> { + edits + .iter() + .flat_map(|edit| { + // TODO multi-file edits + let old_text = snapshot.text_for_range(edit.range.clone()); + + excerpt_edits_from_response( + old_text.collect::>(), + &edit.content, + edit.range.start, + &snapshot, + ) + }) + .collect::>() + .into() +} + +fn excerpt_edits_from_response( + old_text: Cow, + new_text: &str, + offset: usize, + snapshot: &BufferSnapshot, +) -> impl Iterator, String)> { + text_diff(&old_text, new_text) + .into_iter() + .map(move |(mut old_range, new_text)| { + old_range.start += offset; + old_range.end += offset; + + let prefix_len = common_prefix( + snapshot.chars_for_range(old_range.clone()), + new_text.chars(), + ); + old_range.start += prefix_len; + + let suffix_len = common_prefix( + snapshot.reversed_chars_for_range(old_range.clone()), + new_text[prefix_len..].chars().rev(), + ); + old_range.end = old_range.end.saturating_sub(suffix_len); + + let new_text = new_text[prefix_len..new_text.len() - suffix_len].to_string(); + let range = if old_range.is_empty() { + let anchor = snapshot.anchor_after(old_range.start); + anchor..anchor + } else { + snapshot.anchor_after(old_range.start)..snapshot.anchor_before(old_range.end) + }; + (range, new_text) + }) +} + +fn common_prefix, T2: Iterator>(a: T1, b: T2) -> usize { + a.zip(b) + .take_while(|(a, b)| a == b) + .map(|(a, _)| a.len_utf8()) + .sum() +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + use cloud_llm_client::predict_edits_v3; + use gpui::{App, Entity, TestAppContext, prelude::*}; + use indoc::indoc; + use language::{Buffer, ToOffset as _}; + + #[gpui::test] + async fn test_compute_edits(cx: &mut TestAppContext) { + let old = indoc! {r#" + fn main() { + let args = + println!("{}", args[1]) + } + "#}; + + let new = indoc! {r#" + fn main() { + let args = std::env::args(); + println!("{}", args[1]); + } + "#}; + + let buffer = cx.new(|cx| Buffer::local(old, cx)); + let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot()); + + // TODO cover more cases when multi-file is supported + let big_edits = vec![predict_edits_v3::Edit { + path: PathBuf::from("test.txt"), + range: 0..old.len(), + content: new.into(), + }]; + + let edits = edits_from_response(&big_edits, &snapshot); + assert_eq!(edits.len(), 2); + assert_eq!( + edits[0].0.to_point(&snapshot).start, + language::Point::new(1, 14) + ); + assert_eq!(edits[0].1, " std::env::args();"); + assert_eq!( + edits[1].0.to_point(&snapshot).start, + language::Point::new(2, 27) + ); + assert_eq!(edits[1].1, ";"); + } + + #[gpui::test] + async fn test_edit_prediction_basic_interpolation(cx: &mut TestAppContext) { + let buffer = cx.new(|cx| Buffer::local("Lorem ipsum dolor", cx)); + let edits: Arc<[(Range, String)]> = cx.update(|cx| { + to_prediction_edits( + [(2..5, "REM".to_string()), (9..11, "".to_string())], + &buffer, + cx, + ) + .into() + }); + + let edit_preview = cx + .read(|cx| buffer.read(cx).preview_edits(edits.clone(), cx)) + .await; + + let prediction = EditPrediction { + id: EditPredictionId(Uuid::new_v4()), + edits, + snapshot: cx.read(|cx| buffer.read(cx).snapshot()), + edit_preview, + }; + + cx.update(|cx| { + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(2..5, "REM".to_string()), (9..11, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(2..2, "REM".to_string()), (6..8, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.undo(cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(2..5, "REM".to_string()), (9..11, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "R")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(3..3, "EM".to_string()), (7..9, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "E")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(4..4, "M".to_string()), (8..10, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "M")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(9..11, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(4..4, "M".to_string()), (8..10, "".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(8..10, "")], None, cx)); + assert_eq!( + from_prediction_edits( + &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), + &buffer, + cx + ), + vec![(4..4, "M".to_string())] + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(4..6, "")], None, cx)); + assert_eq!(prediction.interpolate(&buffer.read(cx).snapshot()), None); + }) + } + + fn to_prediction_edits( + iterator: impl IntoIterator, String)>, + buffer: &Entity, + cx: &App, + ) -> Vec<(Range, String)> { + let buffer = buffer.read(cx); + iterator + .into_iter() + .map(|(range, text)| { + ( + buffer.anchor_after(range.start)..buffer.anchor_before(range.end), + text, + ) + }) + .collect() + } + + fn from_prediction_edits( + editor_edits: &[(Range, String)], + buffer: &Entity, + cx: &App, + ) -> Vec<(Range, String)> { + let buffer = buffer.read(cx); + editor_edits + .iter() + .map(|(range, text)| { + ( + range.start.to_offset(buffer)..range.end.to_offset(buffer), + text.clone(), + ) + }) + .collect() + } +} diff --git a/crates/zeta2/src/provider.rs b/crates/zeta2/src/provider.rs new file mode 100644 index 0000000000000000000000000000000000000000..ae30c0bee0da47d8f6174e76918e6dd751d348d2 --- /dev/null +++ b/crates/zeta2/src/provider.rs @@ -0,0 +1,322 @@ +use std::{ + cmp, + sync::Arc, + time::{Duration, Instant}, +}; + +use anyhow::Context as _; +use arrayvec::ArrayVec; +use client::{Client, UserStore}; +use edit_prediction::{DataCollectionState, Direction, EditPredictionProvider}; +use gpui::{App, Entity, EntityId, Task, prelude::*}; +use language::{BufferSnapshot, ToPoint as _}; +use project::Project; +use util::ResultExt as _; + +use crate::{Zeta, prediction::EditPrediction}; + +pub struct ZetaEditPredictionProvider { + zeta: Entity, + current_prediction: Option, + next_pending_prediction_id: usize, + pending_predictions: ArrayVec, + last_request_timestamp: Instant, +} + +impl ZetaEditPredictionProvider { + pub const THROTTLE_TIMEOUT: Duration = Duration::from_millis(300); + + pub fn new( + project: Option<&Entity>, + client: &Arc, + user_store: &Entity, + cx: &mut App, + ) -> Self { + let zeta = Zeta::global(client, user_store, cx); + if let Some(project) = project { + zeta.update(cx, |zeta, cx| { + zeta.register_project(project, cx); + }); + } + + Self { + zeta, + current_prediction: None, + next_pending_prediction_id: 0, + pending_predictions: ArrayVec::new(), + last_request_timestamp: Instant::now(), + } + } +} + +#[derive(Clone)] +struct CurrentEditPrediction { + buffer_id: EntityId, + prediction: EditPrediction, +} + +impl CurrentEditPrediction { + fn should_replace_prediction(&self, old_prediction: &Self, snapshot: &BufferSnapshot) -> bool { + if self.buffer_id != old_prediction.buffer_id { + return true; + } + + let Some(old_edits) = old_prediction.prediction.interpolate(snapshot) else { + return true; + }; + let Some(new_edits) = self.prediction.interpolate(snapshot) else { + return false; + }; + + if old_edits.len() == 1 && new_edits.len() == 1 { + let (old_range, old_text) = &old_edits[0]; + let (new_range, new_text) = &new_edits[0]; + new_range == old_range && new_text.starts_with(old_text) + } else { + true + } + } +} + +struct PendingPrediction { + id: usize, + _task: Task<()>, +} + +impl EditPredictionProvider for ZetaEditPredictionProvider { + fn name() -> &'static str { + "zed-predict2" + } + + fn display_name() -> &'static str { + "Zed's Edit Predictions 2" + } + + fn show_completions_in_menu() -> bool { + true + } + + fn show_tab_accept_marker() -> bool { + true + } + + fn data_collection_state(&self, _cx: &App) -> DataCollectionState { + // TODO [zeta2] + DataCollectionState::Unsupported + } + + fn toggle_data_collection(&mut self, _cx: &mut App) { + // TODO [zeta2] + } + + fn usage(&self, cx: &App) -> Option { + self.zeta.read(cx).usage(cx) + } + + fn is_enabled( + &self, + _buffer: &Entity, + _cursor_position: language::Anchor, + _cx: &App, + ) -> bool { + true + } + + fn is_refreshing(&self) -> bool { + !self.pending_predictions.is_empty() + } + + fn refresh( + &mut self, + project: Option>, + buffer: Entity, + cursor_position: language::Anchor, + _debounce: bool, + cx: &mut Context, + ) { + let Some(project) = project else { + return; + }; + + if self + .zeta + .read(cx) + .user_store + .read_with(cx, |user_store, _cx| { + user_store.account_too_young() || user_store.has_overdue_invoices() + }) + { + return; + } + + if let Some(current_prediction) = self.current_prediction.as_ref() { + let snapshot = buffer.read(cx).snapshot(); + if current_prediction + .prediction + .interpolate(&snapshot) + .is_some() + { + return; + } + } + + let pending_prediction_id = self.next_pending_prediction_id; + self.next_pending_prediction_id += 1; + let last_request_timestamp = self.last_request_timestamp; + + let task = cx.spawn(async move |this, cx| { + if let Some(timeout) = (last_request_timestamp + Self::THROTTLE_TIMEOUT) + .checked_duration_since(Instant::now()) + { + cx.background_executor().timer(timeout).await; + } + + let prediction_request = this.update(cx, |this, cx| { + this.last_request_timestamp = Instant::now(); + this.zeta.update(cx, |zeta, cx| { + zeta.request_prediction(&project, &buffer, cursor_position, cx) + }) + }); + + let prediction = match prediction_request { + Ok(prediction_request) => { + let prediction_request = prediction_request.await; + prediction_request.map(|c| { + c.map(|prediction| CurrentEditPrediction { + buffer_id: buffer.entity_id(), + prediction, + }) + }) + } + Err(error) => Err(error), + }; + + this.update(cx, |this, cx| { + if this.pending_predictions[0].id == pending_prediction_id { + this.pending_predictions.remove(0); + } else { + this.pending_predictions.clear(); + } + + let Some(new_prediction) = prediction + .context("edit prediction failed") + .log_err() + .flatten() + else { + cx.notify(); + return; + }; + + if let Some(old_prediction) = this.current_prediction.as_ref() { + let snapshot = buffer.read(cx).snapshot(); + if new_prediction.should_replace_prediction(old_prediction, &snapshot) { + this.current_prediction = Some(new_prediction); + } + } else { + this.current_prediction = Some(new_prediction); + } + + cx.notify(); + }) + .ok(); + }); + + // We always maintain at most two pending predictions. When we already + // have two, we replace the newest one. + if self.pending_predictions.len() <= 1 { + self.pending_predictions.push(PendingPrediction { + id: pending_prediction_id, + _task: task, + }); + } else if self.pending_predictions.len() == 2 { + self.pending_predictions.pop(); + self.pending_predictions.push(PendingPrediction { + id: pending_prediction_id, + _task: task, + }); + } + + cx.notify(); + } + + fn cycle( + &mut self, + _buffer: Entity, + _cursor_position: language::Anchor, + _direction: Direction, + _cx: &mut Context, + ) { + } + + fn accept(&mut self, _cx: &mut Context) { + // TODO [zeta2] report accept + self.current_prediction.take(); + self.pending_predictions.clear(); + } + + fn discard(&mut self, _cx: &mut Context) { + self.pending_predictions.clear(); + self.current_prediction.take(); + } + + fn suggest( + &mut self, + buffer: &Entity, + cursor_position: language::Anchor, + cx: &mut Context, + ) -> Option { + let CurrentEditPrediction { + buffer_id, + prediction, + .. + } = self.current_prediction.as_mut()?; + + // Invalidate previous prediction if it was generated for a different buffer. + if *buffer_id != buffer.entity_id() { + self.current_prediction.take(); + return None; + } + + let buffer = buffer.read(cx); + let Some(edits) = prediction.interpolate(&buffer.snapshot()) else { + self.current_prediction.take(); + return None; + }; + + let cursor_row = cursor_position.to_point(buffer).row; + let (closest_edit_ix, (closest_edit_range, _)) = + edits.iter().enumerate().min_by_key(|(_, (range, _))| { + let distance_from_start = cursor_row.abs_diff(range.start.to_point(buffer).row); + let distance_from_end = cursor_row.abs_diff(range.end.to_point(buffer).row); + cmp::min(distance_from_start, distance_from_end) + })?; + + let mut edit_start_ix = closest_edit_ix; + for (range, _) in edits[..edit_start_ix].iter().rev() { + let distance_from_closest_edit = + closest_edit_range.start.to_point(buffer).row - range.end.to_point(buffer).row; + if distance_from_closest_edit <= 1 { + edit_start_ix -= 1; + } else { + break; + } + } + + let mut edit_end_ix = closest_edit_ix + 1; + for (range, _) in &edits[edit_end_ix..] { + let distance_from_closest_edit = + range.start.to_point(buffer).row - closest_edit_range.end.to_point(buffer).row; + if distance_from_closest_edit <= 1 { + edit_end_ix += 1; + } else { + break; + } + } + + Some(edit_prediction::EditPrediction { + id: Some(prediction.id.to_string().into()), + edits: edits[edit_start_ix..edit_end_ix].to_vec(), + edit_preview: Some(prediction.edit_preview.clone()), + }) + } +} diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 8986fb20740327f5598611d981b53569edcb559e..8af20a6236e06c78d50b9f3c22609adfbe5571e2 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -1,5 +1,4 @@ use anyhow::{Context as _, Result, anyhow}; -use arrayvec::ArrayVec; use chrono::TimeDelta; use client::{Client, EditPredictionUsage, UserStore}; use cloud_llm_client::predict_edits_v3::{self, Signature}; @@ -7,7 +6,6 @@ use cloud_llm_client::{ EXPIRED_LLM_TOKEN_HEADER_NAME, MINIMUM_REQUIRED_VERSION_HEADER_NAME, ZED_VERSION_HEADER_NAME, }; use cloud_zeta2_prompt::DEFAULT_MAX_PROMPT_BYTES; -use edit_prediction::{DataCollectionState, Direction, EditPredictionProvider}; use edit_prediction_context::{ DeclarationId, EditPredictionContext, EditPredictionExcerptOptions, SyntaxIndex, SyntaxIndexState, @@ -19,26 +17,26 @@ use gpui::{ App, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task, WeakEntity, http_client, prelude::*, }; -use language::{ - Anchor, Buffer, DiagnosticSet, LanguageServerId, OffsetRangeExt as _, ToOffset as _, ToPoint, - text_diff, -}; -use language::{BufferSnapshot, EditPreview}; +use language::BufferSnapshot; +use language::{Buffer, DiagnosticSet, LanguageServerId, ToOffset as _, ToPoint}; use language_model::{LlmApiToken, RefreshLlmTokenListener}; use project::Project; use release_channel::AppVersion; -use std::borrow::Cow; -use std::cmp; use std::collections::{HashMap, VecDeque, hash_map}; use std::path::PathBuf; use std::str::FromStr as _; +use std::sync::Arc; use std::time::{Duration, Instant}; -use std::{ops::Range, sync::Arc}; use thiserror::Error; -use util::{ResultExt as _, some_or_debug_panic}; -use uuid::Uuid; +use util::some_or_debug_panic; use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification}; +mod prediction; +mod provider; + +use crate::prediction::{EditPrediction, edits_from_response, interpolate_edits}; +pub use provider::ZetaEditPredictionProvider; + const BUFFER_CHANGE_GROUPING_INTERVAL: Duration = Duration::from_secs(1); /// Maximum number of events to track. @@ -441,7 +439,7 @@ impl Zeta { } let (response, usage) = response?; - let edits = Self::compute_edits(&response.edits, &snapshot); + let edits = edits_from_response(&response.edits, &snapshot); anyhow::Ok(Some((response.request_id, edits, usage))) } @@ -466,7 +464,7 @@ impl Zeta { buffer.read_with(cx, |buffer, cx| { let new_snapshot = buffer.snapshot(); let edits: Arc<[_]> = - interpolate(&snapshot, &new_snapshot, edits)?.into(); + interpolate_edits(&snapshot, &new_snapshot, edits)?.into(); Some((edits.clone(), new_snapshot, buffer.preview_edits(edits, cx))) })? else { @@ -474,7 +472,7 @@ impl Zeta { }; Ok(Some(EditPrediction { - id: EditPredictionId(id), + id: id.into(), edits, snapshot, edit_preview: edit_preview_task.await, @@ -585,62 +583,6 @@ impl Zeta { } } - fn compute_edits( - edits: &[predict_edits_v3::Edit], - snapshot: &BufferSnapshot, - ) -> Arc<[(Range, String)]> { - edits - .iter() - .flat_map(|edit| { - // TODO multi-file edits - let old_text = snapshot.text_for_range(edit.range.clone()); - - Self::compute_excerpt_edits( - old_text.collect::>(), - &edit.content, - edit.range.start, - &snapshot, - ) - }) - .collect::>() - .into() - } - - fn compute_excerpt_edits( - old_text: Cow, - new_text: &str, - offset: usize, - snapshot: &BufferSnapshot, - ) -> impl Iterator, String)> { - text_diff(&old_text, new_text) - .into_iter() - .map(move |(mut old_range, new_text)| { - old_range.start += offset; - old_range.end += offset; - - let prefix_len = common_prefix( - snapshot.chars_for_range(old_range.clone()), - new_text.chars(), - ); - old_range.start += prefix_len; - - let suffix_len = common_prefix( - snapshot.reversed_chars_for_range(old_range.clone()), - new_text[prefix_len..].chars().rev(), - ); - old_range.end = old_range.end.saturating_sub(suffix_len); - - let new_text = new_text[prefix_len..new_text.len() - suffix_len].to_string(); - let range = if old_range.is_empty() { - let anchor = snapshot.anchor_after(old_range.start); - anchor..anchor - } else { - snapshot.anchor_after(old_range.start)..snapshot.anchor_before(old_range.end) - }; - (range, new_text) - }) - } - fn gather_nearby_diagnostics( cursor_offset: usize, diagnostic_sets: &[(LanguageServerId, DiagnosticSet)], @@ -750,13 +692,6 @@ impl Zeta { } } -fn common_prefix, T2: Iterator>(a: T1, b: T2) -> usize { - a.zip(b) - .take_while(|(a, b)| a == b) - .map(|(a, _)| a.len_utf8()) - .sum() -} - #[derive(Error, Debug)] #[error( "You must update to Zed version {minimum_version} or higher to continue using edit predictions." @@ -765,341 +700,6 @@ pub struct ZedUpdateRequiredError { minimum_version: SemanticVersion, } -pub struct ZetaEditPredictionProvider { - zeta: Entity, - current_prediction: Option, - next_pending_prediction_id: usize, - pending_predictions: ArrayVec, - last_request_timestamp: Instant, -} - -impl ZetaEditPredictionProvider { - pub const THROTTLE_TIMEOUT: Duration = Duration::from_millis(300); - - pub fn new( - project: Option<&Entity>, - client: &Arc, - user_store: &Entity, - cx: &mut App, - ) -> Self { - let zeta = Zeta::global(client, user_store, cx); - if let Some(project) = project { - zeta.update(cx, |zeta, cx| { - zeta.register_project(project, cx); - }); - } - - Self { - zeta, - current_prediction: None, - next_pending_prediction_id: 0, - pending_predictions: ArrayVec::new(), - last_request_timestamp: Instant::now(), - } - } -} - -#[derive(Clone)] -struct CurrentEditPrediction { - buffer_id: EntityId, - prediction: EditPrediction, -} - -impl CurrentEditPrediction { - fn should_replace_prediction(&self, old_prediction: &Self, snapshot: &BufferSnapshot) -> bool { - if self.buffer_id != old_prediction.buffer_id { - return true; - } - - let Some(old_edits) = old_prediction.prediction.interpolate(snapshot) else { - return true; - }; - let Some(new_edits) = self.prediction.interpolate(snapshot) else { - return false; - }; - - if old_edits.len() == 1 && new_edits.len() == 1 { - let (old_range, old_text) = &old_edits[0]; - let (new_range, new_text) = &new_edits[0]; - new_range == old_range && new_text.starts_with(old_text) - } else { - true - } - } -} - -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] -pub struct EditPredictionId(Uuid); - -impl From for gpui::ElementId { - fn from(value: EditPredictionId) -> Self { - gpui::ElementId::Uuid(value.0) - } -} - -impl std::fmt::Display for EditPredictionId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Clone)] -pub struct EditPrediction { - id: EditPredictionId, - edits: Arc<[(Range, String)]>, - snapshot: BufferSnapshot, - edit_preview: EditPreview, -} - -impl EditPrediction { - fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option, String)>> { - interpolate(&self.snapshot, new_snapshot, self.edits.clone()) - } -} - -struct PendingPrediction { - id: usize, - _task: Task<()>, -} - -impl EditPredictionProvider for ZetaEditPredictionProvider { - fn name() -> &'static str { - "zed-predict2" - } - - fn display_name() -> &'static str { - "Zed's Edit Predictions 2" - } - - fn show_completions_in_menu() -> bool { - true - } - - fn show_tab_accept_marker() -> bool { - true - } - - fn data_collection_state(&self, _cx: &App) -> DataCollectionState { - // TODO [zeta2] - DataCollectionState::Unsupported - } - - fn toggle_data_collection(&mut self, _cx: &mut App) { - // TODO [zeta2] - } - - fn usage(&self, cx: &App) -> Option { - self.zeta.read(cx).usage(cx) - } - - fn is_enabled( - &self, - _buffer: &Entity, - _cursor_position: language::Anchor, - _cx: &App, - ) -> bool { - true - } - - fn is_refreshing(&self) -> bool { - !self.pending_predictions.is_empty() - } - - fn refresh( - &mut self, - project: Option>, - buffer: Entity, - cursor_position: language::Anchor, - _debounce: bool, - cx: &mut Context, - ) { - let Some(project) = project else { - return; - }; - - if self - .zeta - .read(cx) - .user_store - .read_with(cx, |user_store, _cx| { - user_store.account_too_young() || user_store.has_overdue_invoices() - }) - { - return; - } - - if let Some(current_prediction) = self.current_prediction.as_ref() { - let snapshot = buffer.read(cx).snapshot(); - if current_prediction - .prediction - .interpolate(&snapshot) - .is_some() - { - return; - } - } - - let pending_prediction_id = self.next_pending_prediction_id; - self.next_pending_prediction_id += 1; - let last_request_timestamp = self.last_request_timestamp; - - let task = cx.spawn(async move |this, cx| { - if let Some(timeout) = (last_request_timestamp + Self::THROTTLE_TIMEOUT) - .checked_duration_since(Instant::now()) - { - cx.background_executor().timer(timeout).await; - } - - let prediction_request = this.update(cx, |this, cx| { - this.last_request_timestamp = Instant::now(); - this.zeta.update(cx, |zeta, cx| { - zeta.request_prediction(&project, &buffer, cursor_position, cx) - }) - }); - - let prediction = match prediction_request { - Ok(prediction_request) => { - let prediction_request = prediction_request.await; - prediction_request.map(|c| { - c.map(|prediction| CurrentEditPrediction { - buffer_id: buffer.entity_id(), - prediction, - }) - }) - } - Err(error) => Err(error), - }; - - this.update(cx, |this, cx| { - if this.pending_predictions[0].id == pending_prediction_id { - this.pending_predictions.remove(0); - } else { - this.pending_predictions.clear(); - } - - let Some(new_prediction) = prediction - .context("edit prediction failed") - .log_err() - .flatten() - else { - cx.notify(); - return; - }; - - if let Some(old_prediction) = this.current_prediction.as_ref() { - let snapshot = buffer.read(cx).snapshot(); - if new_prediction.should_replace_prediction(old_prediction, &snapshot) { - this.current_prediction = Some(new_prediction); - } - } else { - this.current_prediction = Some(new_prediction); - } - - cx.notify(); - }) - .ok(); - }); - - // We always maintain at most two pending predictions. When we already - // have two, we replace the newest one. - if self.pending_predictions.len() <= 1 { - self.pending_predictions.push(PendingPrediction { - id: pending_prediction_id, - _task: task, - }); - } else if self.pending_predictions.len() == 2 { - self.pending_predictions.pop(); - self.pending_predictions.push(PendingPrediction { - id: pending_prediction_id, - _task: task, - }); - } - - cx.notify(); - } - - fn cycle( - &mut self, - _buffer: Entity, - _cursor_position: language::Anchor, - _direction: Direction, - _cx: &mut Context, - ) { - } - - fn accept(&mut self, _cx: &mut Context) { - // TODO [zeta2] report accept - self.current_prediction.take(); - self.pending_predictions.clear(); - } - - fn discard(&mut self, _cx: &mut Context) { - self.pending_predictions.clear(); - self.current_prediction.take(); - } - - fn suggest( - &mut self, - buffer: &Entity, - cursor_position: language::Anchor, - cx: &mut Context, - ) -> Option { - let CurrentEditPrediction { - buffer_id, - prediction, - .. - } = self.current_prediction.as_mut()?; - - // Invalidate previous prediction if it was generated for a different buffer. - if *buffer_id != buffer.entity_id() { - self.current_prediction.take(); - return None; - } - - let buffer = buffer.read(cx); - let Some(edits) = prediction.interpolate(&buffer.snapshot()) else { - self.current_prediction.take(); - return None; - }; - - let cursor_row = cursor_position.to_point(buffer).row; - let (closest_edit_ix, (closest_edit_range, _)) = - edits.iter().enumerate().min_by_key(|(_, (range, _))| { - let distance_from_start = cursor_row.abs_diff(range.start.to_point(buffer).row); - let distance_from_end = cursor_row.abs_diff(range.end.to_point(buffer).row); - cmp::min(distance_from_start, distance_from_end) - })?; - - let mut edit_start_ix = closest_edit_ix; - for (range, _) in edits[..edit_start_ix].iter().rev() { - let distance_from_closest_edit = - closest_edit_range.start.to_point(buffer).row - range.end.to_point(buffer).row; - if distance_from_closest_edit <= 1 { - edit_start_ix -= 1; - } else { - break; - } - } - - let mut edit_end_ix = closest_edit_ix + 1; - for (range, _) in &edits[edit_end_ix..] { - let distance_from_closest_edit = - range.start.to_point(buffer).row - closest_edit_range.end.to_point(buffer).row; - if distance_from_closest_edit <= 1 { - edit_end_ix += 1; - } else { - break; - } - } - - Some(edit_prediction::EditPrediction { - id: Some(prediction.id.to_string().into()), - edits: edits[edit_start_ix..edit_end_ix].to_vec(), - edit_preview: Some(prediction.edit_preview.clone()), - }) - } -} - fn make_cloud_request( excerpt_path: PathBuf, context: EditPredictionContext, @@ -1215,238 +815,3 @@ fn add_signature( declaration_to_signature_index.insert(declaration_id, signature_index); Some(signature_index) } - -fn interpolate( - old_snapshot: &BufferSnapshot, - new_snapshot: &BufferSnapshot, - current_edits: Arc<[(Range, String)]>, -) -> Option, String)>> { - let mut edits = Vec::new(); - - let mut model_edits = current_edits.iter().peekable(); - for user_edit in new_snapshot.edits_since::(&old_snapshot.version) { - while let Some((model_old_range, _)) = model_edits.peek() { - let model_old_range = model_old_range.to_offset(old_snapshot); - if model_old_range.end < user_edit.old.start { - let (model_old_range, model_new_text) = model_edits.next().unwrap(); - edits.push((model_old_range.clone(), model_new_text.clone())); - } else { - break; - } - } - - if let Some((model_old_range, model_new_text)) = model_edits.peek() { - let model_old_offset_range = model_old_range.to_offset(old_snapshot); - if user_edit.old == model_old_offset_range { - let user_new_text = new_snapshot - .text_for_range(user_edit.new.clone()) - .collect::(); - - if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) { - if !model_suffix.is_empty() { - let anchor = old_snapshot.anchor_after(user_edit.old.end); - edits.push((anchor..anchor, model_suffix.to_string())); - } - - model_edits.next(); - continue; - } - } - } - - return None; - } - - edits.extend(model_edits.cloned()); - - if edits.is_empty() { None } else { Some(edits) } -} - -#[cfg(test)] -mod tests { - use super::*; - use gpui::TestAppContext; - use indoc::indoc; - - #[gpui::test] - async fn test_compute_edits(cx: &mut TestAppContext) { - let old = indoc! {r#" - fn main() { - let args = - println!("{}", args[1]) - } - "#}; - - let new = indoc! {r#" - fn main() { - let args = std::env::args(); - println!("{}", args[1]); - } - "#}; - - let buffer = cx.new(|cx| Buffer::local(old, cx)); - let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot()); - - // TODO cover more cases when multi-file is supported - let big_edits = vec![predict_edits_v3::Edit { - path: PathBuf::from("test.txt"), - range: 0..old.len(), - content: new.into(), - }]; - - let edits = Zeta::compute_edits(&big_edits, &snapshot); - assert_eq!(edits.len(), 2); - assert_eq!( - edits[0].0.to_point(&snapshot).start, - language::Point::new(1, 14) - ); - assert_eq!(edits[0].1, " std::env::args();"); - assert_eq!( - edits[1].0.to_point(&snapshot).start, - language::Point::new(2, 27) - ); - assert_eq!(edits[1].1, ";"); - } - - #[gpui::test] - async fn test_edit_prediction_basic_interpolation(cx: &mut TestAppContext) { - let buffer = cx.new(|cx| Buffer::local("Lorem ipsum dolor", cx)); - let edits: Arc<[(Range, String)]> = cx.update(|cx| { - to_prediction_edits( - [(2..5, "REM".to_string()), (9..11, "".to_string())], - &buffer, - cx, - ) - .into() - }); - - let edit_preview = cx - .read(|cx| buffer.read(cx).preview_edits(edits.clone(), cx)) - .await; - - let prediction = EditPrediction { - id: EditPredictionId(Uuid::new_v4()), - edits, - snapshot: cx.read(|cx| buffer.read(cx).snapshot()), - edit_preview, - }; - - cx.update(|cx| { - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(2..5, "REM".to_string()), (9..11, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "")], None, cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(2..2, "REM".to_string()), (6..8, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.undo(cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(2..5, "REM".to_string()), (9..11, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "R")], None, cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(3..3, "EM".to_string()), (7..9, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "E")], None, cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(4..4, "M".to_string()), (8..10, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "M")], None, cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(9..11, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "")], None, cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(4..4, "M".to_string()), (8..10, "".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(8..10, "")], None, cx)); - assert_eq!( - from_prediction_edits( - &prediction.interpolate(&buffer.read(cx).snapshot()).unwrap(), - &buffer, - cx - ), - vec![(4..4, "M".to_string())] - ); - - buffer.update(cx, |buffer, cx| buffer.edit([(4..6, "")], None, cx)); - assert_eq!(prediction.interpolate(&buffer.read(cx).snapshot()), None); - }) - } - - fn to_prediction_edits( - iterator: impl IntoIterator, String)>, - buffer: &Entity, - cx: &App, - ) -> Vec<(Range, String)> { - let buffer = buffer.read(cx); - iterator - .into_iter() - .map(|(range, text)| { - ( - buffer.anchor_after(range.start)..buffer.anchor_before(range.end), - text, - ) - }) - .collect() - } - - fn from_prediction_edits( - editor_edits: &[(Range, String)], - buffer: &Entity, - cx: &App, - ) -> Vec<(Range, String)> { - let buffer = buffer.read(cx); - editor_edits - .iter() - .map(|(range, text)| { - ( - range.start.to_offset(buffer)..range.end.to_offset(buffer), - text.clone(), - ) - }) - .collect() - } -} From 6f3e66d0277f32c3db5c07b00629a0e5fdc35c62 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:59:42 -0300 Subject: [PATCH 244/721] Adjust stash picker design (#38789) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just making it more consistent with other pickers—button actions justified to the right and timestamp directly in the list item to avoid as much as possible relevant information tucked away in a tooltip where using the keyboard will mostly be the main mean of interaction. Screenshot 2025-09-24 at 10  41@2x Release Notes: - N/A --- crates/git_ui/src/stash_picker.rs | 123 +++++++++++++++--------------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs index 4d142d6383a9a4e79e342420bada00c790770902..d82498007d3d38e509e34e86044fa0a0e188c910 100644 --- a/crates/git_ui/src/stash_picker.rs +++ b/crates/git_ui/src/stash_picker.rs @@ -400,27 +400,38 @@ impl PickerDelegate for StashListDelegate { let stash_label = HighlightedLabel::new(stash_message, positions) .truncate() .into_any_element(); + let branch_name = entry_match.entry.branch.clone().unwrap_or_default(); let branch_label = h_flex() - .gap_1() + .gap_1p5() .w_full() .child( - Icon::new(IconName::GitBranch) + h_flex() + .gap_0p5() + .child( + Icon::new(IconName::GitBranch) + .color(Color::Muted) + .size(IconSize::Small), + ) + .child( + Label::new(branch_name) + .truncate() + .color(Color::Muted) + .size(LabelSize::Small), + ), + ) + .child( + Label::new("•") + .alpha(0.5) .color(Color::Muted) - .size(IconSize::Small), + .size(LabelSize::Small), ) .child( - Label::new(branch_name) - .truncate() + Label::new(entry_match.formatted_timestamp.clone()) .color(Color::Muted) .size(LabelSize::Small), ); - let tooltip_text = format!( - "stash@{{{}}} created {}", - entry_match.entry.index, entry_match.formatted_timestamp - ); - Some( ListItem::new(SharedString::from(format!("stash-{ix}"))) .inset(true) @@ -433,7 +444,10 @@ impl PickerDelegate for StashListDelegate { .child(stash_label) .child(branch_label.into_element()), ) - .tooltip(Tooltip::text(tooltip_text)), + .tooltip(Tooltip::text(format!( + "stash@{{{}}}", + entry_match.entry.index + ))), ) } @@ -452,60 +466,49 @@ impl PickerDelegate for StashListDelegate { h_flex() .w_full() .p_1p5() - .justify_between() + .gap_0p5() + .justify_end() .border_t_1() .border_color(cx.theme().colors().border_variant) .child( - h_flex() - .gap_0p5() - .child( - Button::new("apply-stash", "Apply") - .key_binding( - KeyBinding::for_action_in( - &menu::Confirm, - &focus_handle, - window, - cx, - ) - .map(|kb| kb.size(rems_from_px(12.))), - ) - .on_click(|_, window, cx| { - window.dispatch_action(menu::Confirm.boxed_clone(), cx) - }), + Button::new("apply-stash", "Apply") + .key_binding( + KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx) + .map(|kb| kb.size(rems_from_px(12.))), ) - .child( - Button::new("pop-stash", "Pop") - .key_binding( - KeyBinding::for_action_in( - &menu::SecondaryConfirm, - &focus_handle, - window, - cx, - ) - .map(|kb| kb.size(rems_from_px(12.))), - ) - .on_click(|_, window, cx| { - window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx) - }), + .on_click(|_, window, cx| { + window.dispatch_action(menu::Confirm.boxed_clone(), cx) + }), + ) + .child( + Button::new("pop-stash", "Pop") + .key_binding( + KeyBinding::for_action_in( + &menu::SecondaryConfirm, + &focus_handle, + window, + cx, + ) + .map(|kb| kb.size(rems_from_px(12.))), + ) + .on_click(|_, window, cx| { + window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx) + }), + ) + .child( + Button::new("drop-stash", "Drop") + .key_binding( + KeyBinding::for_action_in( + &stash_picker::DropStashItem, + &focus_handle, + window, + cx, + ) + .map(|kb| kb.size(rems_from_px(12.))), ) - .child( - Button::new("drop-stash", "Drop") - .key_binding( - KeyBinding::for_action_in( - &stash_picker::DropStashItem, - &focus_handle, - window, - cx, - ) - .map(|kb| kb.size(rems_from_px(12.))), - ) - .on_click(|_, window, cx| { - window.dispatch_action( - stash_picker::DropStashItem.boxed_clone(), - cx, - ) - }), - ), + .on_click(|_, window, cx| { + window.dispatch_action(stash_picker::DropStashItem.boxed_clone(), cx) + }), ) .into_any(), ) From 53885c00d3b3415ffd25814ab8557a2d50b8d8f7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 24 Sep 2025 08:45:14 -0700 Subject: [PATCH 245/721] Start up settings UI 2 (#38673) Release Notes: - N/A --------- Co-authored-by: Anthony Co-authored-by: Ben Kunkle Co-authored-by: Anthony Co-authored-by: Ben Kunkle --- Cargo.lock | 26 ++ Cargo.toml | 1 + crates/settings/Cargo.toml | 1 + crates/settings/src/keymap_file.rs | 1 - crates/settings/src/settings_store.rs | 8 +- crates/settings_ui/Cargo.toml | 44 +++ crates/settings_ui/LICENSE-GPL | 1 + crates/settings_ui/examples/ui.rs | 48 +++ crates/settings_ui/src/settings_ui.rs | 446 ++++++++++++++++++++++++++ crates/zed/Cargo.toml | 15 +- crates/zed/src/main.rs | 1 + 11 files changed, 582 insertions(+), 10 deletions(-) create mode 100644 crates/settings_ui/Cargo.toml create mode 120000 crates/settings_ui/LICENSE-GPL create mode 100644 crates/settings_ui/examples/ui.rs create mode 100644 crates/settings_ui/src/settings_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 46de7e804b2b13ff775ac87953bb7c9593c39120..c3c374fb12a7feb61770016d714115759d6c6ef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14502,6 +14502,31 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "settings_ui" +version = "0.1.0" +dependencies = [ + "anyhow", + "assets", + "command_palette_hooks", + "editor", + "feature_flags", + "fs", + "futures 0.3.31", + "gpui", + "language", + "menu", + "paths", + "project", + "serde", + "settings", + "theme", + "ui", + "workspace", + "workspace-hack", + "zlog", +] + [[package]] name = "sha1" version = "0.10.6" @@ -20253,6 +20278,7 @@ dependencies = [ "session", "settings", "settings_profile_selector", + "settings_ui", "shellexpand 2.1.2", "smol", "snippet_provider", diff --git a/Cargo.toml b/Cargo.toml index a06ba76326e1b3c6482507119c824f143e26065a..4d51d0577051280305330a6481b0138027dd0b24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,6 +151,7 @@ members = [ "crates/settings", "crates/settings_macros", "crates/settings_profile_selector", + "crates/settings_ui", "crates/snippet", "crates/snippet_provider", "crates/snippets_ui", diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 4790df4d95f676b7e7170e86b6d05976cbd3c69a..cfa3238ad65b22c1dcba2daaa9e70322819a493a 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -45,6 +45,7 @@ zlog.workspace = true [dev-dependencies] fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } + indoc.workspace = true pretty_assertions.workspace = true unindent.workspace = true diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 6e9ab5c34d5749e3cdaf0f144930353f2323f17b..10421ef8f8984a7b5af58ec06d12e21889ea3bba 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -177,7 +177,6 @@ impl KeymapFile { } } - #[cfg(feature = "test-support")] pub fn load_asset_allow_partial_failure( asset_path: &str, cx: &App, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 040122bc1d1fde4387e4ef0c92e1d71b0420b5c6..c5eb80c48c4481646446bb58f92378d54801615c 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -138,7 +138,6 @@ pub struct SettingsLocation<'a> { pub path: &'a Path, } -/// A set of strongly-typed setting values defined via multiple config files. pub struct SettingsStore { setting_values: HashMap>, default_settings: Rc, @@ -318,7 +317,7 @@ impl SettingsStore { .set_global_value(Box::new(value)) } - /// Get the user's settings as a raw JSON value. + /// Get the user's settings content. /// /// For user-facing functionality use the typed setting interface. /// (e.g. ProjectSettings::get_global(cx)) @@ -326,6 +325,11 @@ impl SettingsStore { self.user_settings.as_ref() } + /// Get the default settings content as a raw JSON value. + pub fn raw_default_settings(&self) -> &SettingsContent { + &self.default_settings + } + /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator { self.user_settings diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4dd1ecdb7f2a425b20f04a623853af75d3006ec2 --- /dev/null +++ b/crates/settings_ui/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "settings_ui" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/settings_ui.rs" + +[features] +default = [] +test-support = [] + +[dependencies] +project.workspace = true +fs.workspace = true +anyhow.workspace = true +command_palette_hooks.workspace = true +editor.workspace = true +feature_flags.workspace = true +gpui.workspace = true +menu.workspace = true +serde.workspace = true +settings.workspace = true +theme.workspace = true +ui.workspace = true +workspace-hack.workspace = true +workspace.workspace = true + +[dev-dependencies] +settings = { workspace = true, features = ["test-support"] } +futures.workspace = true +language.workspace = true +assets.workspace = true +paths.workspace = true +zlog.workspace = true + +[[example]] +name = "ui" +path = "examples/ui.rs" diff --git a/crates/settings_ui/LICENSE-GPL b/crates/settings_ui/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/settings_ui/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/settings_ui/examples/ui.rs b/crates/settings_ui/examples/ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..46b5fa02a45d94d0094a12db90746b7889ce6ba5 --- /dev/null +++ b/crates/settings_ui/examples/ui.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use futures::StreamExt; +use settings::{DEFAULT_KEYMAP_PATH, KeymapFile, SettingsStore, watch_config_file}; +use settings_ui::open_settings_editor; +use ui::BorrowAppContext; + +fn main() { + let app = gpui::Application::new().with_assets(assets::Assets); + + let fs = Arc::new(fs::RealFs::new(None, app.background_executor())); + let mut user_settings_file_rx = watch_config_file( + &app.background_executor(), + fs.clone(), + paths::settings_file().clone(), + ); + zlog::init(); + zlog::init_output_stderr(); + + app.run(move |cx| { + ::set_global(fs.clone(), cx); + settings::init(cx); + theme::init(theme::LoadThemes::JustBase, cx); + workspace::init_settings(cx); + project::Project::init_settings(cx); + language::init(cx); + editor::init(cx); + menu::init(); + + let keybindings = + KeymapFile::load_asset_allow_partial_failure(DEFAULT_KEYMAP_PATH, cx).unwrap(); + cx.bind_keys(keybindings); + cx.spawn(async move |cx| { + while let Some(content) = user_settings_file_rx.next().await { + cx.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.set_user_settings(&content, cx).unwrap() + }) + }) + .ok(); + } + }) + .detach(); + + open_settings_editor(cx).unwrap(); + cx.activate(true); + }); +} diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..86e8b61ec6a439033f43a9e085282383ad619c26 --- /dev/null +++ b/crates/settings_ui/src/settings_ui.rs @@ -0,0 +1,446 @@ +//! # settings_ui +use std::{rc::Rc, sync::Arc}; + +use editor::Editor; +use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; +use gpui::{ + App, AppContext as _, Context, Div, Entity, IntoElement, ReadGlobal as _, Render, Window, + WindowHandle, WindowOptions, actions, div, px, size, +}; +use project::WorktreeId; +use settings::{SettingsContent, SettingsStore}; +use std::path::Path; +use ui::{ + ActiveTheme as _, AnyElement, BorrowAppContext as _, Button, Clickable as _, Color, + FluentBuilder as _, Icon, IconName, InteractiveElement as _, Label, LabelCommon as _, + LabelSize, ParentElement, SharedString, StatefulInteractiveElement as _, Styled, Switch, + v_flex, +}; + +fn user_settings_data() -> Vec { + vec![ + SettingsPage { + title: "General Page", + items: vec![ + SettingsPageItem::SectionHeader("General Section"), + SettingsPageItem::SettingItem(SettingItem { + title: "Confirm Quit", + description: "Whether to confirm before quitting Zed", + render: Rc::new(|_, cx| { + render_toggle_button( + "confirm_quit", + SettingsFile::User, + cx, + |settings_content| &mut settings_content.workspace.confirm_quit, + ) + }), + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Auto Update", + description: "Automatically update Zed (may be ignored on Linux if installed through a package manager)", + render: Rc::new(|_, cx| { + render_toggle_button( + "Auto Update", + SettingsFile::User, + cx, + |settings_content| &mut settings_content.auto_update, + ) + }), + }), + ], + }, + SettingsPage { + title: "Project", + items: vec![ + SettingsPageItem::SectionHeader("Worktree Settings Content"), + SettingsPageItem::SettingItem(SettingItem { + title: "Project Name", + description: "The displayed name of this project. If not set, the root directory name", + render: Rc::new(|window, cx| { + render_text_field( + "project_name", + SettingsFile::User, + window, + cx, + |settings_content| &mut settings_content.project.worktree.project_name, + ) + }), + }), + ], + }, + ] +} + +fn project_settings_data() -> Vec { + vec![SettingsPage { + title: "Project", + items: vec![ + SettingsPageItem::SectionHeader("Worktree Settings Content"), + SettingsPageItem::SettingItem(SettingItem { + title: "Project Name", + description: " The displayed name of this project. If not set, the root directory name", + render: Rc::new(|window, cx| { + render_text_field( + "project_name", + SettingsFile::Local(( + WorktreeId::from_usize(0), + Arc::from(Path::new("TODO: actually pass through file")), + )), + window, + cx, + |settings_content| &mut settings_content.project.worktree.project_name, + ) + }), + }), + ], + }] +} + +pub struct SettingsUiFeatureFlag; + +impl FeatureFlag for SettingsUiFeatureFlag { + const NAME: &'static str = "settings-ui"; +} + +actions!( + zed, + [ + /// Opens Settings Editor. + OpenSettingsEditor + ] +); + +pub fn init(cx: &mut App) { + cx.observe_new(|workspace: &mut workspace::Workspace, _, _| { + workspace.register_action_renderer(|div, _, _, cx| { + let settings_ui_actions = [std::any::TypeId::of::()]; + let has_flag = cx.has_flag::(); + command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _| { + if has_flag { + filter.show_action_types(&settings_ui_actions); + } else { + filter.hide_action_types(&settings_ui_actions); + } + }); + if has_flag { + div.on_action(cx.listener(|_, _: &OpenSettingsEditor, _, cx| { + open_settings_editor(cx).ok(); + })) + } else { + div + } + }); + }) + .detach(); +} + +pub fn open_settings_editor(cx: &mut App) -> anyhow::Result> { + cx.open_window( + WindowOptions { + titlebar: None, + focus: true, + show: true, + kind: gpui::WindowKind::Normal, + window_min_size: Some(size(px(300.), px(500.))), // todo(settings_ui): Does this min_size make sense? + ..Default::default() + }, + |window, cx| cx.new(|cx| SettingsWindow::new(window, cx)), + ) +} + +pub struct SettingsWindow { + files: Vec, + current_file: SettingsFile, + pages: Vec, + search: Entity, + current_page: usize, // Index into pages - should probably be (usize, Option) for section + page +} + +#[derive(Clone)] +struct SettingsPage { + title: &'static str, + items: Vec, +} + +#[derive(Clone)] +enum SettingsPageItem { + SectionHeader(&'static str), + SettingItem(SettingItem), +} + +impl SettingsPageItem { + fn render(&self, window: &mut Window, cx: &mut App) -> AnyElement { + match self { + SettingsPageItem::SectionHeader(header) => Label::new(SharedString::new_static(header)) + .size(LabelSize::Large) + .into_any_element(), + SettingsPageItem::SettingItem(setting_item) => div() + .child(setting_item.title) + .child(setting_item.description) + .child((setting_item.render)(window, cx)) + .into_any_element(), + } + } +} + +impl SettingsPageItem { + fn _header(&self) -> Option<&'static str> { + match self { + SettingsPageItem::SectionHeader(header) => Some(header), + _ => None, + } + } +} + +#[derive(Clone)] +struct SettingItem { + title: &'static str, + description: &'static str, + render: std::rc::Rc AnyElement>, +} + +#[allow(unused)] +#[derive(Clone)] +enum SettingsFile { + User, // Uses all settings. + Local((WorktreeId, Arc)), // Has a special name, and special set of settings + Server(&'static str), // Uses a special name, and the user settings +} + +impl SettingsFile { + fn pages(&self) -> Vec { + match self { + SettingsFile::User => user_settings_data(), + SettingsFile::Local(_) => project_settings_data(), + SettingsFile::Server(_) => user_settings_data(), + } + } + + fn name(&self) -> String { + match self { + SettingsFile::User => "User".to_string(), + SettingsFile::Local((_, path)) => format!("Local ({})", path.display()), + SettingsFile::Server(file) => format!("Server ({})", file), + } + } +} + +impl SettingsWindow { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let current_file = SettingsFile::User; + let search = cx.new(|cx| { + let mut editor = Editor::single_line(window, cx); + editor.set_placeholder_text("Search Settings", window, cx); + editor + }); + let mut this = Self { + files: vec![ + SettingsFile::User, + SettingsFile::Local(( + WorktreeId::from_usize(0), + Arc::from(Path::new("/my-project/")), + )), + ], + current_file: current_file, + pages: vec![], + current_page: 0, + search, + }; + cx.observe_global_in::(window, move |_, _, cx| { + cx.notify(); + }) + .detach(); + + this.build_ui(); + this + } + + fn build_ui(&mut self) { + self.pages = self.current_file.pages(); + } + + fn change_file(&mut self, ix: usize) { + self.current_file = self.files[ix].clone(); + self.build_ui(); + } + + fn render_files(&self, _window: &mut Window, cx: &mut Context) -> Div { + div() + .flex() + .flex_row() + .gap_1() + .children(self.files.iter().enumerate().map(|(ix, file)| { + Button::new(ix, file.name()) + .on_click(cx.listener(move |this, _, _window, _cx| this.change_file(ix))) + })) + } + + fn render_search(&self, _window: &mut Window, _cx: &mut App) -> Div { + div() + .child(Icon::new(IconName::MagnifyingGlass)) + .child(self.search.clone()) + } + + fn render_nav(&self, window: &mut Window, cx: &mut Context) -> Div { + let mut nav = v_flex() + .p_4() + .gap_2() + .child(div().h_10()) // Files spacer; + .child(self.render_search(window, cx)); + + for (ix, page) in self.pages.iter().enumerate() { + nav = nav.child( + div() + .id(page.title) + .child( + Label::new(page.title) + .size(LabelSize::Large) + .when(self.is_page_selected(ix), |this| { + this.color(Color::Selected) + }), + ) + .on_click(cx.listener(move |this, _, _, cx| { + this.current_page = ix; + cx.notify(); + })), + ); + } + nav + } + + fn render_page( + &self, + page: &SettingsPage, + window: &mut Window, + cx: &mut Context, + ) -> Div { + div() + .child(self.render_files(window, cx)) + .child(Label::new(page.title)) + .children(page.items.iter().map(|item| item.render(window, cx))) + } + + fn current_page(&self) -> &SettingsPage { + &self.pages[self.current_page] + } + + fn is_page_selected(&self, ix: usize) -> bool { + ix == self.current_page + } +} + +impl Render for SettingsWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .size_full() + .bg(cx.theme().colors().background) + .flex() + .flex_row() + .text_color(cx.theme().colors().text) + .child(self.render_nav(window, cx).w(px(300.0))) + .child(self.render_page(self.current_page(), window, cx).w_full()) + } +} + +fn write_setting_value( + get_value: fn(&mut SettingsContent) -> &mut Option, + value: Option, + cx: &mut App, +) { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_settings_file(::global(cx), move |settings, _cx| { + *get_value(settings) = value; + }); + }); +} + +fn render_text_field( + id: &'static str, + _file: SettingsFile, + window: &mut Window, + cx: &mut App, + get_value: fn(&mut SettingsContent) -> &mut Option, +) -> AnyElement { + // TODO: Updating file does not cause the editor text to reload, suspicious it may be a missing global update/notify in SettingsStore + + // TODO: in settings window state + let store = SettingsStore::global(cx); + + // TODO: This clone needs to go!! + let mut defaults = store.raw_default_settings().clone(); + let mut user_settings = store + .raw_user_settings() + .cloned() + .unwrap_or_default() + .content; + + // TODO: unwrap_or_default here because project name is null + let initial_text = get_value(user_settings.as_mut()) + .clone() + .unwrap_or_else(|| get_value(&mut defaults).clone().unwrap_or_default()); + + let editor = window.use_keyed_state((id.into(), initial_text.clone()), cx, { + move |window, cx| { + let mut editor = Editor::single_line(window, cx); + editor.set_text(initial_text, window, cx); + editor + } + }); + + let weak_editor = editor.downgrade(); + let theme_colors = cx.theme().colors(); + + div() + .child(editor) + .bg(theme_colors.editor_background) + .border_1() + .rounded_lg() + .border_color(theme_colors.border) + .on_action::({ + move |_, _, cx| { + let Some(editor) = weak_editor.upgrade() else { + return; + }; + let new_value = editor.read_with(cx, |editor, cx| editor.text(cx)); + let new_value = (!new_value.is_empty()).then_some(new_value); + write_setting_value(get_value, new_value, cx); + editor.update(cx, |_, cx| { + cx.notify(); + }); + } + }) + .into_any_element() +} + +fn render_toggle_button( + id: &'static str, + _: SettingsFile, + cx: &mut App, + get_value: fn(&mut SettingsContent) -> &mut Option, +) -> AnyElement { + // TODO: in settings window state + let store = SettingsStore::global(cx); + + // TODO: This clone needs to go!! + let mut defaults = store.raw_default_settings().clone(); + let mut user_settings = store + .raw_user_settings() + .cloned() + .unwrap_or_default() + .content; + + let toggle_state = + if get_value(&mut user_settings).unwrap_or_else(|| get_value(&mut defaults).unwrap()) { + ui::ToggleState::Selected + } else { + ui::ToggleState::Unselected + }; + + Switch::new(id, toggle_state) + .on_click({ + move |state, _window, cx| { + write_setting_value(get_value, Some(*state == ui::ToggleState::Selected), cx); + } + }) + .into_any_element() +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 18e2226ece905ceea0b262879dc59225f356155d..c550669479e204e15e6647bb211743e17acfc89d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -19,11 +19,11 @@ name = "zed" path = "src/main.rs" [dependencies] -activity_indicator.workspace = true acp_tools.workspace = true +activity_indicator.workspace = true agent.workspace = true -agent_ui.workspace = true agent_settings.workspace = true +agent_ui.workspace = true anyhow.workspace = true askpass.workspace = true assets.workspace = true @@ -60,13 +60,13 @@ extensions_ui.workspace = true feature_flags.workspace = true feedback.workspace = true file_finder.workspace = true -system_specs.workspace = true fs.workspace = true futures.workspace = true git.workspace = true git_hosting_providers.workspace = true git_ui.workspace = true go_to_line.workspace = true +system_specs.workspace = true gpui = { workspace = true, features = [ "wayland", "x11", @@ -75,12 +75,13 @@ gpui = { workspace = true, features = [ ] } gpui_tokio.workspace = true +edit_prediction_button.workspace = true http_client.workspace = true image_viewer.workspace = true -edit_prediction_button.workspace = true inspector_ui.workspace = true install_cli.workspace = true journal.workspace = true +keymap_editor.workspace = true language.workspace = true language_extension.workspace = true language_model.workspace = true @@ -93,7 +94,6 @@ line_ending_selector.workspace = true log.workspace = true markdown.workspace = true markdown_preview.workspace = true -svg_preview.workspace = true menu.workspace = true migrator.workspace = true mimalloc = { version = "0.1", optional = true } @@ -107,7 +107,6 @@ outline_panel.workspace = true parking_lot.workspace = true paths.workspace = true picker.workspace = true -settings_profile_selector.workspace = true profiling.workspace = true project.workspace = true project_panel.workspace = true @@ -126,12 +125,14 @@ serde.workspace = true serde_json.workspace = true session.workspace = true settings.workspace = true -keymap_editor.workspace = true +settings_profile_selector.workspace = true +settings_ui.workspace = true shellexpand.workspace = true smol.workspace = true snippet_provider.workspace = true snippets_ui.workspace = true supermaven.workspace = true +svg_preview.workspace = true sysinfo.workspace = true tab_switcher.workspace = true task.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 98140cc61497ac5c1cfd3cf9f0ff1b6d19e4888c..43ee2251bbaf130e413a6f534f1cca38e76164eb 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -614,6 +614,7 @@ pub fn main() { markdown_preview::init(cx); svg_preview::init(cx); onboarding::init(cx); + settings_ui::init(cx); keymap_editor::init(cx); extensions_ui::init(cx); zeta::init(cx); From 39370bceb25fae18f6a2cc1ad1590790e7a36ec3 Mon Sep 17 00:00:00 2001 From: Nia Date: Wed, 24 Sep 2025 18:03:08 +0200 Subject: [PATCH 246/721] perf: Bugfixes (#38725) Release Notes: - N/A --- .cargo/config.toml | 2 +- Cargo.lock | 1 + crates/util_macros/src/util_macros.rs | 8 +++- crates/vim/Cargo.toml | 1 + crates/vim/src/test.rs | 40 ++++++++++++++++++- crates/vim/src/test/neovim_connection.rs | 8 +++- tooling/perf/src/main.rs | 49 +++++++++++++++--------- 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index f4e1d6f79c810205d71531fef5e56401f7e0d095..a882cd67bf4040ecaead1f8cce9ffe7a2f96d553 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,7 +4,7 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"] [alias] xtask = "run --package xtask --" -perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--config", "target.'cfg(true)'.runner='cargo run -p perf --release'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"] +perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--all-features", "--config", "target.'cfg(true)'.runner='cargo run -p perf --release'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"] perf-compare = ["run", "--release", "-p", "perf", "--", "compare"] [target.x86_64-unknown-linux-gnu] diff --git a/Cargo.lock b/Cargo.lock index c3c374fb12a7feb61770016d714115759d6c6ef5..6190534f0f3383db2b9aa95544b27956042f87b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17679,6 +17679,7 @@ dependencies = [ "multi_buffer", "nvim-rs", "parking_lot", + "perf", "picker", "project", "project_panel", diff --git a/crates/util_macros/src/util_macros.rs b/crates/util_macros/src/util_macros.rs index 69f6306133f490087b2cefeb71aeafab08b98a9a..bf813c4f7c98e6b17cf513d85258052f15fda682 100644 --- a/crates/util_macros/src/util_macros.rs +++ b/crates/util_macros/src/util_macros.rs @@ -133,7 +133,6 @@ impl PerfArgs { /// Marks a test as perf-sensitive, to be triaged when checking the performance /// of a build. This also automatically applies `#[test]`. /// -/// /// # Usage /// Applying this attribute to a test marks it as average importance by default. /// There are 4 levels of importance (`Critical`, `Important`, `Average`, `Fluff`); @@ -194,7 +193,12 @@ pub fn perf(our_attr: TokenStream, input: TokenStream) -> TokenStream { sig: mut sig_main, block, } = parse_macro_input!(input as ItemFn); - attrs_main.push(parse_quote!(#[test])); + if !attrs_main + .iter() + .any(|a| Some(&parse_quote!(test)) == a.path().segments.last()) + { + attrs_main.push(parse_quote!(#[test])); + } attrs_main.push(parse_quote!(#[allow(non_snake_case)])); let fns = if cfg!(perf_enabled) { diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index a76d1f7ddc7b619ac231cd163a0721439255889a..41780dd3487a3f04229f48d1002c72c1dec0e044 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -66,5 +66,6 @@ parking_lot.workspace = true project_panel.workspace = true release_channel.workspace = true settings.workspace = true +perf.workspace = true util = { workspace = true, features = ["test-support"] } workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 867b0829dde3eef418049f2bb4eec2985fabcfad..bb95b358bc85104fe528fa76c008574183e289af 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -35,6 +35,7 @@ async fn test_initially_disabled(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("hjklˇ"); } +#[perf] #[gpui::test] async fn test_neovim(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -233,6 +234,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { cx.assert_editor_state(" a\nbˇ\nccc\n"); } +#[perf] #[gpui::test] async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -348,6 +350,7 @@ async fn test_kebab_case(cx: &mut gpui::TestAppContext) { ) } +#[perf] #[gpui::test] async fn test_join_lines(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -434,6 +437,7 @@ async fn test_join_lines(cx: &mut gpui::TestAppContext) { } #[cfg(target_os = "macos")] +#[perf] #[gpui::test] async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -585,6 +589,7 @@ async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) { "}); } +#[perf] #[gpui::test] async fn test_folds(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -650,6 +655,7 @@ async fn test_folds(cx: &mut gpui::TestAppContext) { "}); } +#[perf] #[gpui::test] async fn test_folds_panic(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -685,6 +691,7 @@ async fn test_folds_panic(cx: &mut gpui::TestAppContext) { ˇ"}); } +#[perf] #[gpui::test] async fn test_clear_counts(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -702,6 +709,7 @@ async fn test_clear_counts(cx: &mut gpui::TestAppContext) { the lazy dog"}); } +#[perf] #[gpui::test] async fn test_zero(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -725,6 +733,7 @@ async fn test_zero(cx: &mut gpui::TestAppContext) { the lazy dog"}); } +#[perf] #[gpui::test] async fn test_selection_goal(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -741,6 +750,7 @@ async fn test_selection_goal(cx: &mut gpui::TestAppContext) { } #[cfg(target_os = "macos")] +#[perf] #[gpui::test] async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -794,6 +804,7 @@ async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) { }); } +#[perf] #[gpui::test] async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -812,6 +823,7 @@ async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) { }); } +#[perf] #[gpui::test] async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -859,6 +871,7 @@ async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) { ); } +#[perf] #[gpui::test] async fn test_jk(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -921,6 +934,7 @@ async fn test_jk_multi(cx: &mut gpui::TestAppContext) { cx.assert_state("jkˇoone jkˇoone jkˇoone", Mode::Normal); } +#[perf] #[gpui::test] async fn test_jk_delay(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -960,6 +974,7 @@ async fn test_jk_delay(cx: &mut gpui::TestAppContext) { cx.assert_state("jˇkhello", Mode::Normal); } +#[perf] #[gpui::test] async fn test_comma_w(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1106,7 +1121,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal) } -#[perf(iterations = 1)] +#[perf] #[gpui::test] async fn test_remap(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1184,6 +1199,7 @@ async fn test_remap(cx: &mut gpui::TestAppContext) { cx.assert_state("12ˇ 34", Mode::Normal); } +#[perf] #[gpui::test] async fn test_undo(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1246,6 +1262,7 @@ async fn test_mouse_selection(cx: &mut TestAppContext) { cx.assert_state("one «ˇtwo» three", Mode::Visual) } +#[perf] #[gpui::test] async fn test_lowercase_marks(cx: &mut TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1266,6 +1283,7 @@ async fn test_lowercase_marks(cx: &mut TestAppContext) { .assert_eq("line one\nˇtwo\nline three"); } +#[perf] #[gpui::test] async fn test_lt_gt_marks(cx: &mut TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1342,6 +1360,7 @@ async fn test_lt_gt_marks(cx: &mut TestAppContext) { }); } +#[perf] #[gpui::test] async fn test_caret_mark(cx: &mut TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1392,6 +1411,7 @@ async fn test_caret_mark(cx: &mut TestAppContext) { } #[cfg(target_os = "macos")] +#[perf] #[gpui::test] async fn test_dw_eol(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1484,6 +1504,7 @@ async fn test_toggle_comments(cx: &mut gpui::TestAppContext) { ); } +#[perf] #[gpui::test] async fn test_find_multibyte(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1560,6 +1581,7 @@ async fn test_sneak(cx: &mut gpui::TestAppContext) { cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal); } +#[perf] #[gpui::test] async fn test_plus_minus(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1579,6 +1601,7 @@ async fn test_plus_minus(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_matches(); } +#[perf] #[gpui::test] async fn test_command_alias(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; @@ -1595,6 +1618,7 @@ async fn test_command_alias(cx: &mut gpui::TestAppContext) { cx.set_state("ˇHello world", Mode::Normal); } +#[perf] #[gpui::test] async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1628,6 +1652,7 @@ async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("do🐱ˇ"); } +#[perf] #[gpui::test] async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1671,6 +1696,7 @@ async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("🍍ˇ"); } +#[perf] #[gpui::test] async fn test_remap_recursion(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1697,6 +1723,7 @@ async fn test_remap_recursion(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("ˇlo"); } +#[perf] #[gpui::test] async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1705,6 +1732,7 @@ async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("ˇi"); } +#[perf] #[gpui::test] async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1728,6 +1756,7 @@ async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) { cx.assert_state(" ˇhi", Mode::Normal); } +#[perf] #[gpui::test] async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1740,6 +1769,7 @@ async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("ˇhello world"); } +#[perf] #[gpui::test] async fn test_blackhole_register(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1750,6 +1780,7 @@ async fn test_blackhole_register(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("hellˇo"); } +#[perf] #[gpui::test] async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1825,6 +1856,7 @@ async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) { }); } +#[perf] #[gpui::test] async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1840,6 +1872,7 @@ async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) { cx.set_shared_state("helˇlo.\n\n\nworld.").await; } +#[perf] #[gpui::test] async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1851,6 +1884,7 @@ async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq("ˇorld."); } +#[perf] #[gpui::test] async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -1862,6 +1896,7 @@ async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) { cx.shared_state().await.assert_eq(" helloˇworld."); } +#[perf] #[gpui::test] async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -2066,6 +2101,7 @@ async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) { }); } +#[perf] #[gpui::test] async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -2096,6 +2132,7 @@ async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) { cx.shared_clipboard().await.assert_eq("lo world."); } +#[perf] #[gpui::test] async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; @@ -2134,6 +2171,7 @@ async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) { .assert_eq(" oth(wow)\n oth(wow)\n"); } +#[perf] #[gpui::test] async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/test/neovim_connection.rs b/crates/vim/src/test/neovim_connection.rs index 4b0cc5b0c5377854b3c6c9950283f68f0c79b66c..dbc406850757e8987013df2b24f9e4107b890f98 100644 --- a/crates/vim/src/test/neovim_connection.rs +++ b/crates/vim/src/test/neovim_connection.rs @@ -59,7 +59,13 @@ pub struct NeovimConnection { } impl NeovimConnection { - pub async fn new(test_case_id: String) -> Self { + pub async fn new(mut test_case_id: String) -> Self { + // When running under perf, don't create duplicate files. + if cfg!(perf_enabled) { + if test_case_id.ends_with(perf::consts::SUF_NORMAL) { + test_case_id.truncate(test_case_id.len() - perf::consts::SUF_NORMAL.len()); + } + } #[cfg(feature = "neovim")] let handler = NvimHandler {}; #[cfg(feature = "neovim")] diff --git a/tooling/perf/src/main.rs b/tooling/perf/src/main.rs index b960d2dce60023b677c7f6cde12e36a0d66d88ae..cbf14a48d83972c89c09694c58b287c4c7f0f7fa 100644 --- a/tooling/perf/src/main.rs +++ b/tooling/perf/src/main.rs @@ -214,7 +214,6 @@ fn parse_mdata(t_bin: &str, mdata_fn: &str) -> Result { fn compare_profiles(args: &[String]) { let ident_new = args.first().expect("FATAL: missing identifier for new run"); let ident_old = args.get(1).expect("FATAL: missing identifier for old run"); - // TODO: move this to a constant also tbh let wspace_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let runs_dir = PathBuf::from(&wspace_dir).join(consts::RUNS_DIR); @@ -321,6 +320,24 @@ fn get_tests(t_bin: &str) -> impl ExactSizeIterator { out.into_iter() } +/// Runs the specified test `count` times, returning the time taken if the test +/// succeeded. +#[inline] +fn spawn_and_iterate(t_bin: &str, t_name: &str, count: NonZero) -> Option { + let mut cmd = Command::new(t_bin); + cmd.args([t_name, "--exact"]); + cmd.env(consts::ITER_ENV_VAR, format!("{count}")); + // Don't let the child muck up our stdin/out/err. + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::null()); + cmd.stderr(Stdio::null()); + let pre = Instant::now(); + // Discard the output beyond ensuring success. + let out = cmd.spawn().unwrap().wait(); + let post = Instant::now(); + out.iter().find_map(|s| s.success().then_some(post - pre)) +} + /// Triage a test to determine the correct number of iterations that it should run. /// Specifically, repeatedly runs the given test until its execution time exceeds /// `thresh`, calling `step(iterations)` after every failed run to determine the new @@ -328,7 +345,8 @@ fn get_tests(t_bin: &str) -> impl ExactSizeIterator { /// else `Some(iterations)`. /// /// # Panics -/// This will panic if `step(usize)` is not monotonically increasing. +/// This will panic if `step(usize)` is not monotonically increasing, or if the test +/// binary is invalid. fn triage_test( t_bin: &str, t_name: &str, @@ -336,22 +354,12 @@ fn triage_test( mut step: impl FnMut(NonZero) -> Option>, ) -> Option> { let mut iter_count = DEFAULT_ITER_COUNT; + // It's possible that the first loop of a test might be an outlier (e.g. it's + // doing some caching), in which case we want to skip it. + let duration_once = spawn_and_iterate(t_bin, t_name, NonZero::new(1).unwrap())?; loop { - let mut cmd = Command::new(t_bin); - cmd.args([t_name, "--exact"]); - cmd.env(consts::ITER_ENV_VAR, format!("{iter_count}")); - // Don't let the child muck up our stdin/out/err. - cmd.stdin(Stdio::null()); - cmd.stdout(Stdio::null()); - cmd.stderr(Stdio::null()); - let pre = Instant::now(); - // Discard the output beyond ensuring success. - let out = cmd.spawn().unwrap().wait(); - let post = Instant::now(); - if !out.unwrap().success() { - break None; - } - if post - pre > thresh { + let duration = spawn_and_iterate(t_bin, t_name, iter_count)?; + if duration.saturating_sub(duration_once) > thresh { break Some(iter_count); } let new = step(iter_count)?; @@ -375,7 +383,10 @@ fn hyp_profile(t_bin: &str, t_name: &str, iterations: NonZero) -> Option< "1", "--export-markdown", "-", - &format!("{t_bin} {t_name}"), + // Parse json instead... + "--time-unit", + "millisecond", + &format!("{t_bin} --exact {t_name}"), ]); perf_cmd.env(consts::ITER_ENV_VAR, format!("{iterations}")); let p_out = perf_cmd.output().unwrap(); @@ -390,7 +401,7 @@ fn hyp_profile(t_bin: &str, t_name: &str, iterations: NonZero) -> Option< // TODO: Parse json instead. let mut res_iter = results_line.split_whitespace(); // Durations are given in milliseconds, so account for that. - let mean = Duration::from_secs_f64(res_iter.nth(4).unwrap().parse::().unwrap() / 1000.); + let mean = Duration::from_secs_f64(res_iter.nth(5).unwrap().parse::().unwrap() / 1000.); let stddev = Duration::from_secs_f64(res_iter.nth(1).unwrap().parse::().unwrap() / 1000.); Some(Timings { mean, stddev }) From a13e3a8af32af2b44b799de53df4a96e236c830d Mon Sep 17 00:00:00 2001 From: morgankrey Date: Wed, 24 Sep 2025 11:10:58 -0500 Subject: [PATCH 247/721] Docs updates September (#38796) Closes #ISSUE Release Notes: - N/A --------- Co-authored-by: Katie Geer Co-authored-by: Marshall Bowers Co-authored-by: David Kleingeld --- docs/src/SUMMARY.md | 2 +- docs/src/ai/agent-panel.md | 47 ++++++----------- docs/src/ai/agent-settings.md | 11 ++-- docs/src/ai/ai-improvement.md | 22 ++++++-- docs/src/ai/billing.md | 35 +++++++++---- docs/src/ai/configuration.md | 7 ++- docs/src/ai/external-agents.md | 2 + docs/src/ai/inline-assistant.md | 2 - docs/src/ai/llm-providers.md | 4 +- docs/src/ai/models.md | 81 +++++++++++++++-------------- docs/src/ai/overview.md | 2 +- docs/src/ai/plans-and-usage.md | 28 +++------- docs/src/ai/privacy-and-security.md | 2 +- docs/src/ai/subscription.md | 8 +-- docs/src/git.md | 2 +- 15 files changed, 130 insertions(+), 125 deletions(-) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index abf6cc127254e5f3b7fdc91219b638a8bf50eec6..eb542497238780d93974cbc2627ce3466e23049b 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -58,9 +58,9 @@ - [LLM Providers](./ai/llm-providers.md) - [Agent Settings](./ai/agent-settings.md) - [Subscription](./ai/subscription.md) + - [Models](./ai/models.md) - [Plans and Usage](./ai/plans-and-usage.md) - [Billing](./ai/billing.md) - - [Models](./ai/models.md) - [Privacy and Security](./ai/privacy-and-security.md) - [AI Improvement](./ai/ai-improvement.md) diff --git a/docs/src/ai/agent-panel.md b/docs/src/ai/agent-panel.md index b6b748e2f58493cd62abbd3c6e7dc443182e992f..445b85337046d75b78ec4101051fc55c7dcf3752 100644 --- a/docs/src/ai/agent-panel.md +++ b/docs/src/ai/agent-panel.md @@ -10,7 +10,7 @@ If you're using the Agent Panel for the first time, you need to have at least on You can do that by: 1. [subscribing to our Pro plan](https://zed.dev/pricing), so you have access to our hosted models -2. [bringing your own API keys](./llm-providers.md#use-your-own-keys) for your desired provider +2. [using your own API keys](./llm-providers.md#use-your-own-keys), either from model providers like Anthropic or model gateways like OpenRouter. 3. using an external agent like [Gemini CLI](./external-agents.md#gemini-cli) or [Claude Code](./external-agents.md#claude-code) ## Overview {#overview} @@ -51,20 +51,18 @@ To view all historical conversations, reach for the `View All` option from withi ### Following the Agent {#following-the-agent} -Zed is built with collaboration natively integrated. -This approach extends to collaboration with AI as well. -To follow the agent reading through your codebase and performing edits, click on the "crosshair" icon button at the bottom left of the panel. +Zed is built with collaboration natively integrated, and this design pattern extends to collaboration with AI. To follow the agent as it reads and edits in your codebase, click on the "crosshair" icon button at the bottom left of the panel. You can also do that with the keyboard by pressing the `cmd`/`ctrl` modifier with `enter` when submitting a message. ### Get Notified {#get-notified} -If you send a prompt to the Agent and then move elsewhere, thus putting Zed in the background, you can be notified of whether its response is finished either via: +If you send a prompt to the Agent and then move elsewhere, putting Zed in the background, you can be notified when its response is finished via: - a visual notification that appears in the top right of your screen -- or a sound notification +- a sound notification -Both notification methods can be used together or individually according to your preference. +These notifications can be used together or individually, according to your preference. You can customize their behavior, including turning them off entirely, by using the `agent.notify_when_agent_waiting` and `agent.play_sound_when_agent_done` settings keys. @@ -76,8 +74,7 @@ To see which files specifically have been edited, expand the accordion bar that You're able to reject or accept each individual change hunk, or the whole set of changes made by the agent. -Edit diffs also appear in individual buffers. -So, if your active tab had edits made by the AI, you'll see diffs with the same accept/reject controls as in the multi-buffer. +Edit diffs also appear in individual buffers. If your active tab had edits made by the AI, you'll see diffs with the same accept/reject controls as in the multi-buffer. ## Adding Context {#adding-context} @@ -89,15 +86,16 @@ Pasting images as context is also supported by the Agent Panel. ### Token Usage {#token-usage} -Zed surfaces how many tokens you are consuming for your currently active thread nearby the profile selector in the panel's message editor. -Depending on how many pieces of context you add, your token consumption can grow rapidly. +Zed surfaces how many tokens you are consuming for your currently active thread nearby the profile selector in the panel's message editor. Depending on how many pieces of context you add, your token consumption can grow rapidly. -With that in mind, once you get close to the model's context window, a banner appears below the message editor suggesting to start a new thread with the current one summarized and added as context. +Once you approach the model's context window, a banner appears below the message editor suggesting to start a new thread with the current one summarized and added as context. You can also do this at any time with an ongoing thread via the "Agent Options" menu on the top right. ## Changing Models {#changing-models} -After you've configured your LLM providers—either via [a custom API key](./llm-providers.md#use-your-own-keys) or through [Zed's hosted models](./models.md)—you can switch between them by clicking on the model selector on the message editor or by using the {#kb agent::ToggleModelSelector} keybinding. +After you've configured your LLM providers—either via [a custom API key](./llm-providers.md) or through [Zed's hosted models](./models.md)—you can switch between them by clicking on the model selector on the message editor or by using the {#kb agent::ToggleModelSelector} keybinding. + +> The same model can be offered via multiple providers - for example, Claude Sonnet 4 is available via Zed Pro, OpenRouter, Anthropic directly, and more. Make sure you've selected the correct model **_provider_** for the model you'd like to use, delineated by the logo to the left of the model in the model selector. ## Using Tools {#using-tools} @@ -140,27 +138,19 @@ You can change that by setting this key to `true` in either your `settings.json` ### Model Support {#model-support} Tool calling needs to be individually supported by each model and model provider. -Therefore, despite the presence of tools, some models may not have the ability to pick them up yet in Zed. -You should see a "No tools" label if you select a model that falls into this case. - -We want to support all of them, though! -We may prioritize which ones to focus on based on popularity and user feedback, so feel free to help and contribute to fast-track those that don't fit this bill. +Therefore, despite the presence of tools, some models may not have the ability to pick them up yet in Zed. You should see a "No tools" label if you select a model that falls into this case. All [Zed's hosted models](./models.md) support tool calling out-of-the-box. ### MCP Servers {#mcp-servers} -Similarly to the built-in tools, some models may not support all tools included in a given MCP Server. -Zed's UI will inform about this via a warning icon that appears close to the model selector. +Similarly to the built-in tools, some models may not support all tools included in a given MCP Server. Zed's UI will inform about this via a warning icon that appears close to the model selector. ## Text Threads {#text-threads} -["Text Threads"](./text-threads.md) present your conversation with the LLM in a different format—as raw text. -With text threads, you have full control over the conversation data. -You can remove and edit responses from the LLM, swap roles, and include more context earlier in the conversation. +["Text Threads"](./text-threads.md) present your conversation with the LLM in a different format—as raw text. With text threads, you have full control over the conversation data. You can remove and edit responses from the LLM, swap roles, and include more context earlier in the conversation. -For users who have been with us for some time, you'll notice that text threads are our original assistant panel—users love it for the control it offers. -We do not plan to deprecate text threads, but it should be noted that if you want the AI to write to your code base autonomously, that's only available in the newer, and now default, "Threads". +For users who have been with us for some time, you'll notice that text threads are our original assistant panel—users love it for the control it offers. We do not plan to deprecate text threads, but it should be noted that if you want the AI to write to your code base autonomously, that's only available in the newer, and now default, "Threads". ## Errors and Debugging {#errors-and-debugging} @@ -170,15 +160,12 @@ You can also open threads as Markdown by clicking on the file icon button, to th ## Feedback {#feedback} -Every change we make to Zed's system prompt and tool set, needs to be backed by a thorough eval with good scores. - -Every time the LLM performs a weird change or investigates a certain topic in your code base incorrectly, it's an indication that there's an improvement opportunity. +Zed supports rating responses from the agent for feedback and improvement. > Note that rating responses will send your data related to that response to Zed's servers. > See [AI Improvement](./ai-improvement.md) and [Privacy and Security](./privacy-and-security.md) for more information about Zed's approach to AI improvement, privacy, and security. > **_If you don't want data persisted on Zed's servers, don't rate_**. We will not collect data for improving our Agentic offering without you explicitly rating responses. -The best way you can help influence the next change to Zed's system prompt and tools is by rating the LLM's response via the thumbs up/down buttons at the end of every response. -In case of a thumbs down, a new text area will show up where you can add more specifics about what happened. +The best way you can help influence the next change to Zed's system prompt and tools is by rating the LLM's response via the thumbs up/down buttons at the end of every response. In case of a thumbs down, a new text area will show up where you can add more specifics about what happened. You can provide feedback on the thread at any point after the agent responds, and multiple times within the same thread. diff --git a/docs/src/ai/agent-settings.md b/docs/src/ai/agent-settings.md index 4d12a87f95f6fd34a6dcfdd822b6e1f71dfeefa2..15f50c03c2fa972e55e00217e87f628093a9eb20 100644 --- a/docs/src/ai/agent-settings.md +++ b/docs/src/ai/agent-settings.md @@ -6,14 +6,13 @@ Learn about all the settings you can customize in Zed's Agent Panel. ### Default Model {#default-model} -If you're using [Zed's hosted LLM service](./plans-and-usage.md), it sets `claude-sonnet-4` as the default model. -But if you're not subscribed to it or simply just want to change it, you can do it so either via the model dropdown in the Agent Panel's bottom-right corner or by manually editing the `default_model` object in your settings: +If you're using [Zed's hosted LLM service](./subscription.md), it sets `claude-sonnet-4` as the default model for agentic work (agent panel, inline assistant) and `gpt-5-nano` as the default "fast" model (thread summarization, git commit messages). If you're not subscribed or want to change these defaults, you can manually edit the `default_model` object in your settings: ```json { "agent": { "default_model": { - "provider": "zed.dev", + "provider": "openai", "model": "gpt-4o" } } @@ -22,7 +21,7 @@ But if you're not subscribed to it or simply just want to change it, you can do ### Feature-specific Models {#feature-specific-models} -Assign distinct and specific models for the following AI-powered features in Zed: +You can assign distinct and specific models for the following AI-powered features: - Thread summary model: Used for generating thread summaries - Inline assistant model: Used for the inline assistant feature @@ -63,7 +62,7 @@ When configured, the inline assist UI will surface controls to cycle between the The models you specify here are always used in _addition_ to your [default model](#default-model). For example, the following configuration will generate two outputs for every assist. -One with Claude Sonnet 4 (the default model), and one with GPT-4o. +One with Claude Sonnet 4 (the default model), and one with GPT-5-mini. ```json { @@ -75,7 +74,7 @@ One with Claude Sonnet 4 (the default model), and one with GPT-4o. "inline_alternatives": [ { "provider": "zed.dev", - "model": "gpt-4o" + "model": "gpt-4-mini" } ] } diff --git a/docs/src/ai/ai-improvement.md b/docs/src/ai/ai-improvement.md index 5296b2a179ebe8ba8b7a966a54138cbde4650b4f..972b5908c08c6a7549553b0ae237714283c4b937 100644 --- a/docs/src/ai/ai-improvement.md +++ b/docs/src/ai/ai-improvement.md @@ -4,18 +4,30 @@ ### Opt-In -When using the Agent Panel, whether through Zed's hosted AI service or via connecting a non-Zed AI service via API key, Zed does not persistently store user content or use user content to evaluate and/or improve our AI features, unless it is explicitly shared with Zed. Each share is opt-in, and sharing once will not cause future content or data to be shared again. +When you use the Agent Panel through any of these means: + +- [Zed's hosted models](./subscription.md) +- [connecting a non-Zed AI service via API key](./llm-providers.md) +- using an [external agent](./external-agents.md) + +Zed does not persistently store user content or use user content to evaluate and/or improve our AI features, unless it is explicitly shared with Zed. Each share is opt-in, and sharing once will not cause future content or data to be shared again. > Note that rating responses will send your data related to that response to Zed's servers. > **_If you don't want data persisted on Zed's servers, don't rate_**. We will not collect data for improving our Agentic offering without you explicitly rating responses. -When using upstream services through Zed AI, we require assurances from our service providers that your user content won't be used for training models. For example, usage of Anthropic Claude 3.5 via Zed AI in the Assistant is governed by the [Anthropic Commercial Terms](https://www.anthropic.com/legal/commercial-terms) which includes the following: +When using upstream services through Zed's hosted models, we require assurances from our service providers that your user content won't be used for training models. + +| Provider | No Training Guarantee | Zero-Data Retention (ZDR) | +| --------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| Anthropic | [Yes](https://www.anthropic.com/legal/commercial-terms) | [Yes](https://privacy.anthropic.com/en/articles/8956058-i-have-a-zero-data-retention-agreement-with-anthropic-what-products-does-it-apply-to) | +| Google | [Yes](https://cloud.google.com/terms/service-terms) | **No**, in flight | +| OpenAI | [Yes](https://openai.com/enterprise-privacy/) | [Yes](https://platform.openai.com/docs/guides/your-data) | -> "Anthropic may not train models on Customer Content from paid Services." +> Zed's use of Gemini models is currently supported via [Google AI Studio](https://ai.google.dev/aistudio), which **_does not_** support ZDR. We're migrating to [Vertex AI](https://cloud.google.com/vertex-ai?hl=en), which **_does_**, and upon completion of that migration will offer ZDR to all users of Zed's hosted Google/Gemini models. -We also have a [zero-data retention agreement](https://privacy.anthropic.com/en/articles/8956058-i-have-a-zero-data-retention-agreement-with-anthropic-what-products-does-it-apply-to) with Anthropic. +> If ZDR from upstream model providers is important to you, _please do not use Gemini models at this time_. Your data will never be used for training purposes by any model providers hosted by Zed, however. -When you directly connect Zed with a non Zed AI service (e.g., via API key) Zed does not have control over how your data is used by that service provider. +When you use your own API keys or external agents, **Zed does not have control over how your data is used by that service provider.** You should reference your agreement with each service provider to understand what terms and conditions apply. ### Data we collect diff --git a/docs/src/ai/billing.md b/docs/src/ai/billing.md index d519b136aeea8c505979cde224d406ac995b65f0..64ff871ce1b629fad72d4ddd6f9c8f42f2bf92da 100644 --- a/docs/src/ai/billing.md +++ b/docs/src/ai/billing.md @@ -1,29 +1,42 @@ # Billing -We use Stripe as our billing and payments provider. All Pro plans require payment via credit card. +We use Stripe as our payments provider, and Orb for invoicing and metering. All Pro plans require payment via credit card or other supported payment method. For invoice-based billing, a Business plan is required. Contact [sales@zed.dev](mailto:sales@zed.dev) for more information. -## Settings {#settings} +## Billing Information {#settings} -You can access billing settings at [zed.dev/account](https://zed.dev/account). -Clicking the button under Account Settings will navigate you to Stripe’s secure portal, where you can update all billing-related settings and configuration. +You can access billing information and settings at [zed.dev/account](https://zed.dev/account). +Most of the page embeds information from our invoicing/metering partner, Orb (we're planning on a more native experience soon!). ## Billing Cycles {#billing-cycles} -Zed is billed on a monthly basis based on the date you initially subscribe. +Zed is billed on a monthly basis based on the date you initially subscribe. You'll receive _at least_ one invoice from Zed each month you're subscribed to Zed Pro, and more than one if you use more than $10 in incremental token spend within the month. -We’ll also bill in-month for additional prompts used beyond your plan’s prompt limit, if usage exceeds $20 before month end. -See [usage-based pricing](./plans-and-usage.md#ubp) for more. +## Threshold Billing {#threshold-billing} + +Zed utilizes threshold billing to ensure timely collection of owed monies and prevent abuse. Every time your usage of Zed's hosted models crosses a $10 spend threshold, a new invoice is generated, and the threshold resets to $0. + +For example, + +- You subscribe on February 1. Your first invoice is $10. +- You use $12 of incremental tokens in the month of February, with the first $10 spent on February 15. You'll receive an invoice for $10 on February 15 +- On March 1, you receive an invoice for $12: $10 (March Pro subscription) and $2 in leftover token spend, since your usage didn't cross the $10 threshold. + +## Payment Failures {#payment-failures} + +If payment of an invoice fails, Zed will block usage of our hosted models until the payment is complete. Email [billing-support@zed.dev](mailto:billing-support@zed.dev) for assistance. ## Invoice History {#invoice-history} -You can access your invoice history by navigating to [zed.dev/account](https://zed.dev/account) and clicking "Manage" on your subscription. +You can access your invoice history by navigating to [zed.dev/account](https://zed.dev/account) and clicking `Invoice history` within the embedded Orb portal. -From Stripe’s secure portal, you can download all current and historical invoices. +If you require historical Stripe invoices, email [billing-support@zed.dev](mailto:billing-support@zed.dev) ## Updating Billing Information {#updating-billing-info} -You can update your payment method, company name, address, and tax information through the billing portal. +Email [billing-support@zed.dev](mailto:billing-support@zed.dev) for help updating payment methods, names, addresses, and tax information. + +> We'll be updating our account page shortly to allow for self-service updates. Stay tuned! Please note that changes to billing information will **only** affect future invoices — **we cannot modify historical invoices**. @@ -31,7 +44,7 @@ Please note that changes to billing information will **only** affect future invo Zed partners with [Sphere](https://www.getsphere.com/) to calculate indirect tax rate for invoices, based on customer location and the product being sold. Tax is listed as a separate line item on invoices, based preferentially on your billing address, followed by the card issue country known to Stripe. -If you have a VAT/GST ID, you can add it at [zed.dev/account](https://zed.dev/account) by clicking "Manage" on your subscription. Check the box that denotes you as a business. +If you have a VAT/GST ID, you can add it at during checkout. Check the box that denotes you as a business. Please note that changes to VAT/GST IDs and address will **only** affect future invoices — **we cannot modify historical invoices**. Questions or issues can be directed to [billing-support@zed.dev](mailto:billing-support@zed.dev). diff --git a/docs/src/ai/configuration.md b/docs/src/ai/configuration.md index d28a7e8ed006b1c788cc0f649362bae41879a99b..c11a0fd65c45ce46598596182fbf8fb0c147380a 100644 --- a/docs/src/ai/configuration.md +++ b/docs/src/ai/configuration.md @@ -1,8 +1,11 @@ # Configuration -When using AI in Zed, you can customize several aspects: +When using AI in Zed, you can configure multiple dimensions: -1. Which [LLM providers](./llm-providers.md) you can use +1. Which LLM providers you can use + - Zed's hosted models, which require [authentication](../accounts.md) and [subscription](./subscription.md) + - [Using your own API keys](./llm-providers.md), which do not + - Using [external agents like Claude Code](./external-agents.md), which do not 2. [Model parameters and usage](./agent-settings.md#model-settings) 3. [Interactions with the Agent Panel](./agent-settings.md#agent-panel-settings) diff --git a/docs/src/ai/external-agents.md b/docs/src/ai/external-agents.md index bc7768c6081ad7a32eb1fd780750a48c4b9200f0..abe14865902ee261b157cf653b0d556cf83d7c71 100644 --- a/docs/src/ai/external-agents.md +++ b/docs/src/ai/external-agents.md @@ -5,6 +5,8 @@ Zed supports terminal-based agents through the [Agent Client Protocol (ACP)](htt Currently, [Gemini CLI](https://github.com/google-gemini/gemini-cli) serves as the reference implementation. [Claude Code](https://www.anthropic.com/claude-code) is also included by default, and you can [add custom ACP-compatible agents](#add-custom-agents) as well. +Zed's affordance for external agents is strictly UI-based; the billing and legal/terms arrangement is directly between you and the agent provider. Zed does not charge for use of external agents, and our [zero-data retention agreements/privacy guarantees](./ai-improvement.md) are **_only_** applicable for Zed's hosted models. + ## Gemini CLI {#gemini-cli} Zed provides the ability to run [Gemini CLI](https://github.com/google-gemini/gemini-cli) directly in the [agent panel](./agent-panel.md). diff --git a/docs/src/ai/inline-assistant.md b/docs/src/ai/inline-assistant.md index da894e2cd87faf6ce8afa9c54a5f2d55bcd07827..41923e85da09c2eed067d40518c89088d653b7b7 100644 --- a/docs/src/ai/inline-assistant.md +++ b/docs/src/ai/inline-assistant.md @@ -14,8 +14,6 @@ Give the Inline Assistant context the same way you can in [the Agent Panel](./ag A useful pattern here is to create a thread in the Agent Panel, and then mention that thread with `@thread` in the Inline Assistant to include it as context. -> The Inline Assistant is limited to normal mode context windows ([see Models](./models.md) for more). - ## Prefilling Prompts To create a custom keybinding that prefills a prompt, you can add the following format in your keymap: diff --git a/docs/src/ai/llm-providers.md b/docs/src/ai/llm-providers.md index 09f67cc9c123a968705a834f9d1c5a2e855a782f..aeed1be17370c28ad67d8ffb7d49fadc5a77cdce 100644 --- a/docs/src/ai/llm-providers.md +++ b/docs/src/ai/llm-providers.md @@ -6,7 +6,7 @@ You can do that by either subscribing to [one of Zed's plans](./plans-and-usage. ## Use Your Own Keys {#use-your-own-keys} -If you already have an API key for an existing LLM provider—say Anthropic or OpenAI, for example—you can insert them into Zed and use the full power of the Agent Panel **_for free_**. +If you already have an API key for an existing LLM provider, like Anthropic or OpenAI, you can add them to Zed and use the full power of the Agent Panel **_for free_**. To add an existing API key to a given provider, go to the Agent Panel settings (`agent: open settings`), look for the desired provider, paste the key into the input, and hit enter. @@ -14,7 +14,7 @@ To add an existing API key to a given provider, go to the Agent Panel settings ( ## Supported Providers -Here's all the supported LLM providers for which you can use your own API keys: +Zed offers an extensive list of "use your own key" LLM providers - [Amazon Bedrock](#amazon-bedrock) - [Anthropic](#anthropic) diff --git a/docs/src/ai/models.md b/docs/src/ai/models.md index 8d46d0b8d161ae23c77322d13244660142eab56a..8bebf15cb107298c00aa9b6fe405500545994be3 100644 --- a/docs/src/ai/models.md +++ b/docs/src/ai/models.md @@ -1,51 +1,58 @@ # Models -Zed’s plans offer hosted versions of major LLM’s, generally with higher rate limits than individual API keys. +Zed’s plans offer hosted versions of major LLMs, generally with higher rate limits than using your API keys. We’re working hard to expand the models supported by Zed’s subscription offerings, so please check back often. -| Model | Provider | Burn Mode | Context Window | Price per Prompt | Price per Request | -| ----------------- | --------- | --------- | -------------- | ---------------- | ----------------- | -| Claude 3.5 Sonnet | Anthropic | ❌ | 60k | $0.04 | N/A | -| Claude 3.7 Sonnet | Anthropic | ❌ | 120k | $0.04 | N/A | -| Claude 3.7 Sonnet | Anthropic | ✅ | 200k | N/A | $0.05 | -| Claude Sonnet 4 | Anthropic | ❌ | 120k | $0.04 | N/A | -| Claude Sonnet 4 | Anthropic | ✅ | 200k | N/A | $0.05 | -| Claude Opus 4 | Anthropic | ❌ | 120k | $0.20 | N/A | -| Claude Opus 4 | Anthropic | ✅ | 200k | N/A | $0.25 | -| Claude Opus 4.1 | Anthropic | ❌ | 120k | $0.20 | N/A | -| Claude Opus 4.1 | Anthropic | ✅ | 200k | N/A | $0.25 | - -> Note: Because of the 5x token cost for [Opus relative to Sonnet](https://www.anthropic.com/pricing#api), each Opus 4 and 4.1 prompt consumes 5 prompts against your billing meter +| Model | Provider | Token Type | Provider Price per 1M tokens | Zed Price per 1M tokens | +| ----------------- | --------- | ------------------- | ---------------------------- | ----------------------- | +| Claude Opus 4.1 | Anthropic | Input | $15.00 | $16.50 | +| | Anthropic | Output | $75.00 | $82.50 | +| | Anthropic | Input - Cache Write | $18.75 | $20.625 | +| | Anthropic | Input - Cache Read | $1.50 | $1.65 | +| Claude Sonnet 4 | Anthropic | Input | $3.00 | $3.30 | +| | Anthropic | Output | $15.00 | $16.50 | +| | Anthropic | Input - Cache Write | $3.75 | $4.125 | +| | Anthropic | Input - Cache Read | $0.30 | $0.33 | +| Claude Sonnet 3.7 | Anthropic | Input | $3.00 | $3.30 | +| | Anthropic | Output | $15.00 | $16.50 | +| | Anthropic | Input - Cache Write | $3.75 | $4.125 | +| | Anthropic | Input - Cache Read | $0.30 | $0.33 | +| GPT-5 | OpenAI | Input | $1.25 | $1.375 | +| | OpenAI | Output | $10.00 | $11.00 | +| | OpenAI | Cached Input | $0.125 | $0.1375 | +| GPT-5 mini | OpenAI | Input | $0.25 | $0.275 | +| | OpenAI | Output | $2.00 | $2.20 | +| | OpenAI | Cached Input | $0.025 | $0.0275 | +| GPT-5 nano | OpenAI | Input | $0.05 | $0.055 | +| | OpenAI | Output | $0.40 | $0.44 | +| | OpenAI | Cached Input | $0.005 | $0.0055 | +| Gemini 2.5 Pro | Google | Input | $1.25 | $1.375 | +| | Google | Output | $10.00 | $11.00 | +| Gemini 2.5 Flash | Google | Input | $0.30 | $0.33 | +| | Google | Output | $2.50 | $2.75 | ## Usage {#usage} -The models above can be used with the prompts included in your plan. For models not marked with [“Burn Mode”](#burn-mode), each prompt is counted against the monthly limit of your plan. - -If you’ve exceeded your limit for the month, and are on a paid plan, you can enable usage-based pricing to continue using models for the rest of the month. See [Plans and Usage](./plans-and-usage.md) for more information. - -Non-Burn Mode usage will use up to 25 tool calls per one prompt. If your prompt extends beyond 25 tool calls, Zed will ask if you’d like to continue, which will consume a second prompt. - -## Burn Mode {#burn-mode} - -> Note: "Burn Mode" is the new name for what was previously called "Max Mode". - -In Burn Mode, we enable models to use [large context windows](#context-windows), unlimited tool calls, and other capabilities for expanded reasoning, to allow an unfettered agentic experience. +Any usage of a Zed-hosted model will be billed at the Zed Price (rightmost column above). See [Plans and Usage](./plans-and-usage.md) for details on Zed's plans and limits for use of hosted models. -Because of the increased cost to Zed, each subsequent request beyond the initial user prompt in Burn Mode models is counted as a prompt for metering. - -In addition, usage-based pricing per request is slightly more expensive for Burn Mode models than usage-based pricing per prompt for regular models. - -> Note that the Agent Panel using a Burn Mode model may consume a good bit of your monthly prompt capacity, if many tool calls are used. -> We encourage you to think through what model is best for your needs before leaving the Agent Panel to work. - -By default, all threads and [text threads](./text-threads.md) start in normal mode. -However, you can use the `agent.preferred_completion_mode` setting to have Burn Mode activated by default. +> We encourage you to think through what model is best for your needs before leaving the Agent Panel to work. All LLMs can "spiral" and occasionally enter unending loops that require user intervention. ## Context Windows {#context-windows} A context window is the maximum span of text and code an LLM can consider at once, including both the input prompt and output generated by the model. -In [Burn Mode](#burn-mode), we increase context window size to allow models to have enhanced reasoning capabilities. +| Model | Provider | Zed-Hosted Context Window | +| ----------------- | --------- | ------------------------- | +| Claude Opus 4.1 | Anthropic | 200k | +| Claude Sonnet 4 | Anthropic | 200k | +| Claude Sonnet 3.7 | Anthropic | 200k | +| GPT-5 | OpenAI | 400k | +| GPT-5 mini | OpenAI | 400k | +| GPT-5 nano | OpenAI | 400k | +| Gemini 2.5 Pro | Google | 200k | +| Gemini 2.5 Flash | Google | 200k | + +> We're planning on expanding supported context windows for hosted Sonnet 4 and Gemini 2.5 Pro/Flash in the near future. Stay tuned! Each Agent thread and text thread in Zed maintains its own context window. The more prompts, attached files, and responses included in a session, the larger the context window grows. @@ -55,7 +62,3 @@ For best results, it’s recommended you take a purpose-based approach to Agent ## Tool Calls {#tool-calls} Models can use [tools](./tools.md) to interface with your code, search the web, and perform other useful functions. - -In [Burn Mode](#burn-mode), models can use an unlimited number of tools per prompt, with each tool call counting as a prompt for metering purposes. - -For non-Burn Mode models, you'll need to interact with the model every 25 tool calls to continue, at which point a new prompt will be counted against your plan limit. diff --git a/docs/src/ai/overview.md b/docs/src/ai/overview.md index 55d37ea3526173b6bf88adc0f15754be51bf6866..ca06a4b1ed53d1fc87136a1d5e82da35552082aa 100644 --- a/docs/src/ai/overview.md +++ b/docs/src/ai/overview.md @@ -8,7 +8,7 @@ Learn how to get started using AI with Zed and all its capabilities. - [External Agents](./external-agents.md): Learn how to plug in your favorite agent into Zed. -- [Subscription](./subscription.md): Learn about Zed's hosted model service and other billing-related information. +- [Subscription](./subscription.md): Learn about Zed's hosted models and other billing-related information. - [Privacy and Security](./privacy-and-security.md): Understand how Zed handles privacy and security with AI features. diff --git a/docs/src/ai/plans-and-usage.md b/docs/src/ai/plans-and-usage.md index 1e6616c79b80489b91e4f92c13b9c5fe39ff1af5..450ece0ca78855b4be4641ceb0b29322d98880c6 100644 --- a/docs/src/ai/plans-and-usage.md +++ b/docs/src/ai/plans-and-usage.md @@ -1,8 +1,5 @@ # Plans and Usage -To view your current usage, you can visit your account at [zed.dev/account](https://zed.dev/account). -You’ll also find usage meters in-product when you’re nearing the limit for your plan or trial. - ## Available Plans {#plans} For costs and more information on pricing, visit [Zed’s pricing page](https://zed.dev/pricing). @@ -11,29 +8,20 @@ Please note that if you’re interested in just using Zed as the world’s faste ## Usage {#usage} -- A `prompt` in Zed is an input from the user, initiated by pressing enter, composed of one or many `requests`. A `prompt` can be initiated from the Agent Panel, or via Inline Assist. -- A `request` in Zed is a response to a `prompt`, plus any tool calls that are initiated as part of that response. There may be one `request` per `prompt`, or many. - -Most models offered by Zed are metered per-prompt. -Some models that use large context windows and unlimited tool calls ([“Burn Mode”](./models.md#burn-mode)) count each individual request within a prompt against your prompt limit, since the agentic work spawned by the prompt is expensive to support. - -See [the Models page](./models.md) for a list of which subset of models are metered by request. +Usage of Zed's hosted models is measured on a token basis, converted to dollars at the rates lists on [the Models page](./models.md) (list price from the provider, +10%). -Plans come with a set amount of prompts included, with the number varying depending on the plan you’ve selected. +Zed Pro comes with $5 of monthly dollar credit. A trial of Zed Pro includes $20 of credit, usable for 14 days. Monthly included credit resets on your monthly billing date. -## Usage-Based Pricing {#ubp} +To view your current usage, you can visit your account at [zed.dev/account](https://zed.dev/account). Information from our metering and billing provider, Orb, is embedded on that page. -You may opt in to usage-based pricing for prompts that exceed what is included in your paid plan from [your account page](https://zed.dev/account). +## Spend Limits {#usage-spend-limits} -Usage-based pricing is only available with a paid plan, and is exclusively opt-in. -From the dashboard, you can toggle usage-based pricing for usage exceeding your paid plan. -You can also configure a spend limit in USD. -Once the spend limit is hit, we’ll stop any further usage until your prompt limit resets. +At the top of [the Account page](https://zed.dev/account), you'll find an input for `Maximum Token Spend`. The dollar amount here specifies your _monthly_ limit for spend on tokens, _not counting_ the $5/month included with your Pro subscription. -We will bill for additional prompts when you’ve made prompts totaling $20, or when your billing date occurs, whichever comes first. +The default value for all Pro users is $10, for a total monthly spend with Zed of $20 ($10 for your Pro subscription, $10 in incremental token spend). This can be set to $0 to limit your spend with Zed to exactly $10/month. If you adjust this limit _higher_ than $10 and consume more than $10 of incremental token spend, you'll be billed via [threshold billing](./billing.md#threshold-billing). -Cost per request for each model can be found on [the models page](./models.md). +Once the spend limit is hit, we’ll stop any further usage until your token spend limit resets. ## Business Usage {#business-usage} -Email [sales@zed.dev](mailto:sales@zed.dev) with any questions on business plans, metering, and usage-based pricing. +Email [sales@zed.dev](mailto:sales@zed.dev) with any questions on business plans. diff --git a/docs/src/ai/privacy-and-security.md b/docs/src/ai/privacy-and-security.md index 5e105a1e5618acc87a0be04290fb7972b6bd017e..23166df1d7e2e188e68d7ecafa98ac92f8e704a4 100644 --- a/docs/src/ai/privacy-and-security.md +++ b/docs/src/ai/privacy-and-security.md @@ -8,7 +8,7 @@ We believe in opt-in data sharing as the default in building AI products, rather As an open-source product, we believe in maximal transparency, and invite you to examine our codebase. If you find issues, we encourage you to share them with us. -It is entirely possible to use Zed without sharing any data with us and without authenticating into the product. We're happy to always support this desired use pattern. +It is entirely possible to use Zed, including Zed's AI capabilities, without sharing any data with us and without authenticating into the product. We're happy to always support this desired use pattern. ## Documentation diff --git a/docs/src/ai/subscription.md b/docs/src/ai/subscription.md index 078fe43384a4fc51b0413ef0bfa8fc7a8ddb1e38..704fdc0ce3a42e92f5a7b5cb58b2bfa1b4fbfb92 100644 --- a/docs/src/ai/subscription.md +++ b/docs/src/ai/subscription.md @@ -2,12 +2,12 @@ Zed's hosted models are offered via subscription to Zed Pro or Zed Business. -> Using your own API keys is _free_—you do not need to subscribe to a Zed plan to use our AI features with your own keys. +> Using [your own API keys](./llm-providers.md), and [external agents](./external-agents.md), is _free_ — you do not need to subscribe to a Zed plan to use AI features. See the following pages for specific aspects of our subscription offering: -- [Plans and Usage](./plans-and-usage.md): Outlines Zed's plans, how usage is measured, and usage-based pricing for overages. +- [Models](./models.md): Overview of the models offered by Zed's subscriptions. -- [Billing](./billing.md): Billing policies and procedures, and how to update or view various billing settings. +- [Plans and Usage](./plans-and-usage.md): Outlines Zed's plans and how usage is measured. -- [Models](./models.md): Overview of the models offered by Zed's subscriptions. +- [Billing](./billing.md): Billing policies and procedures, and how to update or view various billing settings. diff --git a/docs/src/git.md b/docs/src/git.md index cccbad9b2e37ba55dc45f1f100883437759727f0..f40040bec83226b19c17d9efdaf9241032dca7a5 100644 --- a/docs/src/git.md +++ b/docs/src/git.md @@ -79,7 +79,7 @@ In there, you can use the "Uncommit" button, which performs the `git reset HEAD Zed currently supports LLM-powered commit message generation. You can ask AI to generate a commit message by focusing on the message editor within the Git Panel and either clicking on the pencil icon in the bottom left, or reaching for the {#action git::GenerateCommitMessage} ({#kb git::GenerateCommitMessage}) keybinding. -> Note that you need to have an LLM provider configured. Visit [the AI configuration page](./ai/configuration.md) to learn how to do so. +> Note that you need to have an LLM provider configured for billing purposes, either via your own API keys or trialing/paying for Zed's hosted AI models. Visit [the AI configuration page](./ai/configuration.md) to learn how to do so. You can specify your preferred model to use by providing a `commit_message_model` agent setting. See [Feature-specific models](./ai/agent-settings.md#feature-specific-models) for more information. From fa76b6ce0623a11fb162569ea87179c15d7cda8a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 24 Sep 2025 19:36:35 +0300 Subject: [PATCH 248/721] Switch to "standard" as a default line height in the terminal (#38798) Closes https://github.com/zed-industries/zed/issues/38686 Release Notes: - Switched to "standard" as a default line height in the terminal --- assets/settings/default.json | 2 +- docs/src/configuring-zed.md | 6 +++--- docs/src/visual-customization.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index b20bf773f82789b45564957652ea6a033e3a1c10..514dcd4fb3a291508a3952044efcc64b2b3b77f1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1412,7 +1412,7 @@ // "line_height": { // "custom": 2 // }, - "line_height": "comfortable", + "line_height": "standard", // Activate the python virtual environment, if one is found, in the // terminal's working directory (as resolved by the working_directory // setting). Set this to "off" to disable this behavior. diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index cc25f166ce848723e73587ede29516ee91823176..d4729078ce4daf264c9cceaab4bc0b66c378512e 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -3791,11 +3791,11 @@ See Buffer Font Features - Description: Set the terminal's line height. - Setting: `line_height` -- Default: `comfortable` +- Default: `standard` **Options** -1. Use a line height that's `comfortable` for reading, 1.618. (default) +1. Use a line height that's `comfortable` for reading, 1.618. ```json { @@ -3805,7 +3805,7 @@ See Buffer Font Features } ``` -2. Use a `standard` line height, 1.3. This option is useful for TUIs, particularly if they use box characters +2. Use a `standard` line height, 1.3. This option is useful for TUIs, particularly if they use box characters. (default) ```json { diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index 35adf484100fde2249098b8ddc0a2026603f5825..002ebdd4a8c0ca420a5f4be9acb2254c37820664 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -58,7 +58,7 @@ If you would like to use distinct themes for light mode/dark mode that can be se "font_family": "", "font_size": 15, // Terminal line height: comfortable (1.618), standard(1.3) or `{ "custom": 2 }` - "line_height": "comfortable", + "line_height": "standard", }, // Agent Panel Font Settings From 45a4277026b6732e318c43ffc5184a985d7bdb26 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 24 Sep 2025 12:42:01 -0400 Subject: [PATCH 249/721] Add community champion auto labeler (#38802) Release Notes: - N/A --- .../community_champion_auto_labeler.yml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/community_champion_auto_labeler.yml diff --git a/.github/workflows/community_champion_auto_labeler.yml b/.github/workflows/community_champion_auto_labeler.yml new file mode 100644 index 0000000000000000000000000000000000000000..c525bf4738f888b5ca84230982ff1f4f5da2db2f --- /dev/null +++ b/.github/workflows/community_champion_auto_labeler.yml @@ -0,0 +1,48 @@ +name: Community Champion Auto Labeler + +on: + issues: + types: [opened] + pull_request_target: + types: [opened] + +jobs: + label_community_champion: + if: github.repository_owner == 'zed-industries' + runs-on: ubuntu-latest + steps: + - name: Check if author is a community champion and apply label + uses: actions/github-script@v7 + with: + script: | + const communityChampionBody = `${{ secrets.COMMUNITY_CHAMPIONS }}`; + + const communityChampions = communityChampionBody + .split('\n') + .map(handle => handle.trim().toLowerCase()); + + let author; + if (context.eventName === 'issues') { + author = context.payload.issue.user.login; + } else if (context.eventName === 'pull_request_target') { + author = context.payload.pull_request.user.login; + } + + if (!author || !communityChampions.includes(author.toLowerCase())) { + return; + } + + const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number; + + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: ['community champion'] + }); + + console.log(`Applied 'community champion' label to #${issueNumber} by ${author}`); + } catch (error) { + console.error(`Failed to apply label: ${error.message}`); + } From 2b283e7c530a27ec0665a12fbbed2505e6c7156a Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:44:39 -0400 Subject: [PATCH 250/721] Revert "Fix UTF-8 character boundary panic in DirectWrite text ... (#37767)" (#38800) This reverts commit 9e7302520ec93a96a9b275a817f083b96620148e. I run into an infinite hang in Zed nightly and used instruments and activity monitor to sample what was going on. The root cause seemed to be the unwrap_unchecked introduced in reverted PR. Release Notes: - N/A --- crates/gpui/src/text_system.rs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index b0776051d6049e70397912c28435ebe4eb2b8d7a..efa4ad032a66ce92a71cbd82be6ed4a63d527858 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -429,33 +429,7 @@ impl WindowTextSystem { break; }; - let mut run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; - - // Ensure the run length respects UTF-8 character boundaries - if run_len_within_line > 0 { - let text_slice = &line_text[run_start - line_start..]; - if run_len_within_line < text_slice.len() - && !text_slice.is_char_boundary(run_len_within_line) - { - // Find the previous character boundary using efficient bit-level checking - // UTF-8 characters are at most 4 bytes, so we only need to check up to 3 bytes back - let lower_bound = run_len_within_line.saturating_sub(3); - let search_range = - &text_slice.as_bytes()[lower_bound..=run_len_within_line]; - - // SAFETY: A valid character boundary must exist in this range because: - // 1. run_len_within_line is a valid position in the string slice - // 2. UTF-8 characters are at most 4 bytes, so some boundary exists in [run_len_within_line-3..=run_len_within_line] - let pos_from_lower = unsafe { - search_range - .iter() - .rposition(|&b| (b as i8) >= -0x40) - .unwrap_unchecked() - }; - - run_len_within_line = lower_bound + pos_from_lower; - } - } + let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; if last_font == Some(run.font.clone()) { font_runs.last_mut().unwrap().len += run_len_within_line; From 0794de71e3269b977f9efc8dec98f31e9f78ad24 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:57:30 -0300 Subject: [PATCH 251/721] docs: Update note about agent message editor setting (#38805) As of stable 206.0, the `agent.message_editor_min_lines` setting is fully available, so removing the docs note that said it was only for Preview. Release Notes: - N/A --- docs/src/ai/agent-settings.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/src/ai/agent-settings.md b/docs/src/ai/agent-settings.md index 15f50c03c2fa972e55e00217e87f628093a9eb20..e38b5e29d7538a81cd8ed2694e62277600b791fd 100644 --- a/docs/src/ai/agent-settings.md +++ b/docs/src/ai/agent-settings.md @@ -196,8 +196,6 @@ It is set to `4` by default, and the max number of lines is always double of the } ``` -> This setting is currently available only in Preview. - ### Modifier to Send Make a modifier (`cmd` on macOS, `ctrl` on Linux) required to send messages. From e653cc90c5a54dec2c2527f606bd770f68c447f8 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Wed, 24 Sep 2025 12:02:32 -0500 Subject: [PATCH 252/721] Clean up last remnants of Settings UI v1 (#38803) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/settings/src/settings.rs | 2 - crates/settings/src/settings_store.rs | 7 +- crates/settings/src/settings_ui_core.rs | 237 ------------------------ 3 files changed, 1 insertion(+), 245 deletions(-) delete mode 100644 crates/settings/src/settings_ui_core.rs diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 4b53b8b6ea49bc45a059776e3f863d2dd2961651..f98bc7b74a1210a0ab3b79b4fdf70f79f60a873d 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -6,7 +6,6 @@ mod settings_content; mod settings_file; mod settings_json; mod settings_store; -mod settings_ui_core; mod vscode_import; pub use settings_content::*; @@ -27,7 +26,6 @@ pub use settings_json::*; pub use settings_store::{ InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, SettingsStore, }; -pub use settings_ui_core::*; pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index c5eb80c48c4481646446bb58f92378d54801615c..913014cf43e30dc3e41a6b08b8630abe0af43568 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -31,8 +31,7 @@ pub type EditorconfigProperties = ec4rs::Properties; use crate::{ ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent, - LanguageToSettingsMap, SettingsJsonSchemaParams, SettingsUiEntry, ThemeName, VsCodeSettings, - WorktreeId, + LanguageToSettingsMap, SettingsJsonSchemaParams, ThemeName, VsCodeSettings, WorktreeId, merge_from::MergeFrom, parse_json_with_comments, replace_value_in_json_text, settings_content::{ @@ -480,10 +479,6 @@ impl SettingsStore { }) }) } - - pub fn settings_ui_items(&self) -> impl IntoIterator { - [].into_iter() - } } impl SettingsStore { diff --git a/crates/settings/src/settings_ui_core.rs b/crates/settings/src/settings_ui_core.rs deleted file mode 100644 index d4f49785a6442134aba8a639183d082d52ab3baf..0000000000000000000000000000000000000000 --- a/crates/settings/src/settings_ui_core.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::{ - any::TypeId, - num::{NonZeroU32, NonZeroUsize}, - rc::Rc, -}; - -use anyhow::Context as _; -use fs::Fs; -use gpui::{AnyElement, App, AppContext as _, ReadGlobal as _, SharedString, Window}; -use smallvec::SmallVec; - -use crate::SettingsStore; - -pub trait SettingsUi { - fn settings_ui_item() -> SettingsUiItem { - // todo(settings_ui): remove this default impl, only entry should have a default impl - // because it's expected that the macro or custom impl use the item and the known paths to create the entry - SettingsUiItem::None - } - - fn settings_ui_entry() -> SettingsUiEntry { - SettingsUiEntry { - path: None, - title: "None entry", - item: SettingsUiItem::None, - documentation: None, - } - } -} - -#[derive(Clone)] -pub struct SettingsUiEntry { - /// The path in the settings JSON file for this setting. Relative to parent - /// None implies `#[serde(flatten)]` or `Settings::KEY.is_none()` for top level settings - pub path: Option<&'static str>, - /// What is displayed for the text for this entry - pub title: &'static str, - /// documentation for this entry. Constructed from the documentation comment above the struct or field - pub documentation: Option<&'static str>, - pub item: SettingsUiItem, -} - -#[derive(Clone)] -pub enum SettingsUiItemSingle { - SwitchField, - TextField, - /// A numeric stepper for a specific type of number - NumericStepper(NumType), - ToggleGroup { - /// Must be the same length as `labels` - variants: &'static [&'static str], - /// Must be the same length as `variants` - labels: &'static [&'static str], - }, - /// This should be used when toggle group size > 6 - DropDown { - /// Must be the same length as `labels` - variants: &'static [&'static str], - /// Must be the same length as `variants` - labels: &'static [&'static str], - }, - Custom(Rc, &mut Window, &mut App) -> AnyElement>), -} - -pub struct SettingsValue { - pub title: SharedString, - pub documentation: Option, - pub path: SmallVec<[SharedString; 1]>, - pub value: Option, - pub default_value: T, -} - -impl SettingsValue { - pub fn read(&self) -> &T { - match &self.value { - Some(value) => value, - None => &self.default_value, - } - } -} - -impl SettingsValue { - pub fn write_value(path: &SmallVec<[SharedString; 1]>, value: serde_json::Value, cx: &mut App) { - let settings_store = SettingsStore::global(cx); - let fs = ::global(cx); - - let rx = settings_store.update_settings_file_at_path(fs.clone(), path.as_slice(), value); - - let path = path.clone(); - cx.background_spawn(async move { - rx.await? - .with_context(|| format!("Failed to update setting at path `{:?}`", path.join("."))) - }) - .detach_and_log_err(cx); - } -} - -impl SettingsValue { - pub fn write( - path: &SmallVec<[SharedString; 1]>, - value: T, - cx: &mut App, - ) -> Result<(), serde_json::Error> { - SettingsValue::write_value(path, serde_json::to_value(value)?, cx); - Ok(()) - } -} - -#[derive(Clone)] -pub struct SettingsUiItemUnion { - /// Must be the same length as `labels` and `options` - pub defaults: Box<[serde_json::Value]>, - /// Must be the same length as defaults` and `options` - pub labels: &'static [&'static str], - /// Must be the same length as `defaults` and `labels` - pub options: Box<[Option]>, - pub determine_option: fn(&serde_json::Value, &App) -> usize, -} - -// todo(settings_ui): use in ToggleGroup and Dropdown -#[derive(Clone)] -pub struct SettingsEnumVariants {} - -pub struct SettingsUiEntryMetaData { - pub title: SharedString, - pub path: SharedString, - pub documentation: Option, -} - -#[derive(Clone)] -pub struct SettingsUiItemDynamicMap { - pub item: fn() -> SettingsUiItem, - pub determine_items: fn(&serde_json::Value, &App) -> Vec, - pub defaults_path: &'static [&'static str], -} - -#[derive(Clone)] -pub struct SettingsUiItemGroup { - pub items: Vec, -} - -#[derive(Clone)] -pub enum SettingsUiItem { - Group(SettingsUiItemGroup), - Single(SettingsUiItemSingle), - Union(SettingsUiItemUnion), - DynamicMap(SettingsUiItemDynamicMap), - // Array(SettingsUiItemArray), // code-actions: array of objects, array of string - None, -} - -impl SettingsUi for bool { - fn settings_ui_item() -> SettingsUiItem { - SettingsUiItem::Single(SettingsUiItemSingle::SwitchField) - } -} - -impl SettingsUi for Option { - fn settings_ui_item() -> SettingsUiItem { - SettingsUiItem::Single(SettingsUiItemSingle::SwitchField) - } -} - -impl SettingsUi for String { - fn settings_ui_item() -> SettingsUiItem { - SettingsUiItem::Single(SettingsUiItemSingle::TextField) - } -} - -impl SettingsUi for SettingsUiItem { - fn settings_ui_item() -> SettingsUiItem { - SettingsUiItem::Single(SettingsUiItemSingle::TextField) - } -} - -#[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum NumType { - U64 = 0, - U32 = 1, - F32 = 2, - USIZE = 3, - U32NONZERO = 4, -} - -pub static NUM_TYPE_NAMES: std::sync::LazyLock<[&'static str; NumType::COUNT]> = - std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_name)); -pub static NUM_TYPE_IDS: std::sync::LazyLock<[TypeId; NumType::COUNT]> = - std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_id)); - -impl NumType { - const COUNT: usize = 3; - const ALL: [NumType; Self::COUNT] = [NumType::U64, NumType::U32, NumType::F32]; - - pub fn type_id(self) -> TypeId { - match self { - NumType::U64 => TypeId::of::(), - NumType::U32 => TypeId::of::(), - NumType::F32 => TypeId::of::(), - NumType::USIZE => TypeId::of::(), - NumType::U32NONZERO => TypeId::of::(), - } - } - - pub fn type_name(self) -> &'static str { - match self { - NumType::U64 => std::any::type_name::(), - NumType::U32 => std::any::type_name::(), - NumType::F32 => std::any::type_name::(), - NumType::USIZE => std::any::type_name::(), - NumType::U32NONZERO => std::any::type_name::(), - } - } -} - -macro_rules! numeric_stepper_for_num_type { - ($type:ty, $num_type:ident) => { - impl SettingsUi for $type { - fn settings_ui_item() -> SettingsUiItem { - SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type)) - } - } - - impl SettingsUi for Option<$type> { - fn settings_ui_item() -> SettingsUiItem { - SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type)) - } - } - }; -} - -numeric_stepper_for_num_type!(u64, U64); -numeric_stepper_for_num_type!(u32, U32); -// todo(settings_ui) is there a better ui for f32? -numeric_stepper_for_num_type!(f32, F32); -numeric_stepper_for_num_type!(usize, USIZE); -numeric_stepper_for_num_type!(NonZeroUsize, U32NONZERO); From 6b646e3a14aeab9108c8547166fc69aeca0bf873 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Wed, 24 Sep 2025 19:44:03 +0200 Subject: [PATCH 253/721] zeta2: Support `edit prediction: clear history` (#38808) Release Notes: - N/A Co-authored-by: Agus Zubiaga --- crates/zed/src/zed/edit_prediction_registry.rs | 2 ++ crates/zeta2/src/zeta2.rs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/crates/zed/src/zed/edit_prediction_registry.rs b/crates/zed/src/zed/edit_prediction_registry.rs index d0e8e26074296e5b54bccaa73de7e06e4aacf205..5d099834b924859052b2620d7ea33383c3c530c6 100644 --- a/crates/zed/src/zed/edit_prediction_registry.rs +++ b/crates/zed/src/zed/edit_prediction_registry.rs @@ -97,6 +97,8 @@ pub fn init(client: Arc, user_store: Entity, cx: &mut App) { fn clear_zeta_edit_history(_: &zeta::ClearHistory, cx: &mut App) { if let Some(zeta) = zeta::Zeta::global(cx) { zeta.update(cx, |zeta, _| zeta.clear_history()); + } else if let Some(zeta) = zeta2::Zeta::try_global(cx) { + zeta.update(cx, |zeta, _| zeta.clear_history()); } } diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 8af20a6236e06c78d50b9f3c22609adfbe5571e2..fcf42958a07eeb6664252c6b8b1da69be2cb739b 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -108,6 +108,10 @@ pub enum Event { } impl Zeta { + pub fn try_global(cx: &App) -> Option> { + cx.try_global::().map(|global| global.0.clone()) + } + pub fn global( client: &Arc, user_store: &Entity, @@ -162,6 +166,12 @@ impl Zeta { self.options = options; } + pub fn clear_history(&mut self) { + for zeta_project in self.projects.values_mut() { + zeta_project.events.clear(); + } + } + pub fn usage(&self, cx: &App) -> Option { self.user_store.read(cx).edit_prediction_usage() } From 8ebe4fa149c2b8ba71cef5c059c672aee1416293 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 24 Sep 2025 20:07:34 +0200 Subject: [PATCH 254/721] gpui_macros: Hide inner test function from project symbols (#38809) This makes rust-analyzer not consider the function for project symbols, meaning searching for tests wont show two entries. Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/gpui_macros/src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui_macros/src/test.rs b/crates/gpui_macros/src/test.rs index 648d3499edb0a8f13031092e37d761368363af08..42ce304b97a2708bac8dc081b22a561162bdbb1a 100644 --- a/crates/gpui_macros/src/test.rs +++ b/crates/gpui_macros/src/test.rs @@ -100,7 +100,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { }; let inner_fn_attributes = mem::take(&mut inner_fn.attrs); - let inner_fn_name = format_ident!("_{}", inner_fn.sig.ident); + let inner_fn_name = format_ident!("__{}", inner_fn.sig.ident); let outer_fn_name = mem::replace(&mut inner_fn.sig.ident, inner_fn_name.clone()); let result = generate_test_function( From ed7bd5a8ed6cbda563c4e6f1acb66e13f545718b Mon Sep 17 00:00:00 2001 From: Victor Tran Date: Thu, 25 Sep 2025 05:09:03 +1000 Subject: [PATCH 255/721] gpui: Flash menu in menubar on macOS when action is triggered (#38588) On macOS, traditionally when a keyboard shortcut is activated, the menu in the menu bar flashes to indicate that the action was recognised. image This PR adds this functionality to GPUI, where when a keybind is pressed that triggers an action in the menu, the menu flashes. Release Notes: - N/A --- crates/gpui/examples/set_menus.rs | 65 +++++++++++++++----- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/linux/platform.rs | 2 + crates/gpui/src/platform/mac/platform.rs | 60 +++++++++++++++++- crates/gpui/src/platform/test/platform.rs | 4 +- crates/gpui/src/platform/windows/platform.rs | 2 + crates/gpui/src/window.rs | 3 + 7 files changed, 119 insertions(+), 18 deletions(-) diff --git a/crates/gpui/examples/set_menus.rs b/crates/gpui/examples/set_menus.rs index 8a97a8d8a2ce8135b1af0e981a186586c2d5ebb8..2488b5c100b1b7b27530ebf3e26143eb0a92b8fd 100644 --- a/crates/gpui/examples/set_menus.rs +++ b/crates/gpui/examples/set_menus.rs @@ -1,13 +1,17 @@ use gpui::{ - App, Application, Context, Menu, MenuItem, SystemMenuType, Window, WindowOptions, actions, div, - prelude::*, rgb, + App, Application, Context, FocusHandle, KeyBinding, Menu, MenuItem, PromptLevel, + SystemMenuType, Window, WindowOptions, actions, div, prelude::*, rgb, }; -struct SetMenus; +struct SetMenus { + focus_handle: FocusHandle, +} impl Render for SetMenus { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { div() + .key_context("root") + .track_focus(&self.focus_handle) .flex() .bg(rgb(0x2e7d32)) .size_full() @@ -15,6 +19,16 @@ impl Render for SetMenus { .items_center() .text_xl() .text_color(rgb(0xffffff)) + // Actions can also be registered within elements so they are only active when relevant + .on_action(|_: &Open, window, cx| { + let _ = window.prompt(PromptLevel::Info, "Open action fired", None, &["OK"], cx); + }) + .on_action(|_: &Copy, window, cx| { + let _ = window.prompt(PromptLevel::Info, "Copy action fired", None, &["OK"], cx); + }) + .on_action(|_: &Paste, window, cx| { + let _ = window.prompt(PromptLevel::Info, "Paste action fired", None, &["OK"], cx); + }) .child("Set Menus Example") } } @@ -23,24 +37,47 @@ fn main() { Application::new().run(|cx: &mut App| { // Bring the menu bar to the foreground (so you can see the menu bar) cx.activate(true); + // Bind keys to some menu actions + cx.bind_keys([ + KeyBinding::new("secondary-o", Open, None), + KeyBinding::new("secondary-c", Copy, None), + KeyBinding::new("secondary-v", Paste, None), + ]); // Register the `quit` function so it can be referenced by the `MenuItem::action` in the menu bar cx.on_action(quit); // Add menu items - cx.set_menus(vec![Menu { - name: "set_menus".into(), - items: vec![ - MenuItem::os_submenu("Services", SystemMenuType::Services), - MenuItem::separator(), - MenuItem::action("Quit", Quit), - ], - }]); - cx.open_window(WindowOptions::default(), |_, cx| cx.new(|_| SetMenus {})) - .unwrap(); + cx.set_menus(vec![ + Menu { + name: "set_menus".into(), + items: vec![ + MenuItem::os_submenu("Services", SystemMenuType::Services), + MenuItem::separator(), + MenuItem::action("Quit", Quit), + ], + }, + Menu { + name: "File".into(), + items: vec![MenuItem::action("Open", Open)], + }, + Menu { + name: "Edit".into(), + items: vec![ + MenuItem::action("Copy", Copy), + MenuItem::action("Paste", Paste), + ], + }, + ]); + cx.open_window(WindowOptions::default(), |_, cx| { + cx.new(|cx| SetMenus { + focus_handle: cx.focus_handle(), + }) + }) + .unwrap(); }); } // Associate actions using the `actions!` macro (or `Action` derive macro) -actions!(set_menus, [Quit]); +actions!(set_menus, [Quit, Open, Copy, Paste]); // Define the quit function that is registered with the App fn quit(_: &Quit, cx: &mut App) { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 444b60ac154424c423c3cd6a827b22cd7024694f..05b597e9b6936a842ac2cdf7a4784f306ac07eaf 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -250,6 +250,7 @@ pub(crate) trait Platform: 'static { fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); + fn on_action_triggered(&self, action: &dyn Action); fn compositor_name(&self) -> &'static str { "" diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 196e5b65d04125ca90c588212c140d3a63345c2e..a6e22c42da26233a0a83a7e9a1293ef262526170 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -442,6 +442,8 @@ impl Platform for P { }); } + fn on_action_triggered(&self, _action: &dyn Action) {} + fn app_path(&self) -> Result { // get the path of the executable of the current process let app_path = env::current_exe()?; diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 9909c78c472a17e683380be71b65484800c0fa76..e0a1d7770d0fea7022960702c250a4bd7d813a20 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -177,6 +177,8 @@ pub(crate) struct MacPlatformState { dock_menu: Option, menus: Option>, keyboard_mapper: Rc, + action_menus: Vec<(Box, id)>, + handling_menu_item: bool, } impl Default for MacPlatform { @@ -219,6 +221,8 @@ impl MacPlatform { on_keyboard_layout_change: None, menus: None, keyboard_mapper, + handling_menu_item: false, + action_menus: Vec::new(), })) } @@ -241,6 +245,7 @@ impl MacPlatform { menus: &Vec, delegate: id, actions: &mut Vec>, + action_menus: &mut Vec<(Box, id)>, keymap: &Keymap, ) -> id { unsafe { @@ -258,6 +263,7 @@ impl MacPlatform { item_config, delegate, actions, + action_menus, keymap, )); } @@ -292,6 +298,7 @@ impl MacPlatform { &item_config, delegate, actions, + &mut Vec::new(), keymap, )); } @@ -304,6 +311,7 @@ impl MacPlatform { item: &MenuItem, delegate: id, actions: &mut Vec>, + action_menus: &mut Vec<(Box, id)>, keymap: &Keymap, ) -> id { static DEFAULT_CONTEXT: OnceLock> = OnceLock::new(); @@ -412,6 +420,7 @@ impl MacPlatform { let tag = actions.len() as NSInteger; let _: () = msg_send![item, setTag: tag]; actions.push(action.boxed_clone()); + action_menus.push((action.boxed_clone(), item)); item } MenuItem::Submenu(Menu { name, items }) => { @@ -419,7 +428,13 @@ impl MacPlatform { let submenu = NSMenu::new(nil).autorelease(); submenu.setDelegate_(delegate); for item in items { - submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap)); + submenu.addItem_(Self::create_menu_item( + item, + delegate, + actions, + action_menus, + keymap, + )); } item.setSubmenu_(submenu); item.setTitle_(ns_string(name)); @@ -888,6 +903,30 @@ impl Platform for MacPlatform { self.0.lock().validate_menu_command = Some(callback); } + fn on_action_triggered(&self, action: &dyn Action) { + let menu_item = { + let mut state = self.0.lock(); + let Some(menu_item) = state + .action_menus + .iter() + .find(|(menu_action, _)| menu_action.partial_eq(action)) + .map(|(_, menu)| *menu) + else { + return; + }; + + state.handling_menu_item = true; + menu_item + }; + + unsafe { + let parent: id = msg_send![menu_item, menu]; + let index: NSInteger = msg_send![parent, indexOfItem: menu_item]; + let _: () = msg_send![parent, performActionForItemAtIndex: index]; + } + self.0.lock().handling_menu_item = false; + } + fn keyboard_layout(&self) -> Box { Box::new(MacKeyboardLayout::new()) } @@ -905,15 +944,24 @@ impl Platform for MacPlatform { } fn set_menus(&self, menus: Vec, keymap: &Keymap) { + let mut action_menus = Vec::new(); unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; let mut state = self.0.lock(); let actions = &mut state.menu_actions; - let menu = self.create_menu_bar(&menus, NSWindow::delegate(app), actions, keymap); + let menu = self.create_menu_bar( + &menus, + NSWindow::delegate(app), + actions, + &mut action_menus, + keymap, + ); drop(state); app.setMainMenu_(menu); } - self.0.lock().menus = Some(menus.into_iter().map(|menu| menu.owned()).collect()); + let mut state = self.0.lock(); + state.menus = Some(menus.into_iter().map(|menu| menu.owned()).collect()); + state.action_menus = action_menus; } fn get_menus(&self) -> Option> { @@ -1465,6 +1513,12 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_mac_platform(this); let mut lock = platform.0.lock(); + + // If the menu item is currently being handled, i.e., this action is being triggered as a + // result of a keyboard shortcut causing the menu to flash, don't do anything. + if lock.handling_menu_item { + return; + } if let Some(mut callback) = lock.menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 15b909199fbd53b974e6a140f3223641dc0ac6ae..85b84248ff55f6a83d1e99e41d0029ed0b8ce5e7 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,5 +1,5 @@ use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task, @@ -378,6 +378,8 @@ impl Platform for TestPlatform { fn on_validate_app_menu_command(&self, _callback: Box bool>) {} + fn on_action_triggered(&self, _action: &dyn Action) {} + fn app_path(&self) -> Result { unimplemented!() } diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 2eb1862f36a26592e18dc2e44875e08319361cc8..0ad343f2d3a590288f9b274f1456d88052850a4b 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -536,6 +536,8 @@ impl Platform for WindowsPlatform { .validate_app_menu_command = Some(callback); } + fn on_action_triggered(&self, _action: &dyn Action) {} + fn app_path(&self) -> Result { Ok(std::env::current_exe()?) } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index d9bf27dca1253fa0a5286148ea64a03c3a5bac37..e81751f61c38f2098bc00cc2f23b51b415c61555 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4023,6 +4023,8 @@ impl Window { let any_action = action.as_any(); if action_type == any_action.type_id() { cx.propagate_event = false; // Actions stop propagation by default during the bubble phase + cx.platform.on_action_triggered(action); + listener(any_action, DispatchPhase::Bubble, self, cx); if !cx.propagate_event { @@ -4040,6 +4042,7 @@ impl Window { for listener in global_listeners.iter().rev() { cx.propagate_event = false; // Actions stop propagation by default during the bubble phase + cx.platform.on_action_triggered(action); listener(action.as_any(), DispatchPhase::Bubble, cx); if !cx.propagate_event { break; From 523c042930315508dbdfbffff99e725610f84fcf Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Wed, 24 Sep 2025 16:16:06 -0500 Subject: [PATCH 256/721] settings_ui: Collect all settings files (#38816) Closes #ISSUE Updates the settings editor to collect all known settings files from the settings store, in order to show them in the UI. Additionally adds a fake worktree instantiation in the settings UI example binary in order to have more than one file available when testing. Release Notes: - N/A *or* Added/Fixed/Improved ... --- Cargo.lock | 3 + crates/session/src/session.rs | 2 +- crates/settings/src/settings.rs | 3 +- crates/settings/src/settings_store.rs | 40 +++++++++++ crates/settings_ui/Cargo.toml | 5 +- .../settings_ui/examples/.zed/settings.json | 1 + crates/settings_ui/examples/ui.rs | 71 ++++++++++++++++++- crates/settings_ui/src/settings_ui.rs | 51 +++++++++---- 8 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 crates/settings_ui/examples/.zed/settings.json diff --git a/Cargo.lock b/Cargo.lock index 6190534f0f3383db2b9aa95544b27956042f87b3..ffc66d93a070deb136c59465362a4c761165a449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14508,6 +14508,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assets", + "client", "command_palette_hooks", "editor", "feature_flags", @@ -14516,9 +14517,11 @@ dependencies = [ "gpui", "language", "menu", + "node_runtime", "paths", "project", "serde", + "session", "settings", "theme", "ui", diff --git a/crates/session/src/session.rs b/crates/session/src/session.rs index 438059fef78adb6b83853335bfe1d38113a768d9..e8151f97fd4290058ed9928ba17a700fddbf3d35 100644 --- a/crates/session/src/session.rs +++ b/crates/session/src/session.rs @@ -43,7 +43,7 @@ impl Session { } } - #[cfg(any(test, feature = "test-support"))] + // #[cfg(any(test, feature = "test-support"))] pub fn test() -> Self { Self { session_id: Uuid::new_v4().to_string(), diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index f98bc7b74a1210a0ab3b79b4fdf70f79f60a873d..3a8dcfb8546c9a13ffd0927052d3a514c772b547 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -24,7 +24,8 @@ pub use keymap_file::{ pub use settings_file::*; pub use settings_json::*; pub use settings_store::{ - InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, SettingsStore, + InvalidSettingsError, LocalSettingsKind, Settings, SettingsFile, SettingsKey, SettingsLocation, + SettingsStore, }; pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 913014cf43e30dc3e41a6b08b8630abe0af43568..0ed87760f5339fc063a81892c899cac06beb11ef 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -156,6 +156,16 @@ pub struct SettingsStore { mpsc::UnboundedSender LocalBoxFuture<'static, Result<()>>>>, } +#[derive(Clone, PartialEq)] +pub enum SettingsFile { + User, + Global, + Extension, + Server, + Default, + Local((WorktreeId, Arc)), +} + #[derive(Clone)] pub struct Editorconfig { pub is_root: bool, @@ -479,6 +489,36 @@ impl SettingsStore { }) }) } + + pub fn get_all_files(&self) -> Vec { + let mut files = Vec::from_iter( + self.local_settings + .keys() + // rev because these are sorted by path, so highest precedence is last + .rev() + .cloned() + .map(SettingsFile::Local), + ); + + if self.server_settings.is_some() { + files.push(SettingsFile::Server); + } + // ignoring profiles + // ignoring os profiles + // ignoring release channel profiles + + if self.user_settings.is_some() { + files.push(SettingsFile::User); + } + if self.extension_settings.is_some() { + files.push(SettingsFile::Extension); + } + if self.global_settings.is_some() { + files.push(SettingsFile::Global); + } + files.push(SettingsFile::Default); + files + } } impl SettingsStore { diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 4dd1ecdb7f2a425b20f04a623853af75d3006ec2..00e79567ba863a3fbe3440f58cb3ee48ea088db2 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -32,12 +32,15 @@ workspace-hack.workspace = true workspace.workspace = true [dev-dependencies] -settings = { workspace = true, features = ["test-support"] } +settings.workspace = true futures.workspace = true language.workspace = true assets.workspace = true paths.workspace = true zlog.workspace = true +client.workspace = true +session.workspace = true +node_runtime.workspace = true [[example]] name = "ui" diff --git a/crates/settings_ui/examples/.zed/settings.json b/crates/settings_ui/examples/.zed/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93 --- /dev/null +++ b/crates/settings_ui/examples/.zed/settings.json @@ -0,0 +1 @@ +{} diff --git a/crates/settings_ui/examples/ui.rs b/crates/settings_ui/examples/ui.rs index 46b5fa02a45d94d0094a12db90746b7889ce6ba5..ae8847c5635b39a9e7afce11f31d6d87e3cb95f2 100644 --- a/crates/settings_ui/examples/ui.rs +++ b/crates/settings_ui/examples/ui.rs @@ -1,11 +1,42 @@ use std::sync::Arc; use futures::StreamExt; +use gpui::AppContext as _; use settings::{DEFAULT_KEYMAP_PATH, KeymapFile, SettingsStore, watch_config_file}; use settings_ui::open_settings_editor; use ui::BorrowAppContext; +fn merge_paths(a: &std::path::Path, b: &std::path::Path) -> std::path::PathBuf { + let a_parts: Vec<_> = a.components().collect(); + let b_parts: Vec<_> = b.components().collect(); + + let mut overlap = 0; + for i in 0..=a_parts.len().min(b_parts.len()) { + if a_parts[a_parts.len() - i..] == b_parts[..i] { + overlap = i; + } + } + + let mut result = std::path::PathBuf::new(); + for part in &a_parts { + result.push(part.as_os_str()); + } + for part in &b_parts[overlap..] { + result.push(part.as_os_str()); + } + result +} + fn main() { + zlog::init(); + zlog::init_output_stderr(); + + let [crate_path, file_path] = [env!("CARGO_MANIFEST_DIR"), file!()].map(std::path::Path::new); + let example_dir_abs_path = merge_paths(crate_path, file_path) + .parent() + .unwrap() + .to_path_buf(); + let app = gpui::Application::new().with_assets(assets::Assets); let fs = Arc::new(fs::RealFs::new(None, app.background_executor())); @@ -14,15 +45,49 @@ fn main() { fs.clone(), paths::settings_file().clone(), ); - zlog::init(); - zlog::init_output_stderr(); app.run(move |cx| { ::set_global(fs.clone(), cx); settings::init(cx); theme::init(theme::LoadThemes::JustBase, cx); + + client::init_settings(cx); workspace::init_settings(cx); - project::Project::init_settings(cx); + // production client because fake client requires gpui/test-support + // and that causes issues with the real stuff we want to do + let client = client::Client::production(cx); + let user_store = cx.new(|cx| client::UserStore::new(client.clone(), cx)); + let languages = Arc::new(language::LanguageRegistry::new( + cx.background_executor().clone(), + )); + + client::init(&client, cx); + + project::Project::init(&client, cx); + + zlog::info!( + "Creating fake worktree in {}", + example_dir_abs_path.display(), + ); + let project = project::Project::local( + client.clone(), + node_runtime::NodeRuntime::unavailable(), + user_store, + languages, + fs.clone(), + Some(Default::default()), // WARN: if None is passed here, prepare to be process bombed + cx, + ); + let worktree_task = project.update(cx, |project, cx| { + project.create_worktree(example_dir_abs_path, true, cx) + }); + cx.spawn(async move |_| { + let worktree = worktree_task.await.unwrap(); + std::mem::forget(worktree); + }) + .detach(); + std::mem::forget(project); + language::init(cx); editor::init(cx); menu::init(); diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 86e8b61ec6a439033f43a9e085282383ad619c26..d13dd5cd50378a66d03a7a403fe20bd68b7850f5 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -200,7 +200,7 @@ struct SettingItem { } #[allow(unused)] -#[derive(Clone)] +#[derive(Clone, PartialEq)] enum SettingsFile { User, // Uses all settings. Local((WorktreeId, Arc)), // Has a special name, and special set of settings @@ -216,11 +216,11 @@ impl SettingsFile { } } - fn name(&self) -> String { + fn name(&self) -> SharedString { match self { - SettingsFile::User => "User".to_string(), - SettingsFile::Local((_, path)) => format!("Local ({})", path.display()), - SettingsFile::Server(file) => format!("Server ({})", file), + SettingsFile::User => SharedString::new_static("User"), + SettingsFile::Local((_, path)) => format!("Local ({})", path.display()).into(), + SettingsFile::Server(file) => format!("Server ({})", file).into(), } } } @@ -234,22 +234,18 @@ impl SettingsWindow { editor }); let mut this = Self { - files: vec![ - SettingsFile::User, - SettingsFile::Local(( - WorktreeId::from_usize(0), - Arc::from(Path::new("/my-project/")), - )), - ], + files: vec![], current_file: current_file, pages: vec![], current_page: 0, search, }; - cx.observe_global_in::(window, move |_, _, cx| { + cx.observe_global_in::(window, move |this, _, cx| { + this.fetch_files(cx); cx.notify(); }) .detach(); + this.fetch_files(cx); this.build_ui(); this @@ -259,7 +255,36 @@ impl SettingsWindow { self.pages = self.current_file.pages(); } + fn fetch_files(&mut self, cx: &mut App) { + let settings_store = cx.global::(); + let mut ui_files = vec![]; + let all_files = settings_store.get_all_files(); + for file in all_files { + let settings_ui_file = match file { + settings::SettingsFile::User => SettingsFile::User, + settings::SettingsFile::Global => continue, + settings::SettingsFile::Extension => continue, + settings::SettingsFile::Server => SettingsFile::Server("todo: server name"), + settings::SettingsFile::Default => continue, + settings::SettingsFile::Local(location) => SettingsFile::Local(location), + }; + ui_files.push(settings_ui_file); + } + ui_files.reverse(); + if !ui_files.contains(&self.current_file) { + self.change_file(0); + } + self.files = ui_files; + } + fn change_file(&mut self, ix: usize) { + if ix >= self.files.len() { + self.current_file = SettingsFile::User; + return; + } + if self.files[ix] == self.current_file { + return; + } self.current_file = self.files[ix].clone(); self.build_ui(); } From 91b0f4238264e3612f019d4acd519801c9840814 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Sep 2025 15:33:31 -0600 Subject: [PATCH 257/721] Fix panic when hovering string ending with unicode (#38818) Release Notes: - Fixed a panic when hovering a string literal ending with an emoji --- crates/editor/src/hover_links.rs | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 7dad9f28e4b2767945f7f0aa4941c4862255c2a4..8177a4dca1607cff57cb513d743dd2e975b675d4 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -1862,4 +1862,42 @@ mod tests { cx.simulate_click(screen_coord, Modifiers::secondary_key()); cx.update_workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 1)); } + + #[gpui::test] + async fn test_hover_unicode(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + You can't open ˇ\"🤩\" because it's an emoji. + "}); + + // File does not exist + let screen_coord = cx.pixel_position(indoc! {" + You can't open ˇ\"🤩\" because it's an emoji. + "}); + cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); + + // No highlight, does not panic... + cx.update_editor(|editor, window, cx| { + assert!( + editor + .snapshot(window, cx) + .text_highlight_ranges::() + .unwrap_or_default() + .1 + .is_empty() + ); + }); + + // Does not open the directory + cx.simulate_click(screen_coord, Modifiers::secondary_key()); + cx.update_workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 1)); + } } From 4a1bab52f3b799262c1104e42f4c615f5cb4c90d Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 24 Sep 2025 17:52:02 -0400 Subject: [PATCH 258/721] Update release process docs to include storing feature media (#38824) Release Notes: - N/A --- docs/src/development/releases.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/development/releases.md b/docs/src/development/releases.md index 76432d93f002dc7dd9d9d119d24ed1348863c73e..bbb7caf30e183f361e314c36c5dcb773f003b508 100644 --- a/docs/src/development/releases.md +++ b/docs/src/development/releases.md @@ -51,10 +51,14 @@ Credentials for various services used in this process can be found in 1Password. - We sometimes correct things here and there that didn't translate from GitHub's renderer to Kit's. 1. Build social media posts based on the popular items in stable. + - You can use the [prior week's post chain](https://zed.dev/channel/tweets-23331) as your outline. - Stage the copy and assets using [Buffer](https://buffer.com), for both X and BlueSky. - Publish both, one at a time, ensuring both are posted to each respective platform. +1. Store any media created in the `Feature Media` directory in our Google Drive. + Other team members may want to reference or use these later on. + ## Patch release process If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. From 3c626f3758fde66013074ff226ed2e2c89383800 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Sep 2025 16:18:00 -0600 Subject: [PATCH 259/721] Only allow single chars for whitespace map (#38825) Release Notes: - Only allow single characters in the whitespace map --- crates/editor/src/element.rs | 4 ++-- crates/language/src/language_settings.rs | 17 +++++++++++--- crates/settings/src/merge_from.rs | 1 + .../settings/src/settings_content/language.rs | 22 ++++--------------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 72b84b4599b681088b6cc4aa5afd705d8af229f8..65da1d76b78b9099073a9abd655a7f54608bd848 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9324,7 +9324,7 @@ impl Element for EditorElement { .language_settings(cx) .whitespace_map; - let tab_char = whitespace_map.tab(); + let tab_char = whitespace_map.tab.clone(); let tab_len = tab_char.len(); let tab_invisible = window.text_system().shape_line( tab_char, @@ -9340,7 +9340,7 @@ impl Element for EditorElement { None, ); - let space_char = whitespace_map.space(); + let space_char = whitespace_map.space.clone(); let space_len = space_char.len(); let space_invisible = window.text_system().shape_line( space_char, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 0e05123033bf92d537eef5eab258db4eac7e7a56..bae356614b56f5017415937dbaccb83723313c9d 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -7,7 +7,7 @@ use ec4rs::{ property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs}, }; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; -use gpui::{App, Modifiers}; +use gpui::{App, Modifiers, SharedString}; use itertools::{Either, Itertools}; pub use settings::{ @@ -59,6 +59,12 @@ pub struct AllLanguageSettings { pub(crate) file_types: FxHashMap, GlobSet>, } +#[derive(Debug, Clone)] +pub struct WhitespaceMap { + pub space: SharedString, + pub tab: SharedString, +} + /// The settings for a particular language. #[derive(Debug, Clone)] pub struct LanguageSettings { @@ -118,7 +124,7 @@ pub struct LanguageSettings { /// Whether to show tabs and spaces in the editor. pub show_whitespaces: settings::ShowWhitespaceSetting, /// Visible characters used to render whitespace when show_whitespaces is enabled. - pub whitespace_map: settings::WhitespaceMap, + pub whitespace_map: WhitespaceMap, /// Whether to start a new line with a comment when a previous line is a comment as well. pub extend_comment_on_newline: bool, /// Inlay hint related settings. @@ -503,6 +509,8 @@ impl settings::Settings for AllLanguageSettings { let prettier = settings.prettier.unwrap(); let indent_guides = settings.indent_guides.unwrap(); let tasks = settings.tasks.unwrap(); + let whitespace_map = settings.whitespace_map.unwrap(); + LanguageSettings { tab_size: settings.tab_size.unwrap(), hard_tabs: settings.hard_tabs.unwrap(), @@ -536,7 +544,10 @@ impl settings::Settings for AllLanguageSettings { show_edit_predictions: settings.show_edit_predictions.unwrap(), edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(), show_whitespaces: settings.show_whitespaces.unwrap(), - whitespace_map: settings.whitespace_map.unwrap(), + whitespace_map: WhitespaceMap { + space: SharedString::new(whitespace_map.space.to_string()), + tab: SharedString::new(whitespace_map.tab.to_string()), + }, extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(), inlay_hints: InlayHintSettings { enabled: inlay_hints.enabled.unwrap(), diff --git a/crates/settings/src/merge_from.rs b/crates/settings/src/merge_from.rs index c12994786ff5fadb6686c6ab1b93d9700195eb2a..141ae0ce6d2280c390db80d34949c0d62cbffd02 100644 --- a/crates/settings/src/merge_from.rs +++ b/crates/settings/src/merge_from.rs @@ -49,6 +49,7 @@ merge_from_overwrites!( bool, f64, f32, + char, std::num::NonZeroUsize, std::num::NonZeroU32, String, diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index 24a3de0c3b918e86488c42d9d12fedb8e16081c6..493b3813d3d613eebc3031276a072804fe3b7c55 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -261,7 +261,7 @@ pub struct LanguageSettingsContent { /// Visible characters used to render whitespace when show_whitespaces is enabled. /// /// Default: "•" for spaces, "→" for tabs. - pub whitespace_map: Option, + pub whitespace_map: Option, /// Whether to start a new line with a comment when a previous line is a comment as well. /// /// Default: true @@ -354,23 +354,9 @@ pub enum ShowWhitespaceSetting { #[skip_serializing_none] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] -pub struct WhitespaceMap { - pub space: Option, - pub tab: Option, -} - -impl WhitespaceMap { - pub fn space(&self) -> SharedString { - self.space - .as_ref() - .map_or_else(|| SharedString::from("•"), |s| SharedString::from(s)) - } - - pub fn tab(&self) -> SharedString { - self.tab - .as_ref() - .map_or_else(|| SharedString::from("→"), |s| SharedString::from(s)) - } +pub struct WhitespaceMapContent { + pub space: char, + pub tab: char, } /// The behavior of `editor::Rewrap`. From 03f9cf4414f3a3aefc31f307295d450c5d9ee54f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 24 Sep 2025 15:57:33 -0700 Subject: [PATCH 260/721] Represent relative paths using a dedicated, separator-agnostic type (#38744) Closes https://github.com/zed-industries/zed/issues/38690 Closes #37353 ### Background On Windows, paths are normally separated by `\`, unlike mac and linux where they are separated by `/`. When editing code in a project that uses a different path style than your local system (e.g. remoting from Windows to Linux, using WSL, and collaboration between windows and unix users), the correct separator for a path may differ from the "native" separator. Previously, to work around this, Zed converted paths' separators in numerous places. This was applied to both absolute and relative paths, leading to incorrect conversions in some cases. ### Solution Many code paths in Zed use paths that are *relative* to either a worktree root or a git repository. This PR introduces a dedicated type for these paths called `RelPath`, which stores the path in the same way regardless of host platform, and offers `Path`-like manipulation APIs. RelPath supports *displaying* the path using either separator, so that we can display paths in a style that is determined at runtime based on the current project. The representation of absolute paths is left untouched, for now. Absolute paths are different from relative paths because (except in contexts where we know that the path refers to the local filesystem) they should generally be treated as opaque strings. Currently we use a mix of types for these paths (std::path::Path, String, SanitizedPath). Release Notes: - N/A --------- Co-authored-by: Cole Miller Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Peter Tripp Co-authored-by: Smit Barmase Co-authored-by: Lukas Wirth --- Cargo.lock | 3 +- crates/acp_thread/src/acp_thread.rs | 2 +- crates/acp_thread/src/diff.rs | 26 +- crates/action_log/src/action_log.rs | 24 +- crates/agent/src/agent.rs | 6 +- crates/agent/src/context.rs | 5 +- crates/agent/src/context_store.rs | 7 +- crates/agent/src/thread.rs | 105 +- crates/agent/src/thread_store.rs | 36 +- crates/agent2/src/agent.rs | 16 +- crates/agent2/src/db.rs | 12 +- crates/agent2/src/thread.rs | 18 +- crates/agent2/src/tools/copy_path_tool.rs | 4 +- crates/agent2/src/tools/diagnostics_tool.rs | 6 +- crates/agent2/src/tools/edit_file_tool.rs | 43 +- crates/agent2/src/tools/find_path_tool.rs | 17 +- crates/agent2/src/tools/grep_tool.rs | 5 +- .../agent2/src/tools/list_directory_tool.rs | 40 +- crates/agent2/src/tools/move_path_tool.rs | 2 +- crates/agent2/src/tools/read_file_tool.rs | 6 +- .../agent_ui/src/acp/completion_provider.rs | 18 +- crates/agent_ui/src/acp/message_editor.rs | 82 +- crates/agent_ui/src/acp/thread_view.rs | 23 +- crates/agent_ui/src/agent_ui.rs | 2 +- crates/agent_ui/src/context_picker.rs | 15 +- .../src/context_picker/completion_provider.rs | 79 +- .../src/context_picker/file_context_picker.rs | 63 +- .../context_picker/symbol_context_picker.rs | 36 +- crates/agent_ui/src/text_thread_editor.rs | 12 +- crates/assistant_slash_command/Cargo.toml | 1 + .../src/extension_slash_command.rs | 7 +- crates/assistant_slash_commands/Cargo.toml | 5 +- .../src/cargo_workspace_command.rs | 159 +++ .../src/diagnostics_command.rs | 52 +- .../src/file_command.rs | 110 +- crates/assistant_tools/src/copy_path_tool.rs | 4 +- .../assistant_tools/src/diagnostics_tool.rs | 6 +- crates/assistant_tools/src/edit_file_tool.rs | 46 +- crates/assistant_tools/src/find_path_tool.rs | 20 +- crates/assistant_tools/src/grep_tool.rs | 3 +- .../src/list_directory_tool.rs | 28 +- crates/assistant_tools/src/move_path_tool.rs | 2 +- crates/call/src/call_impl/room.rs | 3 +- crates/client/src/telemetry.rs | 11 +- .../20221109000000_test_schema.sql | 3 +- ...250916173002_add_path_style_to_project.sql | 1 + crates/collab/src/db.rs | 2 + crates/collab/src/db/queries/projects.rs | 9 + crates/collab/src/db/tables/project.rs | 1 + crates/collab/src/db/tests/db_tests.rs | 6 +- crates/collab/src/rpc.rs | 3 + .../collab/src/tests/channel_buffer_tests.rs | 17 +- .../collab/src/tests/channel_guest_tests.rs | 3 +- crates/collab/src/tests/editor_tests.rs | 98 +- crates/collab/src/tests/following_tests.rs | 63 +- crates/collab/src/tests/git_tests.rs | 19 +- crates/collab/src/tests/integration_tests.rs | 424 +++--- .../random_project_collaboration_tests.rs | 93 +- .../remote_editing_collaboration_tests.rs | 46 +- crates/copilot/src/copilot.rs | 20 +- crates/dap/src/adapters.rs | 4 +- crates/dap_adapters/src/python.rs | 17 +- .../src/extension_dap_adapter.rs | 3 +- crates/debugger_ui/src/debugger_panel.rs | 11 +- crates/debugger_ui/src/new_process_modal.rs | 33 +- .../src/session/running/breakpoint_list.rs | 13 +- .../src/session/running/module_list.rs | 2 +- .../src/session/running/stack_frame_list.rs | 2 +- crates/debugger_ui/src/stack_trace_view.rs | 2 +- .../debugger_ui/src/tests/debugger_panel.rs | 12 +- crates/debugger_ui/src/tests/inline_values.rs | 8 +- .../debugger_ui/src/tests/stack_frame_list.rs | 16 +- crates/diagnostics/src/buffer_diagnostics.rs | 16 +- crates/diagnostics/src/diagnostics_tests.rs | 8 +- crates/docs_preprocessor/src/main.rs | 3 +- .../src/syntax_index.rs | 10 +- crates/editor/src/clangd_ext.rs | 49 +- crates/editor/src/editor.rs | 38 +- crates/editor/src/editor_tests.rs | 73 +- crates/editor/src/element.rs | 11 +- crates/editor/src/git/blame.rs | 9 +- crates/editor/src/items.rs | 41 +- crates/editor/src/test.rs | 26 +- crates/editor/src/test/editor_test_context.rs | 6 +- crates/eval/src/example.rs | 4 +- .../src/examples/add_arg_to_trait_method.rs | 8 +- .../eval/src/examples/code_block_citations.rs | 2 +- crates/eval/src/instance.rs | 2 +- crates/extension/src/extension.rs | 3 +- crates/extension_host/src/extension_host.rs | 9 +- crates/extension_host/src/headless_host.rs | 7 +- .../src/wasm_host/wit/since_v0_1_0.rs | 11 +- .../src/wasm_host/wit/since_v0_6_0.rs | 14 +- crates/extensions_ui/src/extension_suggest.rs | 25 +- crates/file_finder/src/file_finder.rs | 314 ++--- crates/file_finder/src/file_finder_tests.rs | 328 +++-- crates/file_finder/src/open_path_prompt.rs | 6 +- .../file_finder/src/open_path_prompt_tests.rs | 8 +- crates/fs/src/fake_git_repo.rs | 12 +- crates/fs/src/fs.rs | 54 +- crates/fuzzy/Cargo.toml | 3 + crates/fuzzy/src/matcher.rs | 102 +- crates/fuzzy/src/paths.rs | 58 +- crates/fuzzy/src/strings.rs | 8 +- crates/git/src/blame.rs | 7 +- crates/git/src/commit.rs | 35 +- crates/git/src/git.rs | 19 +- crates/git/src/repository.rs | 174 +-- crates/git/src/status.rs | 7 +- crates/git_ui/src/commit_view.rs | 35 +- crates/git_ui/src/git_panel.rs | 96 +- crates/git_ui/src/project_diff.rs | 35 +- crates/go_to_line/src/go_to_line.rs | 10 +- crates/image_viewer/src/image_viewer.rs | 23 +- crates/inspector_ui/src/div_inspector.rs | 3 +- crates/journal/src/journal.rs | 2 +- crates/language/src/buffer.rs | 29 +- crates/language/src/buffer_tests.rs | 3 +- crates/language/src/language.rs | 3 +- crates/language/src/language_registry.rs | 2 +- crates/language/src/language_settings.rs | 19 +- crates/language/src/manifest.rs | 9 +- crates/language/src/toolchain.rs | 17 +- .../src/extension_lsp_adapter.rs | 4 +- crates/language_tools/src/lsp_button.rs | 9 +- crates/language_tools/src/lsp_log_view.rs | 2 +- .../language_tools/src/lsp_log_view_tests.rs | 2 +- crates/languages/src/json.rs | 9 +- crates/languages/src/python.rs | 10 +- crates/languages/src/rust.rs | 5 +- crates/languages/src/typescript.rs | 10 +- crates/languages/src/vtsls.rs | 4 +- crates/languages/src/yaml.rs | 4 +- crates/markdown/src/markdown.rs | 7 +- crates/markdown/src/parser.rs | 4 +- crates/markdown/src/path_range.rs | 38 +- crates/multi_buffer/src/multi_buffer.rs | 13 +- crates/multi_buffer/src/multi_buffer_tests.rs | 8 +- crates/outline/src/outline.rs | 4 +- crates/outline_panel/src/outline_panel.rs | 195 ++- crates/paths/src/paths.rs | 37 +- crates/prettier/src/prettier.rs | 17 +- crates/project/Cargo.toml | 1 - crates/project/src/agent_server_store.rs | 15 +- crates/project/src/buffer_store.rs | 23 +- crates/project/src/context_server_store.rs | 6 +- .../project/src/debugger/breakpoint_store.rs | 4 +- crates/project/src/debugger/dap_store.rs | 14 +- crates/project/src/git_store.rs | 139 +- crates/project/src/git_store/conflict_set.rs | 17 +- crates/project/src/git_store/git_traversal.rs | 238 ++-- crates/project/src/image_store.rs | 22 +- crates/project/src/lsp_store.rs | 322 +++-- crates/project/src/manifest_tree.rs | 7 +- crates/project/src/manifest_tree/path_trie.rs | 107 +- .../project/src/manifest_tree/server_tree.rs | 6 +- crates/project/src/prettier_store.rs | 8 +- crates/project/src/project.rs | 326 +++-- crates/project/src/project_settings.rs | 135 +- crates/project/src/project_tests.rs | 607 +++++---- crates/project/src/search.rs | 23 +- crates/project/src/task_inventory.rs | 105 +- crates/project/src/task_store.rs | 2 +- crates/project/src/terminals.rs | 12 +- crates/project/src/toolchain_store.rs | 110 +- crates/project/src/worktree_store.rs | 373 +++++- crates/project/src/yarn.rs | 9 +- crates/project_panel/Cargo.toml | 1 - crates/project_panel/src/project_panel.rs | 473 ++++--- .../project_panel/src/project_panel_tests.rs | 124 +- crates/project_symbols/src/project_symbols.rs | 41 +- crates/prompt_store/src/prompts.rs | 7 +- crates/proto/proto/call.proto | 2 + crates/proto/proto/worktree.proto | 4 +- crates/proto/src/proto.rs | 20 - crates/proto/src/typed_envelope.rs | 153 --- crates/remote/src/remote_client.rs | 2 +- crates/remote/src/transport.rs | 7 +- crates/remote/src/transport/ssh.rs | 74 +- crates/remote/src/transport/wsl.rs | 77 +- crates/remote_server/src/headless_project.rs | 29 +- .../remote_server/src/remote_editing_tests.rs | 112 +- crates/repl/src/kernels/mod.rs | 5 +- crates/search/src/buffer_search.rs | 12 +- crates/search/src/project_search.rs | 35 +- crates/settings/src/settings_store.rs | 81 +- crates/svg_preview/src/svg_preview_view.rs | 1 - crates/tab_switcher/src/tab_switcher_tests.rs | 5 +- crates/tasks_ui/src/modal.rs | 4 +- crates/tasks_ui/src/tasks_ui.rs | 9 +- .../src/terminal_path_like_target.rs | 49 +- crates/terminal_view/src/terminal_view.rs | 3 +- crates/title_bar/src/title_bar.rs | 8 +- .../src/active_toolchain.rs | 13 +- .../src/toolchain_selector.rs | 34 +- .../src/components/label/highlighted_label.rs | 8 + crates/util/Cargo.toml | 2 +- crates/util/src/paths.rs | 160 ++- crates/util/src/rel_path.rs | 515 ++++++++ crates/util/src/util.rs | 1 + crates/vim/src/command.rs | 68 +- crates/vim/src/normal.rs | 2 +- crates/vim/src/state.rs | 7 +- crates/workspace/src/item.rs | 7 +- crates/workspace/src/pane.rs | 45 +- crates/workspace/src/persistence.rs | 80 +- crates/workspace/src/workspace.rs | 69 +- crates/worktree/src/worktree.rs | 1161 +++++++---------- crates/worktree/src/worktree_settings.rs | 21 +- crates/worktree/src/worktree_tests.rs | 539 +++----- crates/zed/src/zed.rs | 37 +- crates/zeta/src/license_detection.rs | 12 +- crates/zeta/src/zeta.rs | 9 +- crates/zeta2/src/zeta2.rs | 18 +- crates/zeta2_tools/src/zeta2_tools.rs | 39 +- crates/zeta_cli/src/main.rs | 35 +- 216 files changed, 5873 insertions(+), 5257 deletions(-) create mode 100644 crates/assistant_slash_commands/src/cargo_workspace_command.rs create mode 100644 crates/collab/migrations/20250916173002_add_path_style_to_project.sql create mode 100644 crates/util/src/rel_path.rs diff --git a/Cargo.lock b/Cargo.lock index ffc66d93a070deb136c59465362a4c761165a449..609edba3ec57bbba341799afbfecbcbf22b1181a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -892,6 +892,7 @@ dependencies = [ "serde", "serde_json", "ui", + "util", "workspace", "workspace-hack", ] @@ -12094,7 +12095,6 @@ dependencies = [ "markdown", "node_runtime", "parking_lot", - "pathdiff", "paths", "postage", "prettier", @@ -12147,7 +12147,6 @@ dependencies = [ "git", "git_ui", "gpui", - "indexmap 2.9.0", "language", "menu", "pretty_assertions", diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index f2327ca70b104de12f44d74aacd1a5a2bb1eca3b..b63e9a2a8403a3e271f2d1f9e07ba3dddfb3cd53 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -573,7 +573,7 @@ impl ToolCallContent { ))), acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| { Diff::finalized( - diff.path, + diff.path.to_string_lossy().to_string(), diff.old_text, diff.new_text, language_registry, diff --git a/crates/acp_thread/src/diff.rs b/crates/acp_thread/src/diff.rs index f75af0543e373b47b0c6de36760ba18b5d9da318..753f157af934d6c92fcd6766aa645ec45c6b22f7 100644 --- a/crates/acp_thread/src/diff.rs +++ b/crates/acp_thread/src/diff.rs @@ -6,12 +6,7 @@ use itertools::Itertools; use language::{ Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer, }; -use std::{ - cmp::Reverse, - ops::Range, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use util::ResultExt; pub enum Diff { @@ -21,7 +16,7 @@ pub enum Diff { impl Diff { pub fn finalized( - path: PathBuf, + path: String, old_text: Option, new_text: String, language_registry: Arc, @@ -36,7 +31,7 @@ impl Diff { let buffer = new_buffer.clone(); async move |_, cx| { let language = language_registry - .language_for_file_path(&path) + .language_for_file_path(Path::new(&path)) .await .log_err(); @@ -152,12 +147,15 @@ impl Diff { let path = match self { Diff::Pending(PendingDiff { new_buffer: buffer, .. - }) => buffer.read(cx).file().map(|file| file.path().as_ref()), - Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()), + }) => buffer + .read(cx) + .file() + .map(|file| file.path().display(file.path_style(cx))), + Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()), }; format!( "Diff: {}\n```\n{}\n```\n", - path.unwrap_or(Path::new("untitled")).display(), + path.unwrap_or("untitled".into()), buffer_text ) } @@ -244,8 +242,8 @@ impl PendingDiff { .new_buffer .read(cx) .file() - .map(|file| file.path().as_ref()) - .unwrap_or(Path::new("untitled")) + .map(|file| file.path().display(file.path_style(cx))) + .unwrap_or("untitled".into()) .into(); // Replace the buffer in the multibuffer with the snapshot @@ -348,7 +346,7 @@ impl PendingDiff { } pub struct FinalizedDiff { - path: PathBuf, + path: String, base_text: Arc, new_buffer: Entity, multibuffer: Entity, diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index 11ba596ac5a0ecd4ed49744d0eafa9defcde20c1..b7722f211afda3a77bc96292a50acf869e7424d6 100644 --- a/crates/action_log/src/action_log.rs +++ b/crates/action_log/src/action_log.rs @@ -8,10 +8,7 @@ use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint}; use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle}; use std::{cmp, ops::Range, sync::Arc}; use text::{Edit, Patch, Rope}; -use util::{ - RangeExt, ResultExt as _, - paths::{PathStyle, RemotePathBuf}, -}; +use util::{RangeExt, ResultExt as _}; /// Tracks actions performed by tools in a thread pub struct ActionLog { @@ -62,7 +59,13 @@ impl ActionLog { let file_path = buffer .read(cx) .file() - .map(|file| RemotePathBuf::new(file.full_path(cx), PathStyle::Posix).to_proto()) + .map(|file| { + let mut path = file.full_path(cx).to_string_lossy().into_owned(); + if file.path_style(cx).is_windows() { + path = path.replace('\\', "/"); + } + path + }) .unwrap_or_else(|| format!("buffer_{}", buffer.entity_id())); let mut result = String::new(); @@ -2301,7 +2304,7 @@ mod tests { .await; fs.set_head_for_repo( path!("/project/.git").as_ref(), - &[("file.txt".into(), "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())], + &[("file.txt", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())], "0000000", ); cx.run_until_parked(); @@ -2384,7 +2387,7 @@ mod tests { // - Ignores the last line edit (j stays as j) fs.set_head_for_repo( path!("/project/.git").as_ref(), - &[("file.txt".into(), "A\nb\nc\nf\nG\nh\ni\nj".into())], + &[("file.txt", "A\nb\nc\nf\nG\nh\ni\nj".into())], "0000001", ); cx.run_until_parked(); @@ -2415,10 +2418,7 @@ mod tests { // Make another commit that accepts the NEW line but with different content fs.set_head_for_repo( path!("/project/.git").as_ref(), - &[( - "file.txt".into(), - "A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into(), - )], + &[("file.txt", "A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into())], "0000002", ); cx.run_until_parked(); @@ -2444,7 +2444,7 @@ mod tests { // Final commit that accepts all remaining edits fs.set_head_for_repo( path!("/project/.git").as_ref(), - &[("file.txt".into(), "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())], + &[("file.txt", "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())], "0000003", ); cx.run_until_parked(); diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index 7e3590f05df18d258fae91fd8aa596c07c5fb516..9cd2a93d9bfc9a8a1940fea150f651b60f1a1073 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -9,12 +9,14 @@ pub mod tool_use; pub use context::{AgentContext, ContextId, ContextLoadResult}; pub use context_store::ContextStore; +use fs::Fs; +use std::sync::Arc; pub use thread::{ LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio, }; pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore}; -pub fn init(cx: &mut gpui::App) { - thread_store::init(cx); +pub fn init(fs: Arc, cx: &mut gpui::App) { + thread_store::init(fs, cx); } diff --git a/crates/agent/src/context.rs b/crates/agent/src/context.rs index 4510b0d3d3548b3ff807a3e549a9f2dc53951452..fdcf0316a04eb493fb03c14d2c5891fb86ab72c5 100644 --- a/crates/agent/src/context.rs +++ b/crates/agent/src/context.rs @@ -18,6 +18,7 @@ use std::path::PathBuf; use std::{ops::Range, path::Path, sync::Arc}; use text::{Anchor, OffsetRangeExt as _}; use util::markdown::MarkdownCodeBlock; +use util::rel_path::RelPath; use util::{ResultExt as _, post_inc}; pub const RULES_ICON: IconName = IconName::Reader; @@ -242,7 +243,7 @@ pub struct DirectoryContext { #[derive(Debug, Clone)] pub struct DirectoryContextDescendant { /// Path within the directory. - pub rel_path: Arc, + pub rel_path: Arc, pub fenced_codeblock: SharedString, } @@ -968,7 +969,7 @@ pub fn load_context( }) } -fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec> { +fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec> { let mut files = Vec::new(); for entry in worktree.child_entries(path) { diff --git a/crates/agent/src/context_store.rs b/crates/agent/src/context_store.rs index b531852a184ffeaf86862990f03210ceb6033395..9b9653700005306db07e38e6b74a9d1a585fd2b2 100644 --- a/crates/agent/src/context_store.rs +++ b/crates/agent/src/context_store.rs @@ -14,7 +14,10 @@ use futures::{self, FutureExt}; use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity}; use language::{Buffer, File as _}; use language_model::LanguageModelImage; -use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file}; +use project::{ + Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file, + lsp_store::SymbolLocation, +}; use prompt_store::UserPromptId; use ref_cast::RefCast as _; use std::{ @@ -500,7 +503,7 @@ impl ContextStore { let Some(context_path) = buffer.project_path(cx) else { return false; }; - if context_path != symbol.path { + if symbol.path != SymbolLocation::InProject(context_path) { return false; } let context_range = context.range.to_point_utf16(&buffer.snapshot()); diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 8b9d489ccf472ca16435934e48a12b70dc783c40..0790e8b2894dc5495cd46585a315696118afd33a 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -234,7 +234,6 @@ impl MessageSegment { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ProjectSnapshot { pub worktree_snapshots: Vec, - pub unsaved_buffer_paths: Vec, pub timestamp: DateTime, } @@ -2857,27 +2856,11 @@ impl Thread { .map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx)) .collect(); - cx.spawn(async move |_, cx| { + cx.spawn(async move |_, _| { 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(), }) }) @@ -3275,6 +3258,7 @@ mod tests { use agent_settings::{AgentProfileId, AgentSettings}; use assistant_tool::ToolRegistry; use assistant_tools; + use fs::Fs; use futures::StreamExt; use futures::future::BoxFuture; use futures::stream::BoxStream; @@ -3298,9 +3282,10 @@ mod tests { #[gpui::test] async fn test_message_with_context(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}), ) @@ -3375,9 +3360,10 @@ fn main() {{ #[gpui::test] async fn test_only_include_new_contexts(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({ "file1.rs": "fn function1() {}\n", @@ -3531,9 +3517,10 @@ fn main() {{ #[gpui::test] async fn test_message_without_files(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}), ) @@ -3610,9 +3597,10 @@ fn main() {{ #[gpui::test] #[ignore] // turn this test on when project_notifications tool is re-enabled async fn test_stale_buffer_notification(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}), ) @@ -3738,9 +3726,10 @@ fn main() {{ #[gpui::test] async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}), ) @@ -3760,9 +3749,10 @@ fn main() {{ #[gpui::test] async fn test_serializing_thread_profile(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}), ) @@ -3803,9 +3793,10 @@ fn main() {{ #[gpui::test] async fn test_temperature_setting(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); let project = create_test_project( + &fs, cx, json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}), ) @@ -3897,9 +3888,9 @@ fn main() {{ #[gpui::test] async fn test_thread_summary(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _thread_store, thread, _context_store, model) = setup_test_environment(cx, project.clone()).await; @@ -3982,9 +3973,9 @@ fn main() {{ #[gpui::test] async fn test_thread_summary_error_set_manually(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _thread_store, thread, _context_store, model) = setup_test_environment(cx, project.clone()).await; @@ -4004,9 +3995,9 @@ fn main() {{ #[gpui::test] async fn test_thread_summary_error_retry(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _thread_store, thread, _context_store, model) = setup_test_environment(cx, project.clone()).await; @@ -4158,9 +4149,9 @@ fn main() {{ #[gpui::test] async fn test_retry_on_overloaded_error(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -4236,9 +4227,9 @@ fn main() {{ #[gpui::test] async fn test_retry_on_internal_server_error(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -4318,9 +4309,9 @@ fn main() {{ #[gpui::test] async fn test_exponential_backoff_on_retries(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -4438,9 +4429,9 @@ fn main() {{ #[gpui::test] async fn test_max_retries_exceeded(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -4529,9 +4520,9 @@ fn main() {{ #[gpui::test] async fn test_retry_message_removed_on_retry(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -4702,9 +4693,9 @@ fn main() {{ #[gpui::test] async fn test_successful_completion_clears_retry_state(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -4868,9 +4859,9 @@ fn main() {{ #[gpui::test] async fn test_rate_limit_retry_single_attempt(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -5053,9 +5044,9 @@ fn main() {{ #[gpui::test] async fn test_ui_only_messages_not_sent_to_model(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, model) = setup_test_environment(cx, project.clone()).await; // Insert a regular user message @@ -5153,9 +5144,9 @@ fn main() {{ #[gpui::test] async fn test_no_retry_without_burn_mode(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Ensure we're in Normal mode (not Burn mode) @@ -5226,9 +5217,9 @@ fn main() {{ #[gpui::test] async fn test_retry_canceled_on_stop(cx: &mut TestAppContext) { - init_test_settings(cx); + let fs = init_test_settings(cx); - let project = create_test_project(cx, json!({})).await; + let project = create_test_project(&fs, cx, json!({})).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; // Enable Burn Mode to allow retries @@ -5334,7 +5325,8 @@ fn main() {{ cx.run_until_parked(); } - fn init_test_settings(cx: &mut TestAppContext) { + fn init_test_settings(cx: &mut TestAppContext) -> Arc { + let fs = FakeFs::new(cx.executor()); cx.update(|cx| { let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); @@ -5342,7 +5334,7 @@ fn main() {{ Project::init_settings(cx); AgentSettings::register(cx); prompt_store::init(cx); - thread_store::init(cx); + thread_store::init(fs.clone(), cx); workspace::init_settings(cx); language_model::init_settings(cx); ThemeSettings::register(cx); @@ -5356,16 +5348,17 @@ fn main() {{ )); assistant_tools::init(http_client, cx); }); + fs } // Helper to create a test project with test files async fn create_test_project( + fs: &Arc, cx: &mut TestAppContext, files: serde_json::Value, ) -> Entity { - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/test"), files).await; - Project::test(fs, [path!("/test").as_ref()], cx).await + fs.as_fake().insert_tree(path!("/test"), files).await; + Project::test(fs.clone(), [path!("/test").as_ref()], cx).await } async fn setup_test_environment( diff --git a/crates/agent/src/thread_store.rs b/crates/agent/src/thread_store.rs index 2eae758b835d5d79ccf86f18be032f2d9bb87c2b..fe73b959b7fbe96f139a2b8267748c78cce88e2e 100644 --- a/crates/agent/src/thread_store.rs +++ b/crates/agent/src/thread_store.rs @@ -10,6 +10,7 @@ use assistant_tool::{Tool, ToolId, ToolWorkingSet}; use chrono::{DateTime, Utc}; use collections::HashMap; use context_server::ContextServerId; +use fs::{Fs, RemoveOptions}; use futures::{ FutureExt as _, StreamExt as _, channel::{mpsc, oneshot}, @@ -39,7 +40,7 @@ use std::{ rc::Rc, sync::{Arc, Mutex}, }; -use util::ResultExt as _; +use util::{ResultExt as _, rel_path::RelPath}; use zed_env_vars::ZED_STATELESS; @@ -85,8 +86,8 @@ const RULES_FILE_NAMES: [&str; 9] = [ "GEMINI.md", ]; -pub fn init(cx: &mut App) { - ThreadsDatabase::init(cx); +pub fn init(fs: Arc, cx: &mut App) { + ThreadsDatabase::init(fs, cx); } /// A system prompt shared by all threads created by this ThreadStore @@ -234,7 +235,7 @@ impl ThreadStore { if items.iter().any(|(path, _, _)| { RULES_FILE_NAMES .iter() - .any(|name| path.as_ref() == Path::new(name)) + .any(|name| path.as_ref() == RelPath::new(name).unwrap()) }) { self.enqueue_system_prompt_reload(); } @@ -327,7 +328,7 @@ impl ThreadStore { cx: &mut App, ) -> Task<(WorktreeContext, Option)> { let tree = worktree.read(cx); - let root_name = tree.root_name().into(); + let root_name = tree.root_name_str().into(); let abs_path = tree.abs_path(); let mut context = WorktreeContext { @@ -367,7 +368,7 @@ impl ThreadStore { .into_iter() .filter_map(|name| { worktree - .entry_for_path(name) + .entry_for_path(RelPath::new(name).unwrap()) .filter(|entry| entry.is_file()) .map(|entry| entry.path.clone()) }) @@ -869,13 +870,13 @@ impl ThreadsDatabase { GlobalThreadsDatabase::global(cx).0.clone() } - fn init(cx: &mut App) { + fn init(fs: Arc, cx: &mut App) { let executor = cx.background_executor().clone(); let database_future = executor .spawn({ let executor = executor.clone(); let threads_dir = paths::data_dir().join("threads"); - async move { ThreadsDatabase::new(threads_dir, executor) } + async move { ThreadsDatabase::new(fs, threads_dir, executor).await } }) .then(|result| future::ready(result.map(Arc::new).map_err(Arc::new))) .boxed() @@ -884,13 +885,17 @@ impl ThreadsDatabase { cx.set_global(GlobalThreadsDatabase(database_future)); } - pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result { - std::fs::create_dir_all(&threads_dir)?; + pub async fn new( + fs: Arc, + threads_dir: PathBuf, + executor: BackgroundExecutor, + ) -> Result { + fs.create_dir(&threads_dir).await?; let sqlite_path = threads_dir.join("threads.db"); let mdb_path = threads_dir.join("threads-db.1.mdb"); - let needs_migration_from_heed = mdb_path.exists(); + let needs_migration_from_heed = fs.is_file(&mdb_path).await; let connection = if *ZED_STATELESS { Connection::open_memory(Some("THREAD_FALLBACK_DB")) @@ -932,7 +937,14 @@ impl ThreadsDatabase { .spawn(async move { log::info!("Starting threads.db migration"); Self::migrate_from_heed(&mdb_path, db_connection, executor_clone)?; - std::fs::remove_dir_all(mdb_path)?; + fs.remove_dir( + &mdb_path, + RemoveOptions { + recursive: true, + ignore_if_not_exists: true, + }, + ) + .await?; log::info!("threads.db migrated to sqlite"); Ok::<(), anyhow::Error>(()) }) diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index 36ab1be9ef79221b530258c4fdd55be2ac1e8b29..f3324da448b40f6197b529f8915117df4ae6030e 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -27,6 +27,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::Arc; use util::ResultExt; +use util::rel_path::RelPath; const RULES_FILE_NAMES: [&str; 9] = [ ".rules", @@ -434,7 +435,7 @@ impl NativeAgent { cx: &mut App, ) -> Task<(WorktreeContext, Option)> { let tree = worktree.read(cx); - let root_name = tree.root_name().into(); + let root_name = tree.root_name_str().into(); let abs_path = tree.abs_path(); let mut context = WorktreeContext { @@ -474,7 +475,7 @@ impl NativeAgent { .into_iter() .filter_map(|name| { worktree - .entry_for_path(name) + .entry_for_path(RelPath::new(name).unwrap()) .filter(|entry| entry.is_file()) .map(|entry| entry.path.clone()) }) @@ -558,7 +559,7 @@ impl NativeAgent { if items.iter().any(|(path, _, _)| { RULES_FILE_NAMES .iter() - .any(|name| path.as_ref() == Path::new(name)) + .any(|name| path.as_ref() == RelPath::new(name).unwrap()) }) { self.project_context_needs_refresh.send(()).ok(); } @@ -1208,7 +1209,7 @@ mod tests { use language_model::fake_provider::FakeLanguageModel; use serde_json::json; use settings::SettingsStore; - use util::path; + use util::{path, rel_path::rel_path}; #[gpui::test] async fn test_maintaining_project_context(cx: &mut TestAppContext) { @@ -1258,14 +1259,17 @@ mod tests { fs.insert_file("/a/.rules", Vec::new()).await; cx.run_until_parked(); agent.read_with(cx, |agent, cx| { - let rules_entry = worktree.read(cx).entry_for_path(".rules").unwrap(); + let rules_entry = worktree + .read(cx) + .entry_for_path(rel_path(".rules")) + .unwrap(); assert_eq!( agent.project_context.read(cx).worktrees, vec![WorktreeContext { root_name: "a".into(), abs_path: Path::new("/a").into(), rules_file: Some(RulesFileContext { - path_in_worktree: Path::new(".rules").into(), + path_in_worktree: rel_path(".rules").into(), text: "".into(), project_entry_id: rules_entry.id.to_usize() }) diff --git a/crates/agent2/src/db.rs b/crates/agent2/src/db.rs index 3be37bbb55a9b6820b59245ba05143e3432ab397..563ccdd7ca5b2c2cc63a8c7f30c59b9443f8a0bd 100644 --- a/crates/agent2/src/db.rs +++ b/crates/agent2/src/db.rs @@ -422,17 +422,15 @@ mod tests { use agent::MessageSegment; use agent::context::LoadedContext; use client::Client; - use fs::FakeFs; + use fs::{FakeFs, Fs}; use gpui::AppContext; use gpui::TestAppContext; use http_client::FakeHttpClient; use language_model::Role; use project::Project; - use serde_json::json; use settings::SettingsStore; - use util::test::TempTree; - fn init_test(cx: &mut TestAppContext) { + fn init_test(fs: Arc, cx: &mut TestAppContext) { env_logger::try_init().ok(); cx.update(|cx| { let settings_store = SettingsStore::test(cx); @@ -443,7 +441,7 @@ mod tests { 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::init(fs, cx); agent_settings::init(cx); language_model::init(client, cx); }); @@ -451,10 +449,8 @@ mod tests { #[gpui::test] async fn test_retrieving_old_thread(cx: &mut TestAppContext) { - let tree = TempTree::new(json!({})); - util::paths::set_home_dir(tree.path().into()); - init_test(cx); let fs = FakeFs::new(cx.executor()); + init_test(fs.clone(), cx); let project = Project::test(fs, [], cx).await; // Save a thread using the old agent. diff --git a/crates/agent2/src/thread.rs b/crates/agent2/src/thread.rs index 18f993cbe33ca8bffc2235906baf76c627da0030..630b4f906904df2410716f35ef7d15aa0f706e8b 100644 --- a/crates/agent2/src/thread.rs +++ b/crates/agent2/src/thread.rs @@ -879,27 +879,11 @@ impl Thread { .map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx)) .collect(); - cx.spawn(async move |_, cx| { + cx.spawn(async move |_, _| { 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(), }) }) diff --git a/crates/agent2/src/tools/copy_path_tool.rs b/crates/agent2/src/tools/copy_path_tool.rs index 905c090883192064782be62905c991393249f911..236978c78f0c2fee7ecf611486349bab094b3cec 100644 --- a/crates/agent2/src/tools/copy_path_tool.rs +++ b/crates/agent2/src/tools/copy_path_tool.rs @@ -84,9 +84,7 @@ impl AgentTool for CopyPathTool { .and_then(|project_path| project.entry_for_path(&project_path, cx)) { Some(entity) => match project.find_project_path(&input.destination_path, cx) { - Some(project_path) => { - project.copy_entry(entity.id, None, project_path.path, cx) - } + Some(project_path) => project.copy_entry(entity.id, project_path, cx), None => Task::ready(Err(anyhow!( "Destination path {} was outside the project.", input.destination_path diff --git a/crates/agent2/src/tools/diagnostics_tool.rs b/crates/agent2/src/tools/diagnostics_tool.rs index a38e317d43cb16d8ee652f1a5f7aabd8b1ce4c8f..f07ec4cfe6903ec454eb39a7afc7748327e026ec 100644 --- a/crates/agent2/src/tools/diagnostics_tool.rs +++ b/crates/agent2/src/tools/diagnostics_tool.rs @@ -6,7 +6,7 @@ use language::{DiagnosticSeverity, OffsetRangeExt}; use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::{fmt::Write, path::Path, sync::Arc}; +use std::{fmt::Write, sync::Arc}; use ui::SharedString; use util::markdown::MarkdownInlineCode; @@ -147,9 +147,7 @@ impl AgentTool for DiagnosticsTool { has_diagnostics = true; output.push_str(&format!( "{}: {} error(s), {} warning(s)\n", - Path::new(worktree.read(cx).root_name()) - .join(project_path.path) - .display(), + worktree.read(cx).absolutize(&project_path.path).display(), summary.error_count, summary.warning_count )); diff --git a/crates/agent2/src/tools/edit_file_tool.rs b/crates/agent2/src/tools/edit_file_tool.rs index 81f340b0b5c83648b1ec92210986b475b71c5bcf..9f2e8e3e313c1a0fbf32e8fe2b3da0c0d822ad69 100644 --- a/crates/agent2/src/tools/edit_file_tool.rs +++ b/crates/agent2/src/tools/edit_file_tool.rs @@ -17,10 +17,12 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; use smol::stream::StreamExt as _; +use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::sync::Arc; use ui::SharedString; use util::ResultExt; +use util::rel_path::RelPath; const DEFAULT_UI_TEXT: &str = "Editing file"; @@ -148,12 +150,11 @@ impl EditFileTool { // If any path component matches the local settings folder, then this could affect // the editor in ways beyond the project source, so prompt. - let local_settings_folder = paths::local_settings_folder_relative_path(); + let local_settings_folder = paths::local_settings_folder_name(); let path = Path::new(&input.path); - if path - .components() - .any(|component| component.as_os_str() == local_settings_folder.as_os_str()) - { + if path.components().any(|component| { + component.as_os_str() == <_ as AsRef>::as_ref(&local_settings_folder) + }) { return event_stream.authorize( format!("{} (local settings)", input.display_description), cx, @@ -162,6 +163,7 @@ 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. + // TODO this is broken when remoting if let Ok(canonical_path) = std::fs::canonicalize(&input.path) && canonical_path.starts_with(paths::config_dir()) { @@ -216,9 +218,7 @@ impl AgentTool for EditFileTool { .read(cx) .short_full_path_for_project_path(&project_path, cx) }) - .unwrap_or(Path::new(&input.path).into()) - .to_string_lossy() - .to_string() + .unwrap_or(input.path.to_string_lossy().to_string()) .into(), Err(raw_input) => { if let Some(input) = @@ -235,9 +235,7 @@ impl AgentTool for EditFileTool { .read(cx) .short_full_path_for_project_path(&project_path, cx) }) - .unwrap_or(Path::new(&input.path).into()) - .to_string_lossy() - .to_string() + .unwrap_or(input.path) .into(); } @@ -478,7 +476,7 @@ impl AgentTool for EditFileTool { ) -> Result<()> { event_stream.update_diff(cx.new(|cx| { Diff::finalized( - output.input_path, + output.input_path.to_string_lossy().to_string(), Some(output.old_text.to_string()), output.new_text, self.language_registry.clone(), @@ -542,10 +540,12 @@ fn resolve_path( let file_name = input .path .file_name() + .and_then(|file_name| file_name.to_str()) + .and_then(|file_name| RelPath::new(file_name).ok()) .context("Can't create file: invalid filename")?; let new_file_path = parent_project_path.map(|parent| ProjectPath { - path: Arc::from(parent.path.join(file_name)), + path: parent.path.join(file_name), ..parent }); @@ -690,13 +690,10 @@ mod tests { cx.update(|cx| resolve_path(&input, project, cx)) } + #[track_caller] fn assert_resolved_path_eq(path: anyhow::Result, expected: &str) { - let actual = path - .expect("Should return valid path") - .path - .to_str() - .unwrap() - .replace("\\", "/"); // Naive Windows paths normalization + let actual = path.expect("Should return valid path").path; + let actual = actual.as_str(); assert_eq!(actual, expected); } @@ -1408,8 +1405,8 @@ mod tests { // Parent directory references - find_project_path resolves these ( "project/../other", - false, - "Path with .. is resolved by find_project_path", + true, + "Path with .. that goes outside of root directory", ), ( "project/./src/file.rs", @@ -1437,16 +1434,18 @@ mod tests { ) }); + cx.run_until_parked(); + if should_confirm { stream_rx.expect_authorization().await; } else { - auth.await.unwrap(); assert!( stream_rx.try_next().is_err(), "Failed for case: {} - path: {} - expected no confirmation but got one", description, path ); + auth.await.unwrap(); } } } diff --git a/crates/agent2/src/tools/find_path_tool.rs b/crates/agent2/src/tools/find_path_tool.rs index b8b60f79f4cf9808a730b0c6428885b23b32d998..59f203cec98a17fda9e46f6fc222f3157d125060 100644 --- a/crates/agent2/src/tools/find_path_tool.rs +++ b/crates/agent2/src/tools/find_path_tool.rs @@ -156,10 +156,14 @@ impl AgentTool for FindPathTool { } fn search_paths(glob: &str, project: Entity, cx: &mut App) -> Task>> { - let path_matcher = match PathMatcher::new([ - // Sometimes models try to search for "". In this case, return all paths in the project. - if glob.is_empty() { "*" } else { glob }, - ]) { + let path_style = project.read(cx).path_style(cx); + let path_matcher = match PathMatcher::new( + [ + // Sometimes models try to search for "". In this case, return all paths in the project. + if glob.is_empty() { "*" } else { glob }, + ], + path_style, + ) { Ok(matcher) => matcher, Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))), }; @@ -173,9 +177,8 @@ fn search_paths(glob: &str, project: Entity, cx: &mut App) -> Task>(), + path_style, ) { Ok(matcher) => matcher, Err(error) => { @@ -132,7 +135,7 @@ impl AgentTool for GrepTool { .iter() .chain(global_settings.private_files.sources().iter()); - match PathMatcher::new(exclude_patterns) { + match PathMatcher::new(exclude_patterns, path_style) { Ok(matcher) => matcher, Err(error) => { return Task::ready(Err(anyhow!("invalid exclude pattern: {error}"))); diff --git a/crates/agent2/src/tools/list_directory_tool.rs b/crates/agent2/src/tools/list_directory_tool.rs index fe7e2a4d85d115d7b9b87be9a2b90f3f7bd19028..cd8b46ddebc2d9ffb953f8aabef10c30a33dde37 100644 --- a/crates/agent2/src/tools/list_directory_tool.rs +++ b/crates/agent2/src/tools/list_directory_tool.rs @@ -2,12 +2,12 @@ use crate::{AgentTool, ToolCallEventStream}; use agent_client_protocol::ToolKind; use anyhow::{Result, anyhow}; use gpui::{App, Entity, SharedString, Task}; -use project::{Project, WorktreeSettings}; +use project::{Project, ProjectPath, WorktreeSettings}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; use std::fmt::Write; -use std::{path::Path, sync::Arc}; +use std::sync::Arc; use util::markdown::MarkdownInlineCode; /// Lists files and directories in a given path. Prefer the `grep` or `find_path` tools when searching the codebase. @@ -86,13 +86,13 @@ impl AgentTool for ListDirectoryTool { .read(cx) .worktrees(cx) .filter_map(|worktree| { - worktree.read(cx).root_entry().and_then(|entry| { - if entry.is_dir() { - entry.path.to_str() - } else { - None - } - }) + let worktree = worktree.read(cx); + let root_entry = worktree.root_entry()?; + if root_entry.is_dir() { + Some(root_entry.path.display(worktree.path_style())) + } else { + None + } }) .collect::>() .join("\n"); @@ -143,7 +143,7 @@ impl AgentTool for ListDirectoryTool { } let worktree_snapshot = worktree.read(cx).snapshot(); - let worktree_root_name = worktree.read(cx).root_name().to_string(); + let worktree_root_name = worktree.read(cx).root_name(); let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else { return Task::ready(Err(anyhow!("Path not found: {}", input.path))); @@ -165,25 +165,17 @@ impl AgentTool for ListDirectoryTool { continue; } - if self - .project - .read(cx) - .find_project_path(&entry.path, cx) - .map(|project_path| { - 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) + let project_path: ProjectPath = (worktree_snapshot.id(), entry.path.clone()).into(); + if worktree_settings.is_path_excluded(&project_path.path) + || worktree_settings.is_path_private(&project_path.path) { continue; } - let full_path = Path::new(&worktree_root_name) + let full_path = worktree_root_name .join(&entry.path) - .display() - .to_string(); + .display(worktree_snapshot.path_style()) + .into_owned(); if entry.is_dir() { folders.push(full_path); } else { diff --git a/crates/agent2/src/tools/move_path_tool.rs b/crates/agent2/src/tools/move_path_tool.rs index 91880c1243e0aa48569ab8e6981ddd45b41ab411..ae58145126f6356beaa1457d719812bb56d6e7db 100644 --- a/crates/agent2/src/tools/move_path_tool.rs +++ b/crates/agent2/src/tools/move_path_tool.rs @@ -98,7 +98,7 @@ impl AgentTool for MovePathTool { .and_then(|project_path| project.entry_for_path(&project_path, cx)) { Some(entity) => match project.find_project_path(&input.destination_path, cx) { - Some(project_path) => project.rename_entry(entity.id, project_path.path, cx), + Some(project_path) => project.rename_entry(entity.id, project_path, cx), None => Task::ready(Err(anyhow!( "Destination path {} was outside the project.", input.destination_path diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index 6fa157630d487c517a536126d6b8dd4d4d53a4e1..fcbe8978583d2b17dd9dc6c31cf369749f81ef99 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -82,12 +82,12 @@ impl AgentTool for ReadFileTool { { match (input.start_line, input.end_line) { (Some(start), Some(end)) => { - format!("Read file `{}` (lines {}-{})", path.display(), start, end,) + format!("Read file `{path}` (lines {}-{})", start, end,) } (Some(start), None) => { - format!("Read file `{}` (from line {})", path.display(), start) + format!("Read file `{path}` (from line {})", start) } - _ => format!("Read file `{}`", path.display()), + _ => format!("Read file `{path}`"), } .into() } else { diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs index 8c14bd1642b05623b9236701630127c23f5e30af..8cbae5a5420a2f89d0f7ca478ae77a5d5d350411 100644 --- a/crates/agent_ui/src/acp/completion_provider.rs +++ b/crates/agent_ui/src/acp/completion_provider.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::ops::Range; +use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::AtomicBool; @@ -13,7 +14,7 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{App, Entity, Task, WeakEntity}; use language::{Buffer, CodeLabel, HighlightId}; use lsp::CompletionContext; -use project::lsp_store::CompletionDocumentation; +use project::lsp_store::{CompletionDocumentation, SymbolLocation}; use project::{ Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, Project, ProjectPath, Symbol, WorktreeId, @@ -22,6 +23,7 @@ use prompt_store::PromptStore; use rope::Point; use text::{Anchor, ToPoint as _}; use ui::prelude::*; +use util::rel_path::RelPath; use workspace::Workspace; use crate::AgentPanel; @@ -187,7 +189,7 @@ impl ContextPickerCompletionProvider { pub(crate) fn completion_for_path( project_path: ProjectPath, - path_prefix: &str, + path_prefix: &RelPath, is_recent: bool, is_directory: bool, source_range: Range, @@ -195,10 +197,12 @@ impl ContextPickerCompletionProvider { project: Entity, cx: &mut App, ) -> Option { + let path_style = project.read(cx).path_style(cx); let (file_name, directory) = crate::context_picker::file_context_picker::extract_file_name_and_directory( &project_path.path, path_prefix, + path_style, ); let label = @@ -250,7 +254,15 @@ impl ContextPickerCompletionProvider { let label = CodeLabel::plain(symbol.name.clone(), None); - let abs_path = project.read(cx).absolute_path(&symbol.path, cx)?; + let abs_path = match &symbol.path { + SymbolLocation::InProject(project_path) => { + project.read(cx).absolute_path(&project_path, cx)? + } + SymbolLocation::OutsideProject { + abs_path, + signature: _, + } => PathBuf::from(abs_path.as_ref()), + }; let uri = MentionUri::Symbol { abs_path, name: symbol.name.clone(), diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index c369d36d78eb179ec109c90365237d39159735fa..689d3e25eb239e2bd09952f887f1969fb5b510b5 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -48,7 +48,7 @@ use std::{ use text::OffsetRangeExt; use theme::ThemeSettings; use ui::{ButtonLike, TintColor, Toggleable, prelude::*}; -use util::{ResultExt, debug_panic}; +use util::{ResultExt, debug_panic, paths::PathStyle, rel_path::RelPath}; use workspace::{Workspace, notifications::NotifyResultExt as _}; use zed_actions::agent::Chat; @@ -108,6 +108,11 @@ impl MessageEditor { available_commands.clone(), )); let mention_set = MentionSet::default(); + // TODO: fix mentions when remoting with mixed path styles. + let host_and_guest_paths_differ = project + .read(cx) + .remote_client() + .is_some_and(|client| client.read(cx).path_style() != PathStyle::local()); 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)); @@ -117,7 +122,9 @@ impl MessageEditor { editor.set_show_indent_guides(false, cx); editor.set_soft_wrap(); editor.set_use_modal_editing(true); - editor.set_completion_provider(Some(completion_provider.clone())); + if !host_and_guest_paths_differ { + editor.set_completion_provider(Some(completion_provider.clone())); + } editor.set_context_menu_options(ContextMenuOptions { min_entries_visible: 12, max_entries_visible: 12, @@ -947,6 +954,7 @@ impl MessageEditor { window: &mut Window, cx: &mut Context, ) { + let path_style = self.project.read(cx).path_style(cx); let buffer = self.editor.read(cx).buffer().clone(); let Some(buffer) = buffer.read(cx).as_singleton() else { return; @@ -956,18 +964,15 @@ impl MessageEditor { let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else { continue; }; - let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else { + let Some(worktree) = self.project.read(cx).worktree_for_id(path.worktree_id, cx) else { continue; }; - let path_prefix = abs_path - .file_name() - .unwrap_or(path.path.as_os_str()) - .display() - .to_string(); + let abs_path = worktree.read(cx).absolutize(&path.path); let (file_name, _) = crate::context_picker::file_context_picker::extract_file_name_and_directory( &path.path, - &path_prefix, + worktree.read(cx).root_name(), + path_style, ); let uri = if entry.is_dir() { @@ -1176,7 +1181,7 @@ fn full_mention_for_directory( abs_path: &Path, cx: &mut App, ) -> Task> { - fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc, PathBuf)> { + fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc, PathBuf)> { let mut files = Vec::new(); for entry in worktree.child_entries(path) { @@ -1261,7 +1266,7 @@ fn full_mention_for_directory( }) } -fn render_directory_contents(entries: Vec<(Arc, PathBuf, String)>) -> String { +fn render_directory_contents(entries: Vec<(Arc, PathBuf, String)>) -> String { let mut output = String::new(); for (_relative_path, full_path, content) in entries { let fence = codeblock_fence_for_path(Some(&full_path), None); @@ -1595,7 +1600,7 @@ mod tests { use serde_json::json; use text::Point; use ui::{App, Context, IntoElement, Render, SharedString, Window}; - use util::{path, uri}; + use util::{path, paths::PathStyle, rel_path::rel_path, uri}; use workspace::{AppState, Item, Workspace}; use crate::acp::{ @@ -2105,16 +2110,18 @@ mod tests { let mut cx = VisualTestContext::from_window(*window, cx); let paths = vec![ - path!("a/one.txt"), - path!("a/two.txt"), - path!("a/three.txt"), - path!("a/four.txt"), - path!("b/five.txt"), - path!("b/six.txt"), - path!("b/seven.txt"), - path!("b/eight.txt"), + rel_path("a/one.txt"), + rel_path("a/two.txt"), + rel_path("a/three.txt"), + rel_path("a/four.txt"), + rel_path("b/five.txt"), + rel_path("b/six.txt"), + rel_path("b/seven.txt"), + rel_path("b/eight.txt"), ]; + let slash = PathStyle::local().separator(); + let mut opened_editors = Vec::new(); for path in paths { let buffer = workspace @@ -2122,7 +2129,7 @@ mod tests { workspace.open_path( ProjectPath { worktree_id, - path: Path::new(path).into(), + path: path.into(), }, None, false, @@ -2183,10 +2190,10 @@ mod tests { assert_eq!( current_completion_labels(editor), &[ - "eight.txt dir/b/", - "seven.txt dir/b/", - "six.txt dir/b/", - "five.txt dir/b/", + format!("eight.txt dir{slash}b{slash}"), + format!("seven.txt dir{slash}b{slash}"), + format!("six.txt dir{slash}b{slash}"), + format!("five.txt dir{slash}b{slash}"), ] ); editor.set_text("", window, cx); @@ -2214,14 +2221,14 @@ mod tests { assert_eq!( current_completion_labels(editor), &[ - "eight.txt dir/b/", - "seven.txt dir/b/", - "six.txt dir/b/", - "five.txt dir/b/", - "Files & Directories", - "Symbols", - "Threads", - "Fetch" + format!("eight.txt dir{slash}b{slash}"), + format!("seven.txt dir{slash}b{slash}"), + format!("six.txt dir{slash}b{slash}"), + format!("five.txt dir{slash}b{slash}"), + "Files & Directories".into(), + "Symbols".into(), + "Threads".into(), + "Fetch".into() ] ); }); @@ -2248,7 +2255,10 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!(editor.text(cx), "Lorem @file one"); assert!(editor.has_visible_completions_menu()); - assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]); + assert_eq!( + current_completion_labels(editor), + vec![format!("one.txt dir{slash}a{slash}")] + ); }); editor.update_in(&mut cx, |editor, window, cx| { @@ -2505,7 +2515,7 @@ mod tests { format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png") ); assert!(editor.has_visible_completions_menu()); - assert_eq!(current_completion_labels(editor), &["x.png dir/"]); + assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]); }); editor.update_in(&mut cx, |editor, window, cx| { @@ -2544,7 +2554,7 @@ mod tests { format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png") ); assert!(editor.has_visible_completions_menu()); - assert_eq!(current_completion_labels(editor), &["x.png dir/"]); + assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]); }); editor.update_in(&mut cx, |editor, window, cx| { diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 4a91a9fff7b9902bc51301752a578114159bf680..104b06049d44c2b9b08262b545ebe3a499f3155b 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -3704,29 +3704,32 @@ impl AcpThreadView { |(index, (buffer, _diff))| { let file = buffer.read(cx).file()?; let path = file.path(); + let path_style = file.path_style(cx); + let separator = file.path_style(cx).separator(); let file_path = path.parent().and_then(|parent| { - let parent_str = parent.to_string_lossy(); - - if parent_str.is_empty() { + if parent.is_empty() { None } else { Some( - Label::new(format!("/{}{}", parent_str, std::path::MAIN_SEPARATOR_STR)) - .color(Color::Muted) - .size(LabelSize::XSmall) - .buffer_font(cx), + Label::new(format!( + "{separator}{}{separator}", + parent.display(path_style) + )) + .color(Color::Muted) + .size(LabelSize::XSmall) + .buffer_font(cx), ) } }); let file_name = path.file_name().map(|name| { - Label::new(name.to_string_lossy().to_string()) + Label::new(name.to_string()) .size(LabelSize::XSmall) .buffer_font(cx) }); - let file_icon = FileIcons::get_icon(path, cx) + let file_icon = FileIcons::get_icon(path.as_std_path(), cx) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) .unwrap_or_else(|| { @@ -4569,7 +4572,7 @@ impl AcpThreadView { .read(cx) .visible_worktrees(cx) .next() - .map(|worktree| worktree.read(cx).root_name().to_string()) + .map(|worktree| worktree.read(cx).root_name_str().to_string()) }); if let Some(screen_window) = cx diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index e34789d62d2c95b06f5c4f03b93b60f01c6dbf6a..b64334f403bed8cfcf86e80e4fe4589ba920b06d 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -264,7 +264,7 @@ pub fn init( init_language_model_settings(cx); } assistant_slash_command::init(cx); - agent::init(cx); + agent::init(fs.clone(), cx); agent_panel::init(cx); context_server_configuration::init(language_registry.clone(), fs.clone(), cx); TextThreadEditor::init(cx); diff --git a/crates/agent_ui/src/context_picker.rs b/crates/agent_ui/src/context_picker.rs index bc309302896eb58c318fa79c108ab9adb5a41ab1..58edecdf3da6b16bca82a7d4c0e73dcac3969e03 100644 --- a/crates/agent_ui/src/context_picker.rs +++ b/crates/agent_ui/src/context_picker.rs @@ -33,6 +33,8 @@ use thread_context_picker::{ use ui::{ ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*, }; +use util::paths::PathStyle; +use util::rel_path::RelPath; use workspace::{Workspace, notifications::NotifyResultExt}; use agent::{ @@ -228,12 +230,19 @@ impl ContextPicker { let context_picker = cx.entity(); let menu = ContextMenu::build(window, cx, move |menu, _window, cx| { + let Some(workspace) = self.workspace.upgrade() else { + return menu; + }; + let path_style = workspace.read(cx).path_style(cx); let recent = self.recent_entries(cx); let has_recent = !recent.is_empty(); let recent_entries = recent .into_iter() .enumerate() - .map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry)); + .map(|(ix, entry)| { + self.recent_menu_item(context_picker.clone(), ix, entry, path_style) + }) + .collect::>(); let entries = self .workspace @@ -395,6 +404,7 @@ impl ContextPicker { context_picker: Entity, ix: usize, entry: RecentEntry, + path_style: PathStyle, ) -> ContextMenuItem { match entry { RecentEntry::File { @@ -413,6 +423,7 @@ impl ContextPicker { &path, &path_prefix, false, + path_style, context_store.clone(), cx, ) @@ -586,7 +597,7 @@ impl Render for ContextPicker { pub(crate) enum RecentEntry { File { project_path: ProjectPath, - path_prefix: Arc, + path_prefix: Arc, }, Thread(ThreadContextEntry), } diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index 01a7a51316eee4709eaf9c17c8840e3cd637a62b..33a5a621a1d1ea23ccdb49fd97010fea1856ce80 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -13,6 +13,7 @@ use http_client::HttpClientWithUrl; use itertools::Itertools; use language::{Buffer, CodeLabel, HighlightId}; use lsp::CompletionContext; +use project::lsp_store::SymbolLocation; use project::{ Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, ProjectPath, Symbol, WorktreeId, @@ -22,6 +23,8 @@ use rope::Point; use text::{Anchor, OffsetRangeExt, ToPoint}; use ui::prelude::*; use util::ResultExt as _; +use util::paths::PathStyle; +use util::rel_path::RelPath; use workspace::Workspace; use agent::{ @@ -574,11 +577,12 @@ impl ContextPickerCompletionProvider { fn completion_for_path( project_path: ProjectPath, - path_prefix: &str, + path_prefix: &RelPath, is_recent: bool, is_directory: bool, excerpt_id: ExcerptId, source_range: Range, + path_style: PathStyle, editor: Entity, context_store: Entity, cx: &App, @@ -586,6 +590,7 @@ impl ContextPickerCompletionProvider { let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory( &project_path.path, path_prefix, + path_style, ); let label = @@ -657,17 +662,22 @@ impl ContextPickerCompletionProvider { workspace: Entity, cx: &mut App, ) -> Option { + let path_style = workspace.read(cx).path_style(cx); + let SymbolLocation::InProject(symbol_path) = &symbol.path else { + return None; + }; let path_prefix = workspace .read(cx) .project() .read(cx) - .worktree_for_id(symbol.path.worktree_id, cx)? + .worktree_for_id(symbol_path.worktree_id, cx)? .read(cx) .root_name(); let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory( - &symbol.path.path, + &symbol_path.path, path_prefix, + path_style, ); let full_path = if let Some(directory) = directory { format!("{}{}", directory, file_name) @@ -768,6 +778,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { let text_thread_store = self.text_thread_store.clone(); let editor = self.editor.clone(); let http_client = workspace.read(cx).client().http_client(); + let path_style = workspace.read(cx).path_style(cx); let MentionCompletion { mode, argument, .. } = state; let query = argument.unwrap_or_else(|| "".to_string()); @@ -834,6 +845,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { mat.is_dir, excerpt_id, source_range.clone(), + path_style, editor.clone(), context_store.clone(), cx, @@ -1064,7 +1076,7 @@ mod tests { use serde_json::json; use settings::SettingsStore; use std::{ops::Deref, rc::Rc}; - use util::path; + use util::{path, rel_path::rel_path}; use workspace::{AppState, Item}; #[test] @@ -1215,16 +1227,18 @@ mod tests { let mut cx = VisualTestContext::from_window(*window.deref(), cx); let paths = vec![ - path!("a/one.txt"), - path!("a/two.txt"), - path!("a/three.txt"), - path!("a/four.txt"), - path!("b/five.txt"), - path!("b/six.txt"), - path!("b/seven.txt"), - path!("b/eight.txt"), + rel_path("a/one.txt"), + rel_path("a/two.txt"), + rel_path("a/three.txt"), + rel_path("a/four.txt"), + rel_path("b/five.txt"), + rel_path("b/six.txt"), + rel_path("b/seven.txt"), + rel_path("b/eight.txt"), ]; + let slash = PathStyle::local().separator(); + let mut opened_editors = Vec::new(); for path in paths { let buffer = workspace @@ -1232,7 +1246,7 @@ mod tests { workspace.open_path( ProjectPath { worktree_id, - path: Path::new(path).into(), + path: path.into(), }, None, false, @@ -1308,13 +1322,13 @@ mod tests { assert_eq!( current_completion_labels(editor), &[ - "seven.txt dir/b/", - "six.txt dir/b/", - "five.txt dir/b/", - "four.txt dir/a/", - "Files & Directories", - "Symbols", - "Fetch" + format!("seven.txt dir{slash}b{slash}"), + format!("six.txt dir{slash}b{slash}"), + format!("five.txt dir{slash}b{slash}"), + format!("four.txt dir{slash}a{slash}"), + "Files & Directories".into(), + "Symbols".into(), + "Fetch".into() ] ); }); @@ -1341,7 +1355,10 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!(editor.text(cx), "Lorem @file one"); assert!(editor.has_visible_completions_menu()); - assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]); + assert_eq!( + current_completion_labels(editor), + vec![format!("one.txt dir{slash}a{slash}")] + ); }); editor.update_in(&mut cx, |editor, window, cx| { @@ -1350,7 +1367,10 @@ mod tests { }); editor.update(&mut cx, |editor, cx| { - assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) "); + assert_eq!( + editor.text(cx), + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ") + ); assert!(!editor.has_visible_completions_menu()); assert_eq!( fold_ranges(editor, cx), @@ -1361,7 +1381,10 @@ mod tests { cx.simulate_input(" "); editor.update(&mut cx, |editor, cx| { - assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) "); + assert_eq!( + editor.text(cx), + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ") + ); assert!(!editor.has_visible_completions_menu()); assert_eq!( fold_ranges(editor, cx), @@ -1374,7 +1397,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!( editor.text(cx), - "Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ", + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum "), ); assert!(!editor.has_visible_completions_menu()); assert_eq!( @@ -1388,7 +1411,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!( editor.text(cx), - "Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ", + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum @file "), ); assert!(editor.has_visible_completions_menu()); assert_eq!( @@ -1406,7 +1429,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!( editor.text(cx), - "Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) " + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) ") ); assert!(!editor.has_visible_completions_menu()); assert_eq!( @@ -1423,7 +1446,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!( editor.text(cx), - "Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@" + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n@") ); assert!(editor.has_visible_completions_menu()); assert_eq!( @@ -1444,7 +1467,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert_eq!( editor.text(cx), - "Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) " + format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n[@six.txt](@file:dir{slash}b{slash}six.txt) ") ); assert!(!editor.has_visible_completions_menu()); assert_eq!( 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 d6f2af7083eb4049e168f6409cef22022cbe404b..4f7a4308406f9d9fbdfa42cc86adc1ffe7593396 100644 --- a/crates/agent_ui/src/context_picker/file_context_picker.rs +++ b/crates/agent_ui/src/context_picker/file_context_picker.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::sync::Arc; use std::sync::atomic::AtomicBool; @@ -10,7 +9,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, ProjectPath, WorktreeId}; use ui::{ListItem, Tooltip, prelude::*}; -use util::ResultExt as _; +use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath}; use workspace::Workspace; use crate::context_picker::ContextPicker; @@ -161,6 +160,8 @@ impl PickerDelegate for FileContextPickerDelegate { cx: &mut Context>, ) -> Option { let FileMatch { mat, .. } = &self.matches.get(ix)?; + let workspace = self.workspace.upgrade()?; + let path_style = workspace.read(cx).path_style(cx); Some( ListItem::new(ix) @@ -172,6 +173,7 @@ impl PickerDelegate for FileContextPickerDelegate { &mat.path, &mat.path_prefix, mat.is_dir, + path_style, self.context_store.clone(), cx, )), @@ -214,14 +216,13 @@ pub(crate) fn search_files( let file_matches = project.worktrees(cx).flat_map(|worktree| { let worktree = worktree.read(cx); - let path_prefix: Arc = worktree.root_name().into(); worktree.entries(false, 0).map(move |entry| FileMatch { mat: PathMatch { score: 0., positions: Vec::new(), worktree_id: worktree.id().to_usize(), path: entry.path.clone(), - path_prefix: path_prefix.clone(), + path_prefix: worktree.root_name().into(), distance_to_relative_ancestor: 0, is_dir: entry.is_dir(), }, @@ -269,51 +270,31 @@ pub(crate) fn search_files( } pub fn extract_file_name_and_directory( - path: &Path, - path_prefix: &str, + path: &RelPath, + path_prefix: &RelPath, + path_style: PathStyle, ) -> (SharedString, Option) { - if path == Path::new("") { - ( - SharedString::from( - path_prefix - .trim_end_matches(std::path::MAIN_SEPARATOR) - .to_string(), - ), - None, - ) - } else { - let file_name = path - .file_name() - .unwrap_or_default() - .to_string_lossy() - .to_string() - .into(); - - let mut directory = path_prefix - .trim_end_matches(std::path::MAIN_SEPARATOR) - .to_string(); - if !directory.ends_with('/') { - directory.push('/'); - } - if let Some(parent) = path.parent().filter(|parent| parent != &Path::new("")) { - directory.push_str(&parent.to_string_lossy()); - directory.push('/'); - } - - (file_name, Some(directory.into())) - } + let full_path = path_prefix.join(path); + let file_name = full_path.file_name().unwrap_or_default(); + let display_path = full_path.display(path_style); + let (directory, file_name) = display_path.split_at(display_path.len() - file_name.len()); + ( + file_name.to_string().into(), + Some(SharedString::new(directory)).filter(|dir| !dir.is_empty()), + ) } pub fn render_file_context_entry( id: ElementId, worktree_id: WorktreeId, - path: &Arc, - path_prefix: &Arc, + path: &Arc, + path_prefix: &Arc, is_directory: bool, + path_style: PathStyle, 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, path_style); let added = context_store.upgrade().and_then(|context_store| { let project_path = ProjectPath { @@ -330,9 +311,9 @@ pub fn render_file_context_entry( }); let file_icon = if is_directory { - FileIcons::get_folder_icon(false, path, cx) + FileIcons::get_folder_icon(false, path.as_std_path(), cx) } else { - FileIcons::get_icon(path, cx) + FileIcons::get_icon(path.as_std_path(), cx) } .map(Icon::from_path) .unwrap_or_else(|| Icon::new(IconName::File)); 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 993d65bd12ee4e01ca8d9767ccd46dd3fd645dd3..5b89f09de884067a94832c7bf474a2949e78c420 100644 --- a/crates/agent_ui/src/context_picker/symbol_context_picker.rs +++ b/crates/agent_ui/src/context_picker/symbol_context_picker.rs @@ -2,13 +2,14 @@ use std::cmp::Reverse; use std::sync::Arc; use std::sync::atomic::AtomicBool; -use anyhow::Result; +use anyhow::{Result, anyhow}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity, }; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; +use project::lsp_store::SymbolLocation; use project::{DocumentSymbol, Symbol}; use ui::{ListItem, prelude::*}; use util::ResultExt as _; @@ -191,7 +192,10 @@ pub(crate) fn add_symbol( ) -> Task, bool)>> { let project = workspace.read(cx).project().clone(); let open_buffer_task = project.update(cx, |project, cx| { - project.open_buffer(symbol.path.clone(), cx) + let SymbolLocation::InProject(symbol_path) = &symbol.path else { + return Task::ready(Err(anyhow!("can't add symbol from outside of project"))); + }; + project.open_buffer(symbol_path.clone(), cx) }); cx.spawn(async move |cx| { let buffer = open_buffer_task.await?; @@ -291,10 +295,11 @@ pub(crate) fn search_symbols( .map(|(id, symbol)| { StringMatchCandidate::new(id, symbol.label.filter_text()) }) - .partition(|candidate| { - project - .entry_for_path(&symbols[candidate.id].path, cx) - .is_some_and(|e| !e.is_ignored) + .partition(|candidate| match &symbols[candidate.id].path { + SymbolLocation::InProject(project_path) => project + .entry_for_path(project_path, cx) + .is_some_and(|e| !e.is_ignored), + SymbolLocation::OutsideProject { .. } => false, }) }) .log_err() @@ -360,13 +365,18 @@ fn compute_symbol_entries( } pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful
{ - let path = entry - .symbol - .path - .path - .file_name() - .map(|s| s.to_string_lossy()) - .unwrap_or_default(); + let path = match &entry.symbol.path { + SymbolLocation::InProject(project_path) => { + project_path.path.file_name().unwrap_or_default().into() + } + SymbolLocation::OutsideProject { + abs_path, + signature: _, + } => abs_path + .file_name() + .map(|f| f.to_string_lossy()) + .unwrap_or_default(), + }; let symbol_location = format!("{} L{}", path, entry.symbol.range.start.0.row + 1); h_flex() diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 3c09e47852ffae8f45a5315859a7bb3392b1680d..e01bfc1d0d0745ed612429bc2dae71967fde36d5 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -1431,10 +1431,14 @@ impl TextThreadEditor { else { continue; }; - let worktree_root_name = worktree.read(cx).root_name().to_string(); - let mut full_path = PathBuf::from(worktree_root_name.clone()); - full_path.push(&project_path.path); - file_slash_command_args.push(full_path.to_string_lossy().to_string()); + let path_style = worktree.read(cx).path_style(); + let full_path = worktree + .read(cx) + .root_name() + .join(&project_path.path) + .display(path_style) + .into_owned(); + file_slash_command_args.push(full_path); } let cmd_name = FileSlashCommand.name(); diff --git a/crates/assistant_slash_command/Cargo.toml b/crates/assistant_slash_command/Cargo.toml index f7b7af9b879492cbb48f4e88d8379b45cbc2d053..0908cd61653d35dbb54ae325118a6091cd345a4e 100644 --- a/crates/assistant_slash_command/Cargo.toml +++ b/crates/assistant_slash_command/Cargo.toml @@ -25,6 +25,7 @@ parking_lot.workspace = true serde.workspace = true serde_json.workspace = true ui.workspace = true +util.workspace = true workspace.workspace = true workspace-hack.workspace = true diff --git a/crates/assistant_slash_command/src/extension_slash_command.rs b/crates/assistant_slash_command/src/extension_slash_command.rs index e47ae52c98740af17c90fe657386bb0120773d9b..301cf65cb45895ff2475b74510264260d827da61 100644 --- a/crates/assistant_slash_command/src/extension_slash_command.rs +++ b/crates/assistant_slash_command/src/extension_slash_command.rs @@ -1,12 +1,11 @@ -use std::path::PathBuf; -use std::sync::{Arc, atomic::AtomicBool}; - use anyhow::Result; use async_trait::async_trait; use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate}; use gpui::{App, Task, WeakEntity, Window}; use language::{BufferSnapshot, LspAdapterDelegate}; +use std::sync::{Arc, atomic::AtomicBool}; use ui::prelude::*; +use util::rel_path::RelPath; use workspace::Workspace; use crate::{ @@ -54,7 +53,7 @@ impl WorktreeDelegate for WorktreeDelegateAdapter { self.0.worktree_root_path().to_string_lossy().to_string() } - async fn read_text_file(&self, path: PathBuf) -> Result { + async fn read_text_file(&self, path: &RelPath) -> Result { self.0.read_text_file(path).await } diff --git a/crates/assistant_slash_commands/Cargo.toml b/crates/assistant_slash_commands/Cargo.toml index f151515d4235b7ecb150539aceb1c5478960517b..5844d21a51b0642a89fd13f29f53a074331ee10e 100644 --- a/crates/assistant_slash_commands/Cargo.toml +++ b/crates/assistant_slash_commands/Cargo.toml @@ -41,6 +41,9 @@ worktree.workspace = true workspace-hack.workspace = true [dev-dependencies] +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true -settings.workspace = true +project = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } zlog.workspace = true diff --git a/crates/assistant_slash_commands/src/cargo_workspace_command.rs b/crates/assistant_slash_commands/src/cargo_workspace_command.rs new file mode 100644 index 0000000000000000000000000000000000000000..8a6950a4a2ff40d0452669dd388886d05d71022a --- /dev/null +++ b/crates/assistant_slash_commands/src/cargo_workspace_command.rs @@ -0,0 +1,159 @@ +use anyhow::{Context as _, Result, anyhow}; +use assistant_slash_command::{ + ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, + SlashCommandResult, +}; +use fs::Fs; +use gpui::{App, Entity, Task, WeakEntity}; +use language::{BufferSnapshot, LspAdapterDelegate}; +use project::{Project, ProjectPath}; +use std::{ + fmt::Write, + path::Path, + sync::{Arc, atomic::AtomicBool}, +}; +use ui::prelude::*; +use util::rel_path::RelPath; +use workspace::Workspace; + +pub struct CargoWorkspaceSlashCommand; + +impl CargoWorkspaceSlashCommand { + async fn build_message(fs: Arc, path_to_cargo_toml: &Path) -> Result { + let buffer = fs.load(path_to_cargo_toml).await?; + let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?; + + let mut message = String::new(); + writeln!(message, "You are in a Rust project.")?; + + if let Some(workspace) = cargo_toml.workspace { + writeln!( + message, + "The project is a Cargo workspace with the following members:" + )?; + for member in workspace.members { + writeln!(message, "- {member}")?; + } + + if !workspace.default_members.is_empty() { + writeln!(message, "The default members are:")?; + for member in workspace.default_members { + writeln!(message, "- {member}")?; + } + } + + if !workspace.dependencies.is_empty() { + writeln!( + message, + "The following workspace dependencies are installed:" + )?; + for dependency in workspace.dependencies.keys() { + writeln!(message, "- {dependency}")?; + } + } + } else if let Some(package) = cargo_toml.package { + writeln!( + message, + "The project name is \"{name}\".", + name = package.name + )?; + + let description = package + .description + .as_ref() + .and_then(|description| description.get().ok().cloned()); + if let Some(description) = description.as_ref() { + writeln!(message, "It describes itself as \"{description}\".")?; + } + + if !cargo_toml.dependencies.is_empty() { + writeln!(message, "The following dependencies are installed:")?; + for dependency in cargo_toml.dependencies.keys() { + writeln!(message, "- {dependency}")?; + } + } + } + + Ok(message) + } + + fn path_to_cargo_toml(project: Entity, cx: &mut App) -> Option> { + let worktree = project.read(cx).worktrees(cx).next()?; + let worktree = worktree.read(cx); + let entry = worktree.entry_for_path(RelPath::new("Cargo.toml").unwrap())?; + let path = ProjectPath { + worktree_id: worktree.id(), + path: entry.path.clone(), + }; + Some(Arc::from( + project.read(cx).absolute_path(&path, cx)?.as_path(), + )) + } +} + +impl SlashCommand for CargoWorkspaceSlashCommand { + fn name(&self) -> String { + "cargo-workspace".into() + } + + fn description(&self) -> String { + "insert project workspace metadata".into() + } + + fn menu_text(&self) -> String { + "Insert Project Workspace Metadata".into() + } + + fn complete_argument( + self: Arc, + _arguments: &[String], + _cancel: Arc, + _workspace: Option>, + _window: &mut Window, + _cx: &mut App, + ) -> Task>> { + Task::ready(Err(anyhow!("this command does not require argument"))) + } + + fn requires_argument(&self) -> bool { + false + } + + fn run( + self: Arc, + _arguments: &[String], + _context_slash_command_output_sections: &[SlashCommandOutputSection], + _context_buffer: BufferSnapshot, + workspace: WeakEntity, + _delegate: Option>, + _window: &mut Window, + cx: &mut App, + ) -> Task { + let output = workspace.update(cx, |workspace, cx| { + let project = workspace.project().clone(); + let fs = workspace.project().read(cx).fs().clone(); + let path = Self::path_to_cargo_toml(project, cx); + let output = cx.background_spawn(async move { + let path = path.with_context(|| "Cargo.toml not found")?; + Self::build_message(fs, &path).await + }); + + cx.foreground_executor().spawn(async move { + let text = output.await?; + let range = 0..text.len(); + Ok(SlashCommandOutput { + text, + sections: vec![SlashCommandOutputSection { + range, + icon: IconName::FileTree, + label: "Project".into(), + metadata: None, + }], + run_commands_in_text: false, + } + .into_event_stream()) + }) + }); + output.unwrap_or_else(|error| Task::ready(Err(error))) + } +} diff --git a/crates/assistant_slash_commands/src/diagnostics_command.rs b/crates/assistant_slash_commands/src/diagnostics_command.rs index dd54565c2abc168bb995325f2ebf930bbde90793..77048b1108577689ade4a5e149e54e9784ba0d33 100644 --- a/crates/assistant_slash_commands/src/diagnostics_command.rs +++ b/crates/assistant_slash_commands/src/diagnostics_command.rs @@ -13,12 +13,12 @@ use project::{DiagnosticSummary, PathMatchCandidateSet, Project}; use rope::Point; use std::{ fmt::Write, - path::{Path, PathBuf}, + path::Path, sync::{Arc, atomic::AtomicBool}, }; use ui::prelude::*; -use util::ResultExt; -use util::paths::PathMatcher; +use util::paths::{PathMatcher, PathStyle}; +use util::{ResultExt, rel_path::RelPath}; use workspace::Workspace; use crate::create_label_for_command; @@ -36,7 +36,7 @@ impl DiagnosticsSlashCommand { if query.is_empty() { let workspace = workspace.read(cx); let entries = workspace.recent_navigation_history(Some(10), cx); - let path_prefix: Arc = Arc::default(); + let path_prefix: Arc = RelPath::empty().into(); Task::ready( entries .into_iter() @@ -125,6 +125,7 @@ impl SlashCommand for DiagnosticsSlashCommand { let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { return Task::ready(Err(anyhow!("workspace was dropped"))); }; + let path_style = workspace.read(cx).project().read(cx).path_style(cx); let query = arguments.last().cloned().unwrap_or_default(); let paths = self.search_paths(query.clone(), cancellation_flag.clone(), &workspace, cx); @@ -134,11 +135,11 @@ impl SlashCommand for DiagnosticsSlashCommand { .await .into_iter() .map(|path_match| { - format!( - "{}{}", - path_match.path_prefix, - path_match.path.to_string_lossy() - ) + path_match + .path_prefix + .join(&path_match.path) + .display(path_style) + .to_string() }) .collect(); @@ -183,9 +184,11 @@ impl SlashCommand for DiagnosticsSlashCommand { return Task::ready(Err(anyhow!("workspace was dropped"))); }; - let options = Options::parse(arguments); + let project = workspace.read(cx).project(); + let path_style = project.read(cx).path_style(cx); + let options = Options::parse(arguments, path_style); - let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx); + let task = collect_diagnostics(project.clone(), options, cx); window.spawn(cx, async move |_| { task.await? @@ -204,14 +207,14 @@ struct Options { const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings"; impl Options { - fn parse(arguments: &[String]) -> Self { + fn parse(arguments: &[String], path_style: PathStyle) -> Self { let mut include_warnings = false; let mut path_matcher = None; for arg in arguments { if arg == INCLUDE_WARNINGS_ARGUMENT { include_warnings = true; } else { - path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err(); + path_matcher = PathMatcher::new(&[arg.to_owned()], path_style).log_err(); } } Self { @@ -237,21 +240,15 @@ fn collect_diagnostics( None }; + let path_style = project.read(cx).path_style(cx); let glob_is_exact_file_match = if let Some(path) = options .path_matcher .as_ref() .and_then(|pm| pm.sources().first()) { - PathBuf::try_from(path) - .ok() - .and_then(|path| { - project.read(cx).worktrees(cx).find_map(|worktree| { - 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() - }) - }) + project + .read(cx) + .find_project_path(Path::new(path), cx) .is_some() } else { false @@ -263,9 +260,8 @@ fn collect_diagnostics( .diagnostic_summaries(false, cx) .flat_map(|(path, _, summary)| { let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?; - let mut path_buf = PathBuf::from(worktree.read(cx).root_name()); - path_buf.push(&path.path); - Some((path, path_buf, summary)) + let full_path = worktree.read(cx).root_name().join(&path.path); + Some((path, full_path, summary)) }) .collect(); @@ -281,7 +277,7 @@ 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 - && !path_matcher.is_match(&path) + && !path_matcher.is_match(&path.as_std_path()) { continue; } @@ -294,7 +290,7 @@ fn collect_diagnostics( } let last_end = output.text.len(); - let file_path = path.to_string_lossy().to_string(); + let file_path = path.display(path_style).to_string(); if !glob_is_exact_file_match { writeln!(&mut output.text, "{file_path}").unwrap(); } diff --git a/crates/assistant_slash_commands/src/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs index 4bf53bad9b5364c7fd488cf74644701c6f176b99..afb3d942fefe13b679cf9fbe7b711dfe32eb0195 100644 --- a/crates/assistant_slash_commands/src/file_command.rs +++ b/crates/assistant_slash_commands/src/file_command.rs @@ -14,11 +14,11 @@ use smol::stream::StreamExt; use std::{ fmt::Write, ops::{Range, RangeInclusive}, - path::{Path, PathBuf}, + path::Path, sync::{Arc, atomic::AtomicBool}, }; use ui::prelude::*; -use util::ResultExt; +use util::{ResultExt, rel_path::RelPath}; use workspace::Workspace; use worktree::ChildEntriesOptions; @@ -48,7 +48,7 @@ impl FileSlashCommand { include_dirs: true, include_ignored: false, }; - let entries = worktree.child_entries_with_options(Path::new(""), options); + let entries = worktree.child_entries_with_options(RelPath::empty(), options); entries.map(move |entry| { ( project::ProjectPath { @@ -61,19 +61,18 @@ impl FileSlashCommand { })) .collect::>(); - let path_prefix: Arc = Arc::default(); + let path_prefix: Arc = RelPath::empty().into(); Task::ready( entries .into_iter() .filter_map(|(entry, is_dir)| { let worktree = project.worktree_for_id(entry.worktree_id, cx)?; - let mut full_path = PathBuf::from(worktree.read(cx).root_name()); - full_path.push(&entry.path); + let full_path = worktree.read(cx).root_name().join(&entry.path); Some(PathMatch { score: 0., positions: Vec::new(), worktree_id: entry.worktree_id.to_usize(), - path: full_path.into(), + path: full_path, path_prefix: path_prefix.clone(), distance_to_relative_ancestor: 0, is_dir, @@ -149,6 +148,8 @@ impl SlashCommand for FileSlashCommand { return Task::ready(Err(anyhow!("workspace was dropped"))); }; + let path_style = workspace.read(cx).path_style(cx); + let paths = self.search_paths( arguments.last().cloned().unwrap_or_default(), cancellation_flag, @@ -161,14 +162,14 @@ impl SlashCommand for FileSlashCommand { .await .into_iter() .filter_map(|path_match| { - let text = format!( - "{}{}", - path_match.path_prefix, - path_match.path.to_string_lossy() - ); + let text = path_match + .path_prefix + .join(&path_match.path) + .display(path_style) + .to_string(); let mut label = CodeLabel::default(); - let file_name = path_match.path.file_name()?.to_string_lossy(); + let file_name = path_match.path.file_name()?; let label_text = if path_match.is_dir { format!("{}/ ", file_name) } else { @@ -247,14 +248,13 @@ fn collect_files( cx.spawn(async move |cx| { for snapshot in snapshots { let worktree_id = snapshot.id(); - let mut directory_stack: Vec> = Vec::new(); - let mut folded_directory_names_stack = Vec::new(); + let path_style = snapshot.path_style(); + let mut directory_stack: Vec> = Vec::new(); + let mut folded_directory_names: Arc = RelPath::empty().into(); let mut is_top_level_directory = true; for entry in snapshot.entries(false, 0) { - let mut path_including_worktree_name = PathBuf::new(); - path_including_worktree_name.push(snapshot.root_name()); - path_including_worktree_name.push(&entry.path); + let path_including_worktree_name = snapshot.root_name().join(&entry.path); if !matchers .iter() @@ -277,13 +277,7 @@ fn collect_files( )))?; } - let filename = entry - .path - .file_name() - .unwrap_or_default() - .to_str() - .unwrap_or_default() - .to_string(); + let filename = entry.path.file_name().unwrap_or_default().to_string(); if entry.is_dir() { // Auto-fold directories that contain no files @@ -292,24 +286,23 @@ fn collect_files( if child_entries.next().is_none() && child.kind.is_dir() { if is_top_level_directory { is_top_level_directory = false; - folded_directory_names_stack.push( - path_including_worktree_name.to_string_lossy().to_string(), - ); + folded_directory_names = + folded_directory_names.join(&path_including_worktree_name); } else { - folded_directory_names_stack.push(filename.to_string()); + folded_directory_names = + folded_directory_names.join(RelPath::new(&filename).unwrap()); } continue; } } else { // Skip empty directories - folded_directory_names_stack.clear(); + folded_directory_names = RelPath::empty().into(); continue; } - let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/"); - if prefix_paths.is_empty() { + if folded_directory_names.is_empty() { let label = if is_top_level_directory { is_top_level_directory = false; - path_including_worktree_name.to_string_lossy().to_string() + path_including_worktree_name.display(path_style).to_string() } else { filename }; @@ -320,28 +313,23 @@ fn collect_files( }))?; events_tx.unbounded_send(Ok(SlashCommandEvent::Content( SlashCommandContent::Text { - text: label, + text: label.to_string(), run_commands_in_text: false, }, )))?; directory_stack.push(entry.path.clone()); } else { - // todo(windows) - // Potential bug: this assumes that the path separator is always `\` on Windows - let entry_name = format!( - "{}{}{}", - prefix_paths, - std::path::MAIN_SEPARATOR_STR, - &filename - ); + let entry_name = + folded_directory_names.join(RelPath::new(&filename).unwrap()); + let entry_name = entry_name.display(path_style); events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { icon: IconName::Folder, - label: entry_name.clone().into(), + label: entry_name.to_string().into(), metadata: None, }))?; events_tx.unbounded_send(Ok(SlashCommandEvent::Content( SlashCommandContent::Text { - text: entry_name, + text: entry_name.to_string(), run_commands_in_text: false, }, )))?; @@ -356,7 +344,7 @@ fn collect_files( } else if entry.is_file() { let Some(open_buffer_task) = project_handle .update(cx, |project, cx| { - project.open_buffer((worktree_id, &entry.path), cx) + project.open_buffer((worktree_id, entry.path.clone()), cx) }) .ok() else { @@ -367,7 +355,9 @@ fn collect_files( let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?; append_buffer_to_output( &snapshot, - Some(&path_including_worktree_name), + Some(Path::new( + path_including_worktree_name.display(path_style).as_ref(), + )), &mut output, ) .log_err(); @@ -462,10 +452,9 @@ pub fn build_entry_output_section( /// This contains a small fork of the util::paths::PathMatcher, that is stricter about the prefix /// check. Only subpaths pass the prefix check, rather than any prefix. mod custom_path_matcher { - use std::{fmt::Debug as _, path::Path}; - use globset::{Glob, GlobSet, GlobSetBuilder}; - use util::paths::SanitizedPath; + use std::fmt::Debug as _; + use util::{paths::SanitizedPath, rel_path::RelPath}; #[derive(Clone, Debug, Default)] pub struct PathMatcher { @@ -492,12 +481,12 @@ mod custom_path_matcher { pub fn new(globs: &[String]) -> Result { let globs = globs .iter() - .map(|glob| Glob::new(&SanitizedPath::new(glob).to_glob_string())) + .map(|glob| Glob::new(&SanitizedPath::new(glob).to_string())) .collect::, _>>()?; let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect(); let sources_with_trailing_slash = globs .iter() - .map(|glob| glob.glob().to_string() + std::path::MAIN_SEPARATOR_STR) + .map(|glob| glob.glob().to_string() + "/") .collect(); let mut glob_builder = GlobSetBuilder::new(); for single_glob in globs { @@ -511,16 +500,13 @@ mod custom_path_matcher { }) } - pub fn is_match>(&self, other: P) -> bool { - let other_path = other.as_ref(); + pub fn is_match(&self, other: &RelPath) -> bool { self.sources .iter() .zip(self.sources_with_trailing_slash.iter()) .any(|(source, with_slash)| { - let as_bytes = other_path.as_os_str().as_encoded_bytes(); - // todo(windows) - // Potential bug: this assumes that the path separator is always `\` on Windows - let with_slash = if source.ends_with(std::path::MAIN_SEPARATOR_STR) { + let as_bytes = other.as_str().as_bytes(); + let with_slash = if source.ends_with('/') { source.as_bytes() } else { with_slash.as_bytes() @@ -528,13 +514,13 @@ mod custom_path_matcher { as_bytes.starts_with(with_slash) || as_bytes.ends_with(source.as_bytes()) }) - || self.glob.is_match(other_path) - || self.check_with_end_separator(other_path) + || self.glob.is_match(other) + || self.check_with_end_separator(other) } - fn check_with_end_separator(&self, path: &Path) -> bool { - let path_str = path.to_string_lossy(); - let separator = std::path::MAIN_SEPARATOR_STR; + fn check_with_end_separator(&self, path: &RelPath) -> bool { + let path_str = path.as_str(); + let separator = "/"; if path_str.ends_with(separator) { false } else { diff --git a/crates/assistant_tools/src/copy_path_tool.rs b/crates/assistant_tools/src/copy_path_tool.rs index c56a864bd45efd83d605607962f6103f8da7d1da..572eddcb1079557b464ba29d125aa44929409cc5 100644 --- a/crates/assistant_tools/src/copy_path_tool.rs +++ b/crates/assistant_tools/src/copy_path_tool.rs @@ -96,9 +96,7 @@ impl Tool for CopyPathTool { .and_then(|project_path| project.entry_for_path(&project_path, cx)) { Some(entity) => match project.find_project_path(&input.destination_path, cx) { - Some(project_path) => { - project.copy_entry(entity.id, None, project_path.path, cx) - } + Some(project_path) => project.copy_entry(entity.id, project_path, cx), None => Task::ready(Err(anyhow!( "Destination path {} was outside the project.", input.destination_path diff --git a/crates/assistant_tools/src/diagnostics_tool.rs b/crates/assistant_tools/src/diagnostics_tool.rs index 4ec794e12783746e4e330e79f0c0cb14c84f5d2e..75bd683512b58d2fdb6c43fc319d266f6609f926 100644 --- a/crates/assistant_tools/src/diagnostics_tool.rs +++ b/crates/assistant_tools/src/diagnostics_tool.rs @@ -8,7 +8,7 @@ use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchem use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::{fmt::Write, path::Path, sync::Arc}; +use std::{fmt::Write, sync::Arc}; use ui::IconName; use util::markdown::MarkdownInlineCode; @@ -150,9 +150,7 @@ impl Tool for DiagnosticsTool { has_diagnostics = true; output.push_str(&format!( "{}: {} error(s), {} warning(s)\n", - Path::new(worktree.read(cx).root_name()) - .join(project_path.path) - .display(), + worktree.read(cx).absolutize(&project_path.path).display(), summary.error_count, summary.warning_count )); diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index 1fcd7bbf14fb2e37646902102d51392bc8a470f8..f7ed5e28bf8cf8e4d1452620c7c13732ad28ff8f 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -38,6 +38,7 @@ use settings::Settings; use std::{ cmp::Reverse, collections::HashSet, + ffi::OsStr, ops::Range, path::{Path, PathBuf}, sync::Arc, @@ -45,7 +46,7 @@ use std::{ }; use theme::ThemeSettings; use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*}; -use util::ResultExt; +use util::{ResultExt, rel_path::RelPath}; use workspace::Workspace; pub struct EditFileTool; @@ -146,11 +147,11 @@ impl Tool for EditFileTool { // If any path component matches the local settings folder, then this could affect // the editor in ways beyond the project source, so prompt. - let local_settings_folder = paths::local_settings_folder_relative_path(); + let local_settings_folder = paths::local_settings_folder_name(); let path = Path::new(&input.path); if path .components() - .any(|component| component.as_os_str() == local_settings_folder.as_os_str()) + .any(|c| c.as_os_str() == >::as_ref(local_settings_folder)) { return true; } @@ -195,10 +196,10 @@ impl Tool for EditFileTool { let mut description = input.display_description.clone(); // Add context about why confirmation may be needed - let local_settings_folder = paths::local_settings_folder_relative_path(); + let local_settings_folder = paths::local_settings_folder_name(); if path .components() - .any(|c| c.as_os_str() == local_settings_folder.as_os_str()) + .any(|c| c.as_os_str() == >::as_ref(local_settings_folder)) { description.push_str(" (local settings)"); } else if let Ok(canonical_path) = std::fs::canonicalize(&input.path) @@ -377,7 +378,7 @@ impl Tool for EditFileTool { .await; let output = EditFileToolOutput { - original_path: project_path.path.to_path_buf(), + original_path: project_path.path.as_std_path().to_owned(), new_text, old_text, raw_output: Some(agent_output), @@ -549,10 +550,11 @@ fn resolve_path( let file_name = input .path .file_name() + .and_then(|file_name| file_name.to_str()) .context("Can't create file: invalid filename")?; let new_file_path = parent_project_path.map(|parent| ProjectPath { - path: Arc::from(parent.path.join(file_name)), + path: parent.path.join(RelPath::new(file_name).unwrap()), ..parent }); @@ -1236,7 +1238,7 @@ mod tests { use serde_json::json; use settings::SettingsStore; use std::fs; - use util::path; + use util::{path, rel_path::rel_path}; #[gpui::test] async fn test_edit_nonexistent_file(cx: &mut TestAppContext) { @@ -1355,14 +1357,10 @@ mod tests { cx.update(|cx| resolve_path(&input, project, cx)) } + #[track_caller] fn assert_resolved_path_eq(path: anyhow::Result, expected: &str) { - let actual = path - .expect("Should return valid path") - .path - .to_str() - .unwrap() - .replace("\\", "/"); // Naive Windows paths normalization - assert_eq!(actual, expected); + let actual = path.expect("Should return valid path").path; + assert_eq!(actual.as_ref(), rel_path(expected)); } #[test] @@ -1976,25 +1974,22 @@ mod tests { let project = Project::test(fs.clone(), [path!("/home/user/myproject").as_ref()], cx).await; // Get the actual local settings folder name - let local_settings_folder = paths::local_settings_folder_relative_path(); + let local_settings_folder = paths::local_settings_folder_name(); // Test various config path patterns let test_cases = vec![ ( - format!("{}/settings.json", local_settings_folder.display()), + format!("{local_settings_folder}/settings.json"), true, "Top-level local settings file".to_string(), ), ( - format!( - "myproject/{}/settings.json", - local_settings_folder.display() - ), + format!("myproject/{local_settings_folder}/settings.json"), true, "Local settings in project path".to_string(), ), ( - format!("src/{}/config.toml", local_settings_folder.display()), + format!("src/{local_settings_folder}/config.toml"), true, "Local settings in subdirectory".to_string(), ), @@ -2205,12 +2200,7 @@ mod tests { ("", false, "Empty path is treated as project root"), // Root directory ("/", true, "Root directory should be outside project"), - // Parent directory references - find_project_path resolves these - ( - "project/../other", - false, - "Path with .. is resolved by find_project_path", - ), + ("project/../other", true, "Path with .. is outside project"), ( "project/./src/file.rs", false, diff --git a/crates/assistant_tools/src/find_path_tool.rs b/crates/assistant_tools/src/find_path_tool.rs index d1451132aeb066a5d4ff9e05f81db3855c1d513a..53da3106d2f9fa5fd7928b4291cc8e80daa3bfdb 100644 --- a/crates/assistant_tools/src/find_path_tool.rs +++ b/crates/assistant_tools/src/find_path_tool.rs @@ -161,10 +161,13 @@ impl Tool for FindPathTool { } fn search_paths(glob: &str, project: Entity, cx: &mut App) -> Task>> { - let path_matcher = match PathMatcher::new([ - // Sometimes models try to search for "". In this case, return all paths in the project. - if glob.is_empty() { "*" } else { glob }, - ]) { + let path_matcher = match PathMatcher::new( + [ + // Sometimes models try to search for "". In this case, return all paths in the project. + if glob.is_empty() { "*" } else { glob }, + ], + project.read(cx).path_style(cx), + ) { Ok(matcher) => matcher, Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))), }; @@ -178,10 +181,15 @@ fn search_paths(glob: &str, project: Entity, cx: &mut App) -> Task>(), + project.read(cx).path_style(cx), ) { Ok(matcher) => matcher, Err(error) => { @@ -141,7 +142,7 @@ impl Tool for GrepTool { .iter() .chain(global_settings.private_files.sources().iter()); - match PathMatcher::new(exclude_patterns) { + match PathMatcher::new(exclude_patterns, project.read(cx).path_style(cx)) { Ok(matcher) => matcher, Err(error) => { return Task::ready(Err(anyhow!("invalid exclude pattern: {error}"))).into(); diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 9303a50468c428ddd4e603c69d75030dc860e876..d46ac3ac0dc6bd1210472cad13acadd4ce209def 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -4,11 +4,11 @@ use anyhow::{Result, anyhow}; use assistant_tool::{Tool, ToolResult}; use gpui::{AnyWindowHandle, App, Entity, Task}; use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat}; -use project::{Project, WorktreeSettings}; +use project::{Project, ProjectPath, WorktreeSettings}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; -use std::{fmt::Write, path::Path, sync::Arc}; +use std::{fmt::Write, sync::Arc}; use ui::IconName; use util::markdown::MarkdownInlineCode; @@ -100,7 +100,7 @@ impl Tool for ListDirectoryTool { .filter_map(|worktree| { worktree.read(cx).root_entry().and_then(|entry| { if entry.is_dir() { - entry.path.to_str() + Some(entry.path.as_str()) } else { None } @@ -158,7 +158,6 @@ impl Tool for ListDirectoryTool { } let worktree_snapshot = worktree.read(cx).snapshot(); - let worktree_root_name = worktree.read(cx).root_name().to_string(); let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else { return Task::ready(Err(anyhow!("Path not found: {}", input.path))).into(); @@ -180,23 +179,22 @@ impl Tool for ListDirectoryTool { continue; } - if project - .read(cx) - .find_project_path(&entry.path, cx) - .map(|project_path| { - let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx); + let project_path = ProjectPath { + worktree_id: worktree_snapshot.id(), + path: entry.path.clone(), + }; + 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) + if worktree_settings.is_path_excluded(&project_path.path) + || worktree_settings.is_path_private(&project_path.path) { continue; } - let full_path = Path::new(&worktree_root_name) + let full_path = worktree_snapshot + .root_name() .join(&entry.path) - .display() + .display(worktree_snapshot.path_style()) .to_string(); if entry.is_dir() { folders.push(full_path); diff --git a/crates/assistant_tools/src/move_path_tool.rs b/crates/assistant_tools/src/move_path_tool.rs index 2c065488cea62a73e04c34a659961abc7b94ba54..22dbe9e625468d8c2688b60bdcd94a7da594730e 100644 --- a/crates/assistant_tools/src/move_path_tool.rs +++ b/crates/assistant_tools/src/move_path_tool.rs @@ -108,7 +108,7 @@ impl Tool for MovePathTool { .and_then(|project_path| project.entry_for_path(&project_path, cx)) { Some(entity) => match project.find_project_path(&input.destination_path, cx) { - Some(project_path) => project.rename_entry(entity.id, project_path.path, cx), + Some(project_path) => project.rename_entry(entity.id, project_path, cx), None => Task::ready(Err(anyhow!( "Destination path {} was outside the project.", input.destination_path diff --git a/crates/call/src/call_impl/room.rs b/crates/call/src/call_impl/room.rs index 930846ab8ff37272f9b0fc0652319318c676f3f7..0d44a82c577a841b988808c72bffcff01583b2c2 100644 --- a/crates/call/src/call_impl/room.rs +++ b/crates/call/src/call_impl/room.rs @@ -24,7 +24,7 @@ use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use settings::Settings as _; use std::{future::Future, mem, rc::Rc, sync::Arc, time::Duration}; -use util::{ResultExt, TryFutureExt, post_inc}; +use util::{ResultExt, TryFutureExt, paths::PathStyle, post_inc}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -1163,6 +1163,7 @@ impl Room { room_id: self.id(), worktrees: project.read(cx).worktree_metadata_protos(cx), is_ssh_project: project.read(cx).is_via_remote_server(), + windows_paths: Some(project.read(cx).path_style(cx) == PathStyle::Windows), }); cx.spawn(async move |this, cx| { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7ab289a0ecdbab0909b90f2ef289af3c5d4a61b8..5d43b4543c2e28b07953567f3eb54d19e8b218b5 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -405,7 +405,7 @@ impl Telemetry { let mut project_types: HashSet<&str> = HashSet::new(); for (path, _, _) in updated_entries_set.iter() { - let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else { + let Some(file_name) = path.file_name() else { continue; }; @@ -601,6 +601,7 @@ mod tests { use http_client::FakeHttpClient; use std::collections::HashMap; use telemetry_events::FlexibleEvent; + use util::rel_path::RelPath; use worktree::{PathChange, ProjectEntryId, WorktreeId}; #[gpui::test] @@ -855,12 +856,12 @@ mod tests { let entries: Vec<_> = file_paths .into_iter() .enumerate() - .map(|(i, path)| { - ( - Arc::from(std::path::Path::new(path)), + .filter_map(|(i, path)| { + Some(( + Arc::from(RelPath::new(path).ok()?), ProjectEntryId::from_proto(i as u64 + 1), PathChange::Added, - ) + )) }) .collect(); let updated_entries: UpdatedEntriesSet = Arc::from(entries.as_slice()); diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index b2e25458ef98b295b4d056a7f59521f4fa896f1a..d498ecd50a0b88a3a83c7e35a962136e7da74aa5 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -61,7 +61,8 @@ CREATE TABLE "projects" ( "host_user_id" INTEGER REFERENCES users (id), "host_connection_id" INTEGER, "host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE, - "unregistered" BOOLEAN NOT NULL DEFAULT FALSE + "unregistered" BOOLEAN NOT NULL DEFAULT FALSE, + "windows_paths" BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id"); diff --git a/crates/collab/migrations/20250916173002_add_path_style_to_project.sql b/crates/collab/migrations/20250916173002_add_path_style_to_project.sql new file mode 100644 index 0000000000000000000000000000000000000000..b1244818f14403d38af577be4b14b1a8a765e07b --- /dev/null +++ b/crates/collab/migrations/20250916173002_add_path_style_to_project.sql @@ -0,0 +1 @@ +ALTER TABLE projects ADD COLUMN windows_paths BOOLEAN DEFAULT FALSE; diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 6ec57ce95e1863d973624f57947b28fffec042b1..1152cb97d79ef2c7df437479d79b28a5ca6d2ef7 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -34,6 +34,7 @@ use std::{ }; use time::PrimitiveDateTime; use tokio::sync::{Mutex, OwnedMutexGuard}; +use util::paths::PathStyle; use worktree_settings_file::LocalSettingsKind; #[cfg(test)] @@ -598,6 +599,7 @@ pub struct Project { pub worktrees: BTreeMap, pub repositories: Vec, pub language_servers: Vec, + pub path_style: PathStyle, } pub struct ProjectCollaborator { diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index d83f6de206b414f00ea8f176672aeb41641f289a..8014cd3cab27b472dc5a2fab9e896fb75ff226a2 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -33,6 +33,7 @@ impl Database { connection: ConnectionId, worktrees: &[proto::WorktreeMetadata], is_ssh_project: bool, + windows_paths: bool, ) -> Result> { self.room_transaction(room_id, |tx| async move { let participant = room_participant::Entity::find() @@ -69,6 +70,7 @@ impl Database { connection.owner_id as i32, ))), id: ActiveValue::NotSet, + windows_paths: ActiveValue::set(windows_paths), } .insert(&*tx) .await?; @@ -1046,6 +1048,12 @@ impl Database { .all(tx) .await?; + let path_style = if project.windows_paths { + PathStyle::Windows + } else { + PathStyle::Posix + }; + let project = Project { id: project.id, role, @@ -1073,6 +1081,7 @@ impl Database { capabilities: language_server.capabilities, }) .collect(), + path_style, }; Ok((project, replica_id as ReplicaId)) } diff --git a/crates/collab/src/db/tables/project.rs b/crates/collab/src/db/tables/project.rs index 8a7fea55243b6fdd0352a1fca1cfa4891fbed7fc..11a9b972ebcd7af29d6e6c234096384ce9ff7701 100644 --- a/crates/collab/src/db/tables/project.rs +++ b/crates/collab/src/db/tables/project.rs @@ -12,6 +12,7 @@ pub struct Model { pub host_user_id: Option, pub host_connection_id: Option, pub host_connection_server_id: Option, + pub windows_paths: bool, } impl Model { diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 07ab1e62500c8dcddc0ceaeac5cdc9992a6294f8..def0769c373605021653b07a97cbff2ec807d34d 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -558,18 +558,18 @@ async fn test_project_count(db: &Arc) { .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0); - db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false) + db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false) .await .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1); - db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false) + db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false) .await .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2); // Projects shared by admins aren't counted. - db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false) + db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false) .await .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 49f97eb11d4b4dd44591ca500668828e30013c03..fa2ca6a890af93979eed759265286d99a5a98bb2 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -36,6 +36,7 @@ use reqwest_client::ReqwestClient; use rpc::proto::split_repository_update; use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi}; use tracing::Span; +use util::paths::PathStyle; use futures::{ FutureExt, SinkExt, StreamExt, TryStreamExt, channel::oneshot, future::BoxFuture, @@ -1879,6 +1880,7 @@ async fn share_project( session.connection_id, &request.worktrees, request.is_ssh_project, + request.windows_paths.unwrap_or(false), ) .await?; response.send(proto::ShareProjectResponse { @@ -2012,6 +2014,7 @@ async fn join_project( language_servers, language_server_capabilities, role: project.role.into(), + windows_paths: project.path_style == PathStyle::Windows, })?; for (worktree_id, worktree) in mem::take(&mut project.worktrees) { diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 0b331ff1e66279f5e2f5e52f9d83f0eaca6cfcdb..a2c7e97c63e6ecd4936b7ff1e839518ad66f936e 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -13,6 +13,7 @@ use gpui::{BackgroundExecutor, Context, Entity, TestAppContext, Window}; use rpc::{RECEIVE_TIMEOUT, proto::PeerId}; use serde_json::json; use std::ops::Range; +use util::rel_path::rel_path; use workspace::CollaboratorId; #[gpui::test] @@ -256,7 +257,13 @@ async fn test_channel_notes_participant_indices( executor.start_waiting(); let editor_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx) + workspace.open_path( + (worktree_id_a, rel_path("file.txt")), + None, + true, + window, + cx, + ) }) .await .unwrap() @@ -265,7 +272,13 @@ async fn test_channel_notes_participant_indices( executor.start_waiting(); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx) + workspace.open_path( + (worktree_id_a, rel_path("file.txt")), + None, + true, + window, + cx, + ) }) .await .unwrap() diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index f5051ba876438b6872b3826c1c75258bde990ed8..604dec3194fc8917e66606d4e7b770df337615aa 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -4,6 +4,7 @@ use chrono::Utc; use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext}; use rpc::proto; +use util::rel_path::rel_path; #[gpui::test] async fn test_channel_guests( @@ -55,7 +56,7 @@ async fn test_channel_guests( project_b .update(cx_b, |project, cx| { let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id(); - project.create_entry((worktree_id, "b.txt"), false, cx) + project.create_entry((worktree_id, rel_path("b.txt")), false, cx) }) .await .is_err() diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 2169b7123263305e45f11403796f8d7c395c2cdf..947af01224fdc55284dc3a8166f652a9782d5e5f 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -16,6 +16,7 @@ use editor::{ }; use fs::Fs; use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex}; +use git::repository::repo_path; use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext}; use indoc::indoc; use language::FakeLspAdapter; @@ -38,7 +39,7 @@ use std::{ }, }; use text::Point; -use util::{path, uri}; +use util::{path, rel_path::rel_path, uri}; use workspace::{CloseIntent, Workspace}; #[gpui::test(iterations = 10)] @@ -97,7 +98,7 @@ async fn test_host_disconnect( let editor_b = workspace_b .update(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "b.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("b.txt")), None, true, window, cx) }) .unwrap() .await @@ -205,7 +206,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( // Open a buffer as client A let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); let cx_a = cx_a.add_empty_window(); @@ -222,7 +225,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( let cx_b = cx_b.add_empty_window(); // Open a buffer as client B let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); let editor_b = cx_b @@ -334,7 +339,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Open a file in an editor as the guest. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("main.rs")), cx) + }) .await .unwrap(); let cx_b = cx_b.add_empty_window(); @@ -408,7 +415,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Open the buffer on the host. let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("main.rs")), cx) + }) .await .unwrap(); cx_a.executor().run_until_parked(); @@ -599,7 +608,7 @@ async fn test_collaborating_with_code_actions( let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -825,7 +834,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "one.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx) }) .await .unwrap() @@ -1072,7 +1081,7 @@ async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "one.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx) }) .await .unwrap() @@ -1412,7 +1421,10 @@ async fn test_share_project( project_b .update(cx_b, |project, cx| { let worktree = project.worktrees(cx).next().unwrap(); - let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap(); + let entry = worktree + .read(cx) + .entry_for_path(rel_path("ignored-dir")) + .unwrap(); project.expand_entry(worktree_id, entry.id, cx).unwrap() }) .await @@ -1435,17 +1447,21 @@ async fn test_share_project( // Open the same file as client B and client A. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("b.txt")), cx) + }) .await .unwrap(); buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents")); project_a.read_with(cx_a, |project, cx| { - assert!(project.has_open_buffer((worktree_id, "b.txt"), cx)) + assert!(project.has_open_buffer((worktree_id, rel_path("b.txt")), cx)) }); let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("b.txt")), cx) + }) .await .unwrap(); @@ -1553,7 +1569,9 @@ async fn test_on_input_format_from_host_to_guest( // Open a file in an editor as the host. let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("main.rs")), cx) + }) .await .unwrap(); let cx_a = cx_a.add_empty_window(); @@ -1586,7 +1604,9 @@ async fn test_on_input_format_from_host_to_guest( // Open the buffer on the guest and see that the formatting worked let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("main.rs")), cx) + }) .await .unwrap(); @@ -1686,7 +1706,9 @@ async fn test_on_input_format_from_guest_to_host( // Open a file in an editor as the guest. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("main.rs")), cx) + }) .await .unwrap(); let cx_b = cx_b.add_empty_window(); @@ -1732,7 +1754,9 @@ async fn test_on_input_format_from_guest_to_host( // Open the buffer on the host and see that the formatting worked let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("main.rs")), cx) + }) .await .unwrap(); executor.run_until_parked(); @@ -1881,7 +1905,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( .unwrap(); let editor_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -1931,7 +1955,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -2126,7 +2150,7 @@ async fn test_inlay_hint_refresh_is_forwarded( let editor_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -2135,7 +2159,7 @@ async fn test_inlay_hint_refresh_is_forwarded( let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -2313,7 +2337,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo .unwrap(); let editor_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -2373,7 +2397,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -2594,7 +2618,7 @@ async fn test_lsp_pull_diagnostics( .unwrap(); let editor_a_main = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -2953,7 +2977,7 @@ async fn test_lsp_pull_diagnostics( let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b_main = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -3001,7 +3025,7 @@ async fn test_lsp_pull_diagnostics( let editor_b_lib = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "lib.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx) }) .await .unwrap() @@ -3355,7 +3379,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA }; client_a.fs().set_blame_for_repo( Path::new(path!("/my-repo/.git")), - vec![("file.txt".into(), blame)], + vec![(repo_path("file.txt"), blame)], ); let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await; @@ -3368,7 +3392,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let editor_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "file.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx) }) .await .unwrap() @@ -3380,7 +3404,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "file.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx) }) .await .unwrap() @@ -3558,13 +3582,13 @@ async fn test_collaborating_with_editorconfig( .unwrap(); let main_buffer_a = project_a .update(cx_a, |p, cx| { - p.open_buffer((worktree_id, "src/main.rs"), cx) + p.open_buffer((worktree_id, rel_path("src/main.rs")), cx) }) .await .unwrap(); let other_buffer_a = project_a .update(cx_a, |p, cx| { - p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx) + p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx) }) .await .unwrap(); @@ -3592,13 +3616,13 @@ async fn test_collaborating_with_editorconfig( let project_b = client_b.join_remote_project(project_id, cx_b).await; let main_buffer_b = project_b .update(cx_b, |p, cx| { - p.open_buffer((worktree_id, "src/main.rs"), cx) + p.open_buffer((worktree_id, rel_path("src/main.rs")), cx) }) .await .unwrap(); let other_buffer_b = project_b .update(cx_b, |p, cx| { - p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx) + p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx) }) .await .unwrap(); @@ -3717,7 +3741,7 @@ fn main() { let foo = other::foo(); }"}; let editorconfig_buffer_b = project_b .update(cx_b, |p, cx| { - p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx) + p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx) }) .await .unwrap(); @@ -3794,7 +3818,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_path = ProjectPath { worktree_id, - path: Arc::from(Path::new(&"test.txt")), + path: rel_path(&"test.txt").into(), }; let abs_path = project_a.read_with(cx_a, |project, cx| { project @@ -4017,7 +4041,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes let editor_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() @@ -4026,7 +4050,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes let editor_b = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .await .unwrap() diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index e6bab12934d9c262cbba378e0d2a94143e0a7602..1bf0a28e341cf1e74dd12d962f875eb18f4474db 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -16,7 +16,7 @@ use rpc::proto::PeerId; use serde_json::json; use settings::SettingsStore; use text::{Point, ToPoint}; -use util::{path, test::sample_text}; +use util::{path, rel_path::rel_path, test::sample_text}; use workspace::{CollaboratorId, SplitDirection, Workspace, item::ItemHandle as _}; use super::TestClient; @@ -86,7 +86,7 @@ async fn test_basic_following( let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone()); let editor_a1 = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap() @@ -94,7 +94,7 @@ async fn test_basic_following( .unwrap(); let editor_a2 = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "2.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx) }) .await .unwrap() @@ -104,7 +104,7 @@ async fn test_basic_following( // Client B opens an editor. let editor_b1 = workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap() @@ -146,7 +146,7 @@ async fn test_basic_following( }); assert_eq!( cx_b.read(|cx| editor_b2.project_path(cx)), - Some((worktree_id, "2.txt").into()) + Some((worktree_id, rel_path("2.txt")).into()) ); assert_eq!( editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)), @@ -286,12 +286,12 @@ async fn test_basic_following( let multibuffer_a = cx_a.new(|cx| { let buffer_a1 = project_a.update(cx, |project, cx| { project - .get_open_buffer(&(worktree_id, "1.txt").into(), cx) + .get_open_buffer(&(worktree_id, rel_path("1.txt")).into(), cx) .unwrap() }); let buffer_a2 = project_a.update(cx, |project, cx| { project - .get_open_buffer(&(worktree_id, "2.txt").into(), cx) + .get_open_buffer(&(worktree_id, rel_path("2.txt")).into(), cx) .unwrap() }); let mut result = MultiBuffer::new(Capability::ReadWrite); @@ -618,13 +618,13 @@ async fn test_following_tab_order( //Open 1, 3 in that order on client A workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap(); workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "3.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("3.txt")), None, true, window, cx) }) .await .unwrap(); @@ -632,14 +632,7 @@ async fn test_following_tab_order( let pane_paths = |pane: &Entity, cx: &mut VisualTestContext| { pane.update(cx, |pane, cx| { pane.items() - .map(|item| { - item.project_path(cx) - .unwrap() - .path - .to_str() - .unwrap() - .to_owned() - }) + .map(|item| item.project_path(cx).unwrap().path.as_str().to_owned()) .collect::>() }) }; @@ -656,7 +649,7 @@ async fn test_following_tab_order( //Open just 2 on client B workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "2.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx) }) .await .unwrap(); @@ -668,7 +661,7 @@ async fn test_following_tab_order( //Open just 1 on client B workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap(); @@ -728,7 +721,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap() @@ -739,7 +732,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "2.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx) }) .await .unwrap() @@ -816,14 +809,14 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T // Clients A and B each open a new file. workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "3.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("3.txt")), None, true, window, cx) }) .await .unwrap(); workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "4.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("4.txt")), None, true, window, cx) }) .await .unwrap(); @@ -1259,7 +1252,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont let _editor_a1 = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap() @@ -1359,7 +1352,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont // When client B activates a different item in the original pane, it automatically stops following client A. workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id, "2.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx) }) .await .unwrap(); @@ -1492,7 +1485,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id_a, "w.rs"), None, true, window, cx) + workspace.open_path((worktree_id_a, rel_path("w.rs")), None, true, window, cx) }) .await .unwrap(); @@ -1545,7 +1538,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut // b moves to x.rs in a's project, and a follows workspace_b_project_a .update_in(&mut cx_b2, |workspace, window, cx| { - workspace.open_path((worktree_id_a, "x.rs"), None, true, window, cx) + workspace.open_path((worktree_id_a, rel_path("x.rs")), None, true, window, cx) }) .await .unwrap(); @@ -1574,7 +1567,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut // b moves to y.rs in b's project, a is still following but can't yet see workspace_b .update_in(cx_b, |workspace, window, cx| { - workspace.open_path((worktree_id_b, "y.rs"), None, true, window, cx) + workspace.open_path((worktree_id_b, rel_path("y.rs")), None, true, window, cx) }) .await .unwrap(); @@ -1759,7 +1752,7 @@ async fn test_following_into_excluded_file( // Client A opens editors for a regular file and an excluded file. let editor_for_regular = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap() @@ -1767,7 +1760,13 @@ async fn test_following_into_excluded_file( .unwrap(); let editor_for_excluded_a = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, window, cx) + workspace.open_path( + (worktree_id, rel_path(".git/COMMIT_EDITMSG")), + None, + true, + window, + cx, + ) }) .await .unwrap() @@ -1805,7 +1804,7 @@ async fn test_following_into_excluded_file( }); assert_eq!( cx_b.read(|cx| editor_for_excluded_b.project_path(cx)), - Some((worktree_id, ".git/COMMIT_EDITMSG").into()) + Some((worktree_id, rel_path(".git/COMMIT_EDITMSG")).into()) ); assert_eq!( editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)), @@ -2051,7 +2050,7 @@ async fn test_following_to_channel_notes_without_a_shared_project( // Client A opens a local buffer in their unshared project. let _unshared_editor_a1 = workspace_a .update_in(cx_a, |workspace, window, cx| { - workspace.open_path((worktree_id, "1.txt"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx) }) .await .unwrap() diff --git a/crates/collab/src/tests/git_tests.rs b/crates/collab/src/tests/git_tests.rs index 3f78bd11585a1746d1309491a58e2ef49258a8fd..ef9f871a2252845e5154e456e258b4f4ab4bf057 100644 --- a/crates/collab/src/tests/git_tests.rs +++ b/crates/collab/src/tests/git_tests.rs @@ -1,7 +1,4 @@ -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; +use std::path::Path; use call::ActiveCall; use git::status::{FileStatus, StatusCode, TrackedStatus}; @@ -9,7 +6,7 @@ use git_ui::project_diff::ProjectDiff; use gpui::{TestAppContext, VisualTestContext}; use project::ProjectPath; use serde_json::json; -use util::path; +use util::{path, rel_path::rel_path}; use workspace::Workspace; // @@ -41,13 +38,13 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) ) .await; - client_a.fs().set_git_content_for_repo( + client_a.fs().set_head_and_index_for_repo( Path::new(path!("/a/.git")), &[ - ("changed.txt".into(), "before\n".to_string(), None), - ("unchanged.txt".into(), "unchanged\n".to_string(), None), - ("deleted.txt".into(), "deleted\n".to_string(), None), - ("secret.pem".into(), "shh\n".to_string(), None), + ("changed.txt", "before\n".to_string()), + ("unchanged.txt", "unchanged\n".to_string()), + ("deleted.txt", "deleted\n".to_string()), + ("secret.pem", "shh\n".to_string()), ], ); let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await; @@ -109,7 +106,7 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) project_b.update(cx_b, |project, cx| { let project_path = ProjectPath { worktree_id, - path: Arc::from(PathBuf::from("unchanged.txt")), + path: rel_path("unchanged.txt").into(), }; let status = project.project_path_git_status(&project_path, cx); assert_eq!( diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index de255830cd154e4180afd32538f13df200bc36d5..93c3665c11edebcb371dc8f1b819f84c00b2dda8 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -14,7 +14,10 @@ use client::{RECEIVE_TIMEOUT, User}; use collections::{HashMap, HashSet}; use fs::{FakeFs, Fs as _, RemoveOptions}; use futures::{StreamExt as _, channel::mpsc}; -use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode}; +use git::{ + repository::repo_path, + status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode}, +}; use gpui::{ App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent, TestAppContext, UpdateGlobal, px, size, @@ -30,7 +33,7 @@ use parking_lot::Mutex; use pretty_assertions::assert_eq; use project::{ DiagnosticSummary, HoverBlockKind, Project, ProjectPath, - lsp_store::{FormatTrigger, LspFormatTarget}, + lsp_store::{FormatTrigger, LspFormatTarget, SymbolLocation}, search::{SearchQuery, SearchResult}, }; use prompt_store::PromptBuilder; @@ -49,7 +52,7 @@ use std::{ time::Duration, }; use unindent::Unindent as _; -use util::{path, uri}; +use util::{path, rel_path::rel_path, uri}; use workspace::Pane; #[ctor::ctor] @@ -1418,7 +1421,9 @@ async fn test_unshare_project( assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); @@ -1454,7 +1459,9 @@ async fn test_unshare_project( assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); project_c2 - .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_c, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); @@ -1584,11 +1591,15 @@ async fn test_project_reconnect( }); let buffer_a1 = project_a1 - .update(cx_a, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree1_id, rel_path("a.txt")), cx) + }) .await .unwrap(); let buffer_b1 = project_b1 - .update(cx_b, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree1_id, rel_path("a.txt")), cx) + }) .await .unwrap(); @@ -1675,20 +1686,15 @@ async fn test_project_reconnect( assert!(project.is_shared()); assert!(worktree_a1.read(cx).has_update_observer()); assert_eq!( - worktree_a1 - .read(cx) - .snapshot() - .paths() - .map(|p| p.to_str().unwrap()) - .collect::>(), + worktree_a1.read(cx).snapshot().paths().collect::>(), vec![ - path!("a.txt"), - path!("b.txt"), - path!("subdir2"), - path!("subdir2/f.txt"), - path!("subdir2/g.txt"), - path!("subdir2/h.txt"), - path!("subdir2/i.txt") + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("subdir2"), + rel_path("subdir2/f.txt"), + rel_path("subdir2/g.txt"), + rel_path("subdir2/h.txt"), + rel_path("subdir2/i.txt") ] ); assert!(worktree_a3.read(cx).has_update_observer()); @@ -1697,7 +1703,7 @@ async fn test_project_reconnect( .read(cx) .snapshot() .paths() - .map(|p| p.to_str().unwrap()) + .map(|p| p.as_str()) .collect::>(), vec!["w.txt", "x.txt", "y.txt"] ); @@ -1712,16 +1718,15 @@ async fn test_project_reconnect( .read(cx) .snapshot() .paths() - .map(|p| p.to_str().unwrap()) .collect::>(), vec![ - path!("a.txt"), - path!("b.txt"), - path!("subdir2"), - path!("subdir2/f.txt"), - path!("subdir2/g.txt"), - path!("subdir2/h.txt"), - path!("subdir2/i.txt") + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("subdir2"), + rel_path("subdir2/f.txt"), + rel_path("subdir2/g.txt"), + rel_path("subdir2/h.txt"), + rel_path("subdir2/i.txt") ] ); assert!(project.worktree_for_id(worktree2_id, cx).is_none()); @@ -1732,7 +1737,7 @@ async fn test_project_reconnect( .read(cx) .snapshot() .paths() - .map(|p| p.to_str().unwrap()) + .map(|p| p.as_str()) .collect::>(), vec!["w.txt", "x.txt", "y.txt"] ); @@ -1809,16 +1814,15 @@ async fn test_project_reconnect( .read(cx) .snapshot() .paths() - .map(|p| p.to_str().unwrap()) .collect::>(), vec![ - path!("a.txt"), - path!("b.txt"), - path!("subdir2"), - path!("subdir2/f.txt"), - path!("subdir2/g.txt"), - path!("subdir2/h.txt"), - path!("subdir2/j.txt") + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("subdir2"), + rel_path("subdir2/f.txt"), + rel_path("subdir2/g.txt"), + rel_path("subdir2/h.txt"), + rel_path("subdir2/j.txt") ] ); assert!(project.worktree_for_id(worktree2_id, cx).is_none()); @@ -1829,7 +1833,7 @@ async fn test_project_reconnect( .read(cx) .snapshot() .paths() - .map(|p| p.to_str().unwrap()) + .map(|p| p.as_str()) .collect::>(), vec!["z.txt"] ); @@ -2370,11 +2374,15 @@ async fn test_propagate_saves_and_fs_changes( // Open and edit a buffer as both guests B and C. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("file1.rs")), cx) + }) .await .unwrap(); let buffer_c = project_c - .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx)) + .update(cx_c, |p, cx| { + p.open_buffer((worktree_id, rel_path("file1.rs")), cx) + }) .await .unwrap(); @@ -2390,7 +2398,9 @@ async fn test_propagate_saves_and_fs_changes( // Open and edit that buffer as the host. let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("file1.rs")), cx) + }) .await .unwrap(); @@ -2461,27 +2471,21 @@ async fn test_propagate_saves_and_fs_changes( worktree_a.read_with(cx_a, |tree, _| { assert_eq!( - tree.paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + tree.paths().map(|p| p.as_str()).collect::>(), ["file1.js", "file3", "file4"] ) }); worktree_b.read_with(cx_b, |tree, _| { assert_eq!( - tree.paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + tree.paths().map(|p| p.as_str()).collect::>(), ["file1.js", "file3", "file4"] ) }); worktree_c.read_with(cx_c, |tree, _| { assert_eq!( - tree.paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + tree.paths().map(|p| p.as_str()).collect::>(), ["file1.js", "file3", "file4"] ) }); @@ -2489,17 +2493,17 @@ async fn test_propagate_saves_and_fs_changes( // Ensure buffer files are updated as well. buffer_a.read_with(cx_a, |buffer, _| { - assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); + assert_eq!(buffer.file().unwrap().path().as_str(), "file1.js"); assert_eq!(buffer.language().unwrap().name(), "JavaScript".into()); }); buffer_b.read_with(cx_b, |buffer, _| { - assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); + assert_eq!(buffer.file().unwrap().path().as_str(), "file1.js"); assert_eq!(buffer.language().unwrap().name(), "JavaScript".into()); }); buffer_c.read_with(cx_c, |buffer, _| { - assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); + assert_eq!(buffer.file().unwrap().path().as_str(), "file1.js"); assert_eq!(buffer.language().unwrap().name(), "JavaScript".into()); }); @@ -2524,7 +2528,7 @@ async fn test_propagate_saves_and_fs_changes( project_a .update(cx_a, |project, cx| { let path = ProjectPath { - path: Arc::from(Path::new("file3.rs")), + path: rel_path("file3.rs").into(), worktree_id: worktree_a.read(cx).id(), }; @@ -2538,7 +2542,7 @@ async fn test_propagate_saves_and_fs_changes( new_buffer_b.read_with(cx_b, |buffer_b, _| { assert_eq!( buffer_b.file().unwrap().path().as_ref(), - Path::new("file3.rs") + rel_path("file3.rs") ); new_buffer_a.read_with(cx_a, |buffer_a, _| { @@ -2621,19 +2625,20 @@ async fn test_git_diff_base_change( " .unindent(); - client_a.fs().set_index_for_repo( - Path::new("/dir/.git"), - &[("a.txt".into(), staged_text.clone())], - ); + client_a + .fs() + .set_index_for_repo(Path::new("/dir/.git"), &[("a.txt", staged_text.clone())]); client_a.fs().set_head_for_repo( Path::new("/dir/.git"), - &[("a.txt".into(), committed_text.clone())], + &[("a.txt", committed_text.clone())], "deadbeef", ); // Create the buffer let buffer_local_a = project_local - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); let local_unstaged_diff_a = project_local @@ -2661,7 +2666,9 @@ async fn test_git_diff_base_change( // Create remote buffer let remote_buffer_a = project_remote - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); let remote_unstaged_diff_a = project_remote @@ -2717,11 +2724,11 @@ async fn test_git_diff_base_change( // Update the index text of the open buffer client_a.fs().set_index_for_repo( Path::new("/dir/.git"), - &[("a.txt".into(), new_staged_text.clone())], + &[("a.txt", new_staged_text.clone())], ); client_a.fs().set_head_for_repo( Path::new("/dir/.git"), - &[("a.txt".into(), new_committed_text.clone())], + &[("a.txt", new_committed_text.clone())], "deadbeef", ); @@ -2790,12 +2797,14 @@ async fn test_git_diff_base_change( client_a.fs().set_index_for_repo( Path::new("/dir/sub/.git"), - &[("b.txt".into(), staged_text.clone())], + &[("b.txt", staged_text.clone())], ); // Create the buffer let buffer_local_b = project_local - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("sub/b.txt")), cx) + }) .await .unwrap(); let local_unstaged_diff_b = project_local @@ -2823,7 +2832,9 @@ async fn test_git_diff_base_change( // Create remote buffer let remote_buffer_b = project_remote - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("sub/b.txt")), cx) + }) .await .unwrap(); let remote_unstaged_diff_b = project_remote @@ -2851,7 +2862,7 @@ async fn test_git_diff_base_change( // Updatet the staged text client_a.fs().set_index_for_repo( Path::new("/dir/sub/.git"), - &[("b.txt".into(), new_staged_text.clone())], + &[("b.txt", new_staged_text.clone())], ); // Wait for buffer_local_b to receive it @@ -3011,21 +3022,21 @@ async fn test_git_status_sync( // and b.txt is unmerged. client_a.fs().set_head_for_repo( path!("/dir/.git").as_ref(), - &[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())], + &[("b.txt", "B".into()), ("c.txt", "c".into())], "deadbeef", ); client_a.fs().set_index_for_repo( path!("/dir/.git").as_ref(), &[ - ("a.txt".into(), "".into()), - ("b.txt".into(), "B".into()), - ("c.txt".into(), "c".into()), + ("a.txt", "".into()), + ("b.txt", "B".into()), + ("c.txt", "c".into()), ], ); client_a.fs().set_unmerged_paths_for_repo( path!("/dir/.git").as_ref(), &[( - "b.txt".into(), + repo_path("b.txt"), UnmergedStatus { first_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Deleted, @@ -3056,13 +3067,8 @@ async fn test_git_status_sync( executor.run_until_parked(); #[track_caller] - fn assert_status( - file: impl AsRef, - status: Option, - project: &Project, - cx: &App, - ) { - let file = file.as_ref(); + fn assert_status(file: &str, status: Option, project: &Project, cx: &App) { + let file = repo_path(file); let repos = project .repositories(cx) .values() @@ -3072,7 +3078,7 @@ async fn test_git_status_sync( let repo = repos.into_iter().next().unwrap(); assert_eq!( repo.read(cx) - .status_for_path(&file.into()) + .status_for_path(&file) .map(|entry| entry.status), status ); @@ -3107,7 +3113,7 @@ async fn test_git_status_sync( // and modify c.txt in the working copy. client_a.fs().set_index_for_repo( path!("/dir/.git").as_ref(), - &[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())], + &[("a.txt", "a".into()), ("c.txt", "c".into())], ); client_a .fs() @@ -3202,7 +3208,7 @@ async fn test_fs_operations( let entry = project_b .update(cx_b, |project, cx| { - project.create_entry((worktree_id, "c.txt"), false, cx) + project.create_entry((worktree_id, rel_path("c.txt")), false, cx) }) .await .unwrap() @@ -3211,27 +3217,21 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "c.txt"] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "c.txt"] ); }); project_b .update(cx_b, |project, cx| { - project.rename_entry(entry.id, Path::new("d.txt"), cx) + project.rename_entry(entry.id, (worktree_id, rel_path("d.txt")).into(), cx) }) .await .unwrap() @@ -3240,27 +3240,21 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "d.txt"] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "d.txt"] ); }); let dir_entry = project_b .update(cx_b, |project, cx| { - project.create_entry((worktree_id, "DIR"), true, cx) + project.create_entry((worktree_id, rel_path("DIR")), true, cx) }) .await .unwrap() @@ -3269,27 +3263,21 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["DIR", "a.txt", "b.txt", "d.txt"] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["DIR", "a.txt", "b.txt", "d.txt"] ); }); project_b .update(cx_b, |project, cx| { - project.create_entry((worktree_id, "DIR/e.txt"), false, cx) + project.create_entry((worktree_id, rel_path("DIR/e.txt")), false, cx) }) .await .unwrap() @@ -3298,7 +3286,7 @@ async fn test_fs_operations( project_b .update(cx_b, |project, cx| { - project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx) + project.create_entry((worktree_id, rel_path("DIR/SUBDIR")), true, cx) }) .await .unwrap() @@ -3307,7 +3295,7 @@ async fn test_fs_operations( project_b .update(cx_b, |project, cx| { - project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx) + project.create_entry((worktree_id, rel_path("DIR/SUBDIR/f.txt")), false, cx) }) .await .unwrap() @@ -3316,43 +3304,41 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().collect::>(), [ - path!("DIR"), - path!("DIR/SUBDIR"), - path!("DIR/SUBDIR/f.txt"), - path!("DIR/e.txt"), - path!("a.txt"), - path!("b.txt"), - path!("d.txt") + rel_path("DIR"), + rel_path("DIR/SUBDIR"), + rel_path("DIR/SUBDIR/f.txt"), + rel_path("DIR/e.txt"), + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("d.txt") ] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().collect::>(), [ - path!("DIR"), - path!("DIR/SUBDIR"), - path!("DIR/SUBDIR/f.txt"), - path!("DIR/e.txt"), - path!("a.txt"), - path!("b.txt"), - path!("d.txt") + rel_path("DIR"), + rel_path("DIR/SUBDIR"), + rel_path("DIR/SUBDIR/f.txt"), + rel_path("DIR/e.txt"), + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("d.txt") ] ); }); project_b .update(cx_b, |project, cx| { - project.copy_entry(entry.id, None, Path::new("f.txt"), cx) + project.copy_entry( + entry.id, + (worktree_b.read(cx).id(), rel_path("f.txt")).into(), + cx, + ) }) .await .unwrap() @@ -3360,38 +3346,32 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().collect::>(), [ - path!("DIR"), - path!("DIR/SUBDIR"), - path!("DIR/SUBDIR/f.txt"), - path!("DIR/e.txt"), - path!("a.txt"), - path!("b.txt"), - path!("d.txt"), - path!("f.txt") + rel_path("DIR"), + rel_path("DIR/SUBDIR"), + rel_path("DIR/SUBDIR/f.txt"), + rel_path("DIR/e.txt"), + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("d.txt"), + rel_path("f.txt") ] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().collect::>(), [ - path!("DIR"), - path!("DIR/SUBDIR"), - path!("DIR/SUBDIR/f.txt"), - path!("DIR/e.txt"), - path!("a.txt"), - path!("b.txt"), - path!("d.txt"), - path!("f.txt") + rel_path("DIR"), + rel_path("DIR/SUBDIR"), + rel_path("DIR/SUBDIR/f.txt"), + rel_path("DIR/e.txt"), + rel_path("a.txt"), + rel_path("b.txt"), + rel_path("d.txt"), + rel_path("f.txt") ] ); }); @@ -3406,20 +3386,14 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "d.txt", "f.txt"] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "d.txt", "f.txt"] ); }); @@ -3433,20 +3407,14 @@ async fn test_fs_operations( worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "f.txt"] ); }); worktree_b.read_with(cx_b, |worktree, _| { assert_eq!( - worktree - .paths() - .map(|p| p.to_string_lossy()) - .collect::>(), + worktree.paths().map(|p| p.as_str()).collect::>(), ["a.txt", "b.txt", "f.txt"] ); }); @@ -3511,8 +3479,8 @@ async fn test_local_settings( )) .collect::>(), &[ - (Path::new("").into(), Some(2)), - (Path::new("a").into(), Some(8)), + (rel_path("").into(), Some(2)), + (rel_path("a").into(), Some(8)), ] ) }); @@ -3533,10 +3501,7 @@ async fn test_local_settings( content.all_languages.defaults.tab_size.map(Into::into) )) .collect::>(), - &[ - (Path::new("").into(), None), - (Path::new("a").into(), Some(8)), - ] + &[(rel_path("").into(), None), (rel_path("a").into(), Some(8)),] ) }); @@ -3567,8 +3532,8 @@ async fn test_local_settings( )) .collect::>(), &[ - (Path::new("a").into(), Some(8)), - (Path::new("b").into(), Some(4)), + (rel_path("a").into(), Some(8)), + (rel_path("b").into(), Some(4)), ] ) }); @@ -3599,7 +3564,7 @@ async fn test_local_settings( .local_settings(worktree_b.read(cx).id()) .map(|(path, content)| (path, content.all_languages.defaults.hard_tabs)) .collect::>(), - &[(Path::new("a").into(), Some(true))], + &[(rel_path("a").into(), Some(true))], ) }); } @@ -3636,7 +3601,9 @@ async fn test_buffer_conflict_after_save( // Open a buffer as client B let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); @@ -3700,7 +3667,9 @@ async fn test_buffer_reloading( // Open a buffer as client B let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); @@ -3758,12 +3727,16 @@ async fn test_editing_while_guest_opens_buffer( // Open a buffer as client A let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); // Start opening the same buffer as client B - let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)); + let open_buffer = project_b.update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }); let buffer_b = cx_b.executor().spawn(open_buffer); // Edit the buffer as client A while client B is still opening it. @@ -3810,7 +3783,9 @@ async fn test_leaving_worktree_while_opening_buffer( project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1)); // Begin opening a buffer as client B, but leave the project before the open completes. - let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)); + let open_buffer = project_b.update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }); let buffer_b = cx_b.executor().spawn(open_buffer); cx_b.update(|_| drop(project_b)); drop(buffer_b); @@ -3852,7 +3827,9 @@ async fn test_canceling_buffer_opening( let project_b = client_b.join_remote_project(project_id, cx_b).await; let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.txt")), cx) + }) .await .unwrap(); @@ -3928,7 +3905,7 @@ async fn test_leaving_project( let buffer_b1 = project_b1 .update(cx_b, |project, cx| { let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id(); - project.open_buffer((worktree_id, "a.txt"), cx) + project.open_buffer((worktree_id, rel_path("a.txt")), cx) }) .await .unwrap(); @@ -3966,7 +3943,7 @@ async fn test_leaving_project( let buffer_b2 = project_b2 .update(cx_b, |project, cx| { let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id(); - project.open_buffer((worktree_id, "a.txt"), cx) + project.open_buffer((worktree_id, rel_path("a.txt")), cx) }) .await .unwrap(); @@ -4131,7 +4108,7 @@ async fn test_collaborating_with_diagnostics( &[( ProjectPath { worktree_id, - path: Arc::from(Path::new("a.rs")), + path: rel_path("a.rs").into(), }, LanguageServerId(0), DiagnosticSummary { @@ -4167,7 +4144,7 @@ async fn test_collaborating_with_diagnostics( &[( ProjectPath { worktree_id, - path: Arc::from(Path::new("a.rs")), + path: rel_path("a.rs").into(), }, LanguageServerId(0), DiagnosticSummary { @@ -4208,7 +4185,7 @@ async fn test_collaborating_with_diagnostics( [( ProjectPath { worktree_id, - path: Arc::from(Path::new("a.rs")), + path: rel_path("a.rs").into(), }, LanguageServerId(0), DiagnosticSummary { @@ -4225,7 +4202,7 @@ async fn test_collaborating_with_diagnostics( [( ProjectPath { worktree_id, - path: Arc::from(Path::new("a.rs")), + path: rel_path("a.rs").into(), }, LanguageServerId(0), DiagnosticSummary { @@ -4237,7 +4214,9 @@ async fn test_collaborating_with_diagnostics( }); // Open the file with the errors on client B. They should be present. - let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); + let open_buffer = project_b.update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.rs")), cx) + }); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); buffer_b.read_with(cx_b, |buffer, _| { @@ -4356,7 +4335,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering( let project_b = client_b.join_remote_project(project_id, cx_b).await; let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| { project_b.update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, file_name), cx) + p.open_buffer_with_lsp((worktree_id, rel_path(file_name)), cx) }) })) .await @@ -4454,7 +4433,9 @@ async fn test_reloading_buffer_manually( .await; let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await; let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.rs")), cx) + }) .await .unwrap(); let project_id = active_call_a @@ -4464,7 +4445,9 @@ async fn test_reloading_buffer_manually( let project_b = client_b.join_remote_project(project_id, cx_b).await; - let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); + let open_buffer = project_b.update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.rs")), cx) + }); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); buffer_b.update(cx_b, |buffer, cx| { buffer.edit([(4..7, "six")], None, cx); @@ -4562,7 +4545,9 @@ async fn test_formatting_buffer( let project_b = client_b.join_remote_project(project_id, cx_b).await; let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.rs")), cx) + }) .await .unwrap(); @@ -4688,7 +4673,9 @@ async fn test_prettier_formatting_buffer( .await; let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX; - let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx)); + let open_buffer = project_a.update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.ts")), cx) + }); let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap(); let project_id = active_call_a @@ -4698,7 +4685,7 @@ async fn test_prettier_formatting_buffer( let project_b = client_b.join_remote_project(project_id, cx_b).await; let (buffer_b, _) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "a.ts"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("a.ts")), cx) }) .await .unwrap(); @@ -4838,7 +4825,7 @@ async fn test_definition( // Open the file on client B. let (buffer_b, _handle) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "a.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx) }) .await .unwrap(); @@ -5016,7 +5003,7 @@ async fn test_references( // Open the file on client B. let (buffer_b, _handle) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "one.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("one.rs")), cx) }) .await .unwrap(); @@ -5088,7 +5075,7 @@ async fn test_references( let three_buffer = references[2].buffer.read(cx); assert_eq!( two_buffer.file().unwrap().path().as_ref(), - Path::new("two.rs") + rel_path("two.rs") ); assert_eq!(references[1].buffer, references[0].buffer); assert_eq!( @@ -5288,7 +5275,7 @@ async fn test_document_highlights( // Open the file on client B. let (buffer_b, _handle) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "main.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -5431,7 +5418,7 @@ async fn test_lsp_hover( // Open the file as the guest let (buffer_b, _handle) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "main.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -5623,7 +5610,7 @@ async fn test_project_symbols( // Cause the language server to start. let _buffer = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "one.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("one.rs")), cx) }) .await .unwrap(); @@ -5673,7 +5660,10 @@ async fn test_project_symbols( // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file. let mut fake_symbol = symbols[0].clone(); - fake_symbol.path.path = Path::new(path!("/code/secrets")).into(); + fake_symbol.path = SymbolLocation::OutsideProject { + abs_path: Path::new(path!("/code/secrets")).into(), + signature: [0x17; 32], + }; let error = project_b .update(cx_b, |project, cx| { project.open_buffer_for_symbol(&fake_symbol, cx) @@ -5738,7 +5728,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( let (buffer_b1, _lsp) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "a.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx) }) .await .unwrap(); @@ -5763,14 +5753,14 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx)); (buffer_b2, _) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "b.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("b.rs")), cx) }) .await .unwrap(); } else { (buffer_b2, _) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "b.rs"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("b.rs")), cx) }) .await .unwrap(); @@ -6587,15 +6577,15 @@ async fn test_preview_tabs(cx: &mut TestAppContext) { let path_1 = ProjectPath { worktree_id, - path: Path::new("1.txt").into(), + path: rel_path("1.txt").into(), }; let path_2 = ProjectPath { worktree_id, - path: Path::new("2.js").into(), + path: rel_path("2.js").into(), }; let path_3 = ProjectPath { worktree_id, - path: Path::new("3.rs").into(), + path: rel_path("3.rs").into(), }; let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 326f64cb244b88a64728f4347e3cfc31a8c252bf..0cc4a4eadea213dc4f40d14be2cdf379915686c7 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -27,7 +27,11 @@ use std::{ rc::Rc, sync::Arc, }; -use util::{ResultExt, path}; +use util::{ + ResultExt, path, + paths::PathStyle, + rel_path::{RelPath, RelPathBuf, rel_path}, +}; #[gpui::test( iterations = 100, @@ -66,7 +70,7 @@ enum ClientOperation { OpenBuffer { project_root_name: String, is_local: bool, - full_path: PathBuf, + full_path: RelPathBuf, }, SearchProject { project_root_name: String, @@ -77,24 +81,24 @@ enum ClientOperation { EditBuffer { project_root_name: String, is_local: bool, - full_path: PathBuf, + full_path: RelPathBuf, edits: Vec<(Range, Arc)>, }, CloseBuffer { project_root_name: String, is_local: bool, - full_path: PathBuf, + full_path: RelPathBuf, }, SaveBuffer { project_root_name: String, is_local: bool, - full_path: PathBuf, + full_path: RelPathBuf, detach: bool, }, RequestLspDataInBuffer { project_root_name: String, is_local: bool, - full_path: PathBuf, + full_path: RelPathBuf, offset: usize, kind: LspRequestKind, detach: bool, @@ -102,7 +106,7 @@ enum ClientOperation { CreateWorktreeEntry { project_root_name: String, is_local: bool, - full_path: PathBuf, + full_path: RelPathBuf, is_dir: bool, }, WriteFsEntry { @@ -119,7 +123,7 @@ enum ClientOperation { enum GitOperation { WriteGitIndex { repo_path: PathBuf, - contents: Vec<(PathBuf, String)>, + contents: Vec<(RelPathBuf, String)>, }, WriteGitBranch { repo_path: PathBuf, @@ -127,7 +131,7 @@ enum GitOperation { }, WriteGitStatuses { repo_path: PathBuf, - statuses: Vec<(PathBuf, FileStatus)>, + statuses: Vec<(RelPathBuf, FileStatus)>, }, } @@ -311,8 +315,8 @@ impl RandomizedTest for ProjectCollaborationTest { let Some(worktree) = worktree else { continue }; let is_dir = rng.random::(); let mut full_path = - worktree.read_with(cx, |w, _| PathBuf::from(w.root_name())); - full_path.push(gen_file_name(rng)); + worktree.read_with(cx, |w, _| w.root_name().to_rel_path_buf()); + full_path.push(rel_path(&gen_file_name(rng))); if !is_dir { full_path.set_extension("rs"); } @@ -346,8 +350,18 @@ impl RandomizedTest for ProjectCollaborationTest { continue; }; - let full_path = buffer - .read_with(cx, |buffer, cx| buffer.file().unwrap().full_path(cx)); + let full_path = buffer.read_with(cx, |buffer, cx| { + let file = buffer.file().unwrap(); + let worktree = project + .read(cx) + .worktree_for_id(file.worktree_id(cx), cx) + .unwrap(); + worktree + .read(cx) + .root_name() + .join(file.path()) + .to_rel_path_buf() + }); match rng.random_range(0..100_u32) { // Close the buffer @@ -436,16 +450,16 @@ impl RandomizedTest for ProjectCollaborationTest { .filter(|e| e.is_file()) .choose(rng) .unwrap(); - if entry.path.as_ref() == Path::new("") { - Path::new(worktree.root_name()).into() + if entry.path.as_ref().is_empty() { + worktree.root_name().into() } else { - Path::new(worktree.root_name()).join(&entry.path) + worktree.root_name().join(&entry.path) } }); break ClientOperation::OpenBuffer { project_root_name, is_local, - full_path, + full_path: full_path.to_rel_path_buf(), }; } } @@ -940,7 +954,11 @@ impl RandomizedTest for ProjectCollaborationTest { } for (path, _) in contents.iter() { - if !client.fs().files().contains(&repo_path.join(path)) { + if !client + .fs() + .files() + .contains(&repo_path.join(path.as_std_path())) + { return Err(TestError::Inapplicable); } } @@ -954,8 +972,8 @@ impl RandomizedTest for ProjectCollaborationTest { let dot_git_dir = repo_path.join(".git"); let contents = contents - .into_iter() - .map(|(path, contents)| (path.into(), contents)) + .iter() + .map(|(path, contents)| (path.as_str(), contents.clone())) .collect::>(); if client.fs().metadata(&dot_git_dir).await?.is_none() { client.fs().create_dir(&dot_git_dir).await?; @@ -993,7 +1011,11 @@ impl RandomizedTest for ProjectCollaborationTest { return Err(TestError::Inapplicable); } for (path, _) in statuses.iter() { - if !client.fs().files().contains(&repo_path.join(path)) { + if !client + .fs() + .files() + .contains(&repo_path.join(path.as_std_path())) + { return Err(TestError::Inapplicable); } } @@ -1009,7 +1031,7 @@ impl RandomizedTest for ProjectCollaborationTest { let statuses = statuses .iter() - .map(|(path, val)| (path.as_path(), *val)) + .map(|(path, val)| (path.as_str(), *val)) .collect::>(); if client.fs().metadata(&dot_git_dir).await?.is_none() { @@ -1426,7 +1448,7 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation repo_path: &Path, rng: &mut StdRng, client: &TestClient, - ) -> Vec { + ) -> Vec { let mut paths = client .fs() .files() @@ -1440,7 +1462,11 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation paths .iter() - .map(|path| path.strip_prefix(repo_path).unwrap().to_path_buf()) + .map(|path| { + RelPath::from_std_path(path.strip_prefix(repo_path).unwrap(), PathStyle::local()) + .unwrap() + .to_rel_path_buf() + }) .collect::>() } @@ -1487,7 +1513,7 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation fn buffer_for_full_path( client: &TestClient, project: &Entity, - full_path: &PathBuf, + full_path: &RelPath, cx: &TestAppContext, ) -> Option> { client @@ -1495,7 +1521,12 @@ fn buffer_for_full_path( .iter() .find(|buffer| { buffer.read_with(cx, |buffer, cx| { - buffer.file().unwrap().full_path(cx) == *full_path + let file = buffer.file().unwrap(); + let Some(worktree) = project.read(cx).worktree_for_id(file.worktree_id(cx), cx) + else { + return false; + }; + worktree.read(cx).root_name().join(&file.path()).as_ref() == full_path }) }) .cloned() @@ -1536,23 +1567,23 @@ fn root_name_for_project(project: &Entity, cx: &TestAppContext) -> Stri .next() .unwrap() .read(cx) - .root_name() + .root_name_str() .to_string() }) } fn project_path_for_full_path( project: &Entity, - full_path: &Path, + full_path: &RelPath, cx: &TestAppContext, ) -> Option { let mut components = full_path.components(); - let root_name = components.next().unwrap().as_os_str().to_str().unwrap(); - let path = components.as_path().into(); + let root_name = components.next().unwrap(); + let path = components.rest().into(); let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).find_map(|worktree| { let worktree = worktree.read(cx); - if worktree.root_name() == root_name { + if worktree.root_name_str() == root_name { Some(worktree.id()) } else { None diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index e2e6d7b724386afafad27e72f867be70671263bc..84ee9a33906b976a68da5da7b81c1e89c96190b1 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -33,7 +33,7 @@ use std::{ sync::{Arc, atomic::AtomicUsize}, }; use task::TcpArgumentsTemplate; -use util::path; +use util::{path, rel_path::rel_path}; #[gpui::test(iterations = 10)] async fn test_sharing_an_ssh_remote_project( @@ -124,26 +124,26 @@ async fn test_sharing_an_ssh_remote_project( worktree_a.update(cx_a, |worktree, _cx| { assert_eq!( - worktree.paths().map(Arc::as_ref).collect::>(), + worktree.paths().collect::>(), vec![ - Path::new(".zed"), - Path::new(".zed/settings.json"), - Path::new("README.md"), - Path::new("src"), - Path::new("src/lib.rs"), + rel_path(".zed"), + rel_path(".zed/settings.json"), + rel_path("README.md"), + rel_path("src"), + rel_path("src/lib.rs"), ] ); }); worktree_b.update(cx_b, |worktree, _cx| { assert_eq!( - worktree.paths().map(Arc::as_ref).collect::>(), + worktree.paths().collect::>(), vec![ - Path::new(".zed"), - Path::new(".zed/settings.json"), - Path::new("README.md"), - Path::new("src"), - Path::new("src/lib.rs"), + rel_path(".zed"), + rel_path(".zed/settings.json"), + rel_path("README.md"), + rel_path("src"), + rel_path("src/lib.rs"), ] ); }); @@ -151,7 +151,7 @@ async fn test_sharing_an_ssh_remote_project( // User B can open buffers in the remote project. let buffer_b = project_b .update(cx_b, |project, cx| { - project.open_buffer((worktree_id, "src/lib.rs"), cx) + project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx) }) .await .unwrap(); @@ -177,7 +177,7 @@ async fn test_sharing_an_ssh_remote_project( buffer_b.clone(), ProjectPath { worktree_id: worktree_id.to_owned(), - path: Arc::from(Path::new("src/renamed.rs")), + path: rel_path("src/renamed.rs").into(), }, cx, ) @@ -194,14 +194,8 @@ async fn test_sharing_an_ssh_remote_project( cx_b.run_until_parked(); cx_b.update(|cx| { assert_eq!( - buffer_b - .read(cx) - .file() - .unwrap() - .path() - .to_string_lossy() - .to_string(), - path!("src/renamed.rs").to_string() + buffer_b.read(cx).file().unwrap().path().as_ref(), + rel_path("src/renamed.rs") ); }); } @@ -489,7 +483,7 @@ async fn test_ssh_collaboration_formatting_with_prettier( // Opens the buffer and formats it let (buffer_b, _handle) = project_b .update(cx_b, |p, cx| { - p.open_buffer_with_lsp((worktree_id, "a.ts"), cx) + p.open_buffer_with_lsp((worktree_id, rel_path("a.ts")), cx) }) .await .expect("user B opens buffer for formatting"); @@ -547,7 +541,9 @@ async fn test_ssh_collaboration_formatting_with_prettier( // User A opens and formats the same buffer too let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer((worktree_id, rel_path("a.ts")), cx) + }) .await .expect("user A opens buffer for formatting"); diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 61b7a4e18e4e679c29e26185735352737983c4d1..49753c3a6d9460b86d50e395d394b6af9a819693 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -40,6 +40,7 @@ use std::{ sync::Arc, }; use sum_tree::Dimensions; +use util::rel_path::RelPath; use util::{ResultExt, fs::remove_matching}; use workspace::Workspace; @@ -963,8 +964,7 @@ impl Copilot { let hard_tabs = settings.hard_tabs; let relative_path = buffer .file() - .map(|file| file.path().to_path_buf()) - .unwrap_or_default(); + .map_or(RelPath::empty().into(), |file| file.path().clone()); cx.background_spawn(async move { let (version, snapshot) = snapshot.await?; @@ -975,7 +975,7 @@ impl Copilot { tab_size: tab_size.into(), indent_size: 1, insert_spaces: !hard_tabs, - relative_path: relative_path.to_string_lossy().into(), + relative_path: relative_path.to_proto(), position: point_to_lsp(position), version: version.try_into().unwrap(), }, @@ -1194,7 +1194,7 @@ async fn get_copilot_lsp(fs: Arc, node_runtime: NodeRuntime) -> anyhow:: mod tests { use super::*; use gpui::TestAppContext; - use util::path; + use util::{path, paths::PathStyle, rel_path::rel_path}; #[gpui::test(iterations = 10)] async fn test_buffer_management(cx: &mut TestAppContext) { @@ -1258,7 +1258,7 @@ mod tests { buffer.file_updated( Arc::new(File { abs_path: path!("/root/child/buffer-1").into(), - path: Path::new("child/buffer-1").into(), + path: rel_path("child/buffer-1").into(), }), cx, ) @@ -1355,7 +1355,7 @@ mod tests { struct File { abs_path: PathBuf, - path: Arc, + path: Arc, } impl language::File for File { @@ -1369,15 +1369,19 @@ mod tests { } } - fn path(&self) -> &Arc { + fn path(&self) -> &Arc { &self.path } + fn path_style(&self, _: &App) -> PathStyle { + PathStyle::local() + } + fn full_path(&self, _: &App) -> PathBuf { unimplemented!() } - fn file_name<'a>(&'a self, _: &'a App) -> &'a std::ffi::OsStr { + fn file_name<'a>(&'a self, _: &'a App) -> &'a str { unimplemented!() } diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 2cef266677c1314f6fd253b9caf77914050ceb96..b952a603c786a240feb09264ba834f62df261386 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -24,7 +24,7 @@ use std::{ sync::Arc, }; use task::{DebugScenario, TcpArgumentsTemplate, ZedDebugConfig}; -use util::archive::extract_zip; +use util::{archive::extract_zip, rel_path::RelPath}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum DapStatus { @@ -44,7 +44,7 @@ pub trait DapDelegate: Send + Sync + 'static { fn fs(&self) -> Arc; fn output_to_console(&self, msg: String); async fn which(&self, command: &OsStr) -> Option; - async fn read_text_file(&self, path: PathBuf) -> Result; + async fn read_text_file(&self, path: &RelPath) -> Result; async fn shell_env(&self) -> collections::HashMap; } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 6781e5cbd62d1abc9abfa58223b0771f26cc0c88..468edf5664f1a56c45ea5308747be2b8bcd0a468 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -20,7 +20,7 @@ use std::{ ffi::OsStr, path::{Path, PathBuf}, }; -use util::{ResultExt, maybe}; +use util::{ResultExt, maybe, paths::PathStyle, rel_path::RelPath}; #[derive(Default)] pub(crate) struct PythonDebugAdapter { @@ -726,13 +726,16 @@ impl DebugAdapter for PythonDebugAdapter { .config .get("cwd") .and_then(|cwd| { - cwd.as_str() - .map(Path::new)? - .strip_prefix(delegate.worktree_root_path()) - .ok() + RelPath::from_std_path( + cwd.as_str() + .map(Path::new)? + .strip_prefix(delegate.worktree_root_path()) + .ok()?, + PathStyle::local(), + ) + .ok() }) - .unwrap_or_else(|| "".as_ref()) - .into(); + .unwrap_or_else(|| RelPath::empty().into()); let toolchain = delegate .toolchain_store() .active_toolchain( diff --git a/crates/debug_adapter_extension/src/extension_dap_adapter.rs b/crates/debug_adapter_extension/src/extension_dap_adapter.rs index b656bed9bc2ec972528c4b4c237e8ae0fceedc5a..be225a0c44682f41f7c071641cd4df07798370c8 100644 --- a/crates/debug_adapter_extension/src/extension_dap_adapter.rs +++ b/crates/debug_adapter_extension/src/extension_dap_adapter.rs @@ -15,6 +15,7 @@ use dap::{ use extension::{Extension, WorktreeDelegate}; use gpui::AsyncApp; use task::{DebugScenario, ZedDebugConfig}; +use util::rel_path::RelPath; pub(crate) struct ExtensionDapAdapter { extension: Arc, @@ -57,7 +58,7 @@ impl WorktreeDelegate for WorktreeDelegateAdapter { self.0.worktree_root_path().to_string_lossy().to_string() } - async fn read_text_file(&self, path: PathBuf) -> Result { + async fn read_text_file(&self, path: &RelPath) -> Result { self.0.read_text_file(path).await } diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 787bca01acb204a4a50b18a34f3567137f92aa0e..57f17e577e82bc8f97c6d9a82544324182e3d2f9 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -33,6 +33,7 @@ use std::sync::{Arc, LazyLock}; use task::{DebugScenario, TaskContext}; use tree_sitter::{Query, StreamingIterator as _}; use ui::{ContextMenu, Divider, PopoverMenuHandle, Tab, Tooltip, prelude::*}; +use util::rel_path::RelPath; use util::{ResultExt, debug_panic, maybe}; use workspace::SplitDirection; use workspace::item::SaveOptions; @@ -1061,14 +1062,14 @@ impl DebugPanel { directory_in_worktree: dir, .. } => { - let relative_path = if dir.ends_with(".vscode") { - dir.join("launch.json") + let relative_path = if dir.ends_with(RelPath::new(".vscode").unwrap()) { + dir.join(RelPath::new("launch.json").unwrap()) } else { - dir.join("debug.json") + dir.join(RelPath::new("debug.json").unwrap()) }; ProjectPath { worktree_id: id, - path: Arc::from(relative_path), + path: relative_path, } } _ => return self.save_scenario(scenario, worktree_id, window, cx), @@ -1129,7 +1130,7 @@ impl DebugPanel { let fs = workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?; - path.push(paths::local_settings_folder_relative_path()); + path.push(paths::local_settings_folder_name()); if !fs.is_dir(path.as_path()).await { fs.create_dir(path.as_path()).await?; } diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index a25c02c1b5f72f1e85f532fcee244f0165a8a48e..2ee8d13732f09c517015437691aae659d7449f5c 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -32,7 +32,7 @@ use ui::{ SharedString, Styled, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div, h_flex, relative, rems, v_flex, }; -use util::ResultExt; +use util::{ResultExt, rel_path::RelPath}; use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane}; use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel}; @@ -1026,29 +1026,27 @@ impl DebugDelegate { let mut path = if worktrees.len() > 1 && let Some(worktree) = project.worktree_for_id(*worktree_id, cx) { - let worktree_path = worktree.read(cx).abs_path(); - let full_path = worktree_path.join(directory_in_worktree); - full_path + worktree + .read(cx) + .root_name() + .join(directory_in_worktree) + .to_rel_path_buf() } else { - directory_in_worktree.clone() + directory_in_worktree.to_rel_path_buf() }; - match path - .components() - .next_back() - .and_then(|component| component.as_os_str().to_str()) - { + match path.components().next_back() { Some(".zed") => { - path.push("debug.json"); + path.push(RelPath::new("debug.json").unwrap()); } Some(".vscode") => { - path.push("launch.json"); + path.push(RelPath::new("launch.json").unwrap()); } _ => {} } - Some(path.display().to_string()) + path.display(project.path_style(cx)).to_string() }) - .unwrap_or_else(|_| Some(directory_in_worktree.display().to_string())), + .ok(), Some(TaskSourceKind::AbsPath { abs_path, .. }) => { Some(abs_path.to_string_lossy().into_owned()) } @@ -1135,7 +1133,7 @@ impl DebugDelegate { id: _, directory_in_worktree: dir, id_base: _, - } => dir.ends_with(".zed"), + } => dir.ends_with(RelPath::new(".zed").unwrap()), _ => false, }); @@ -1154,7 +1152,10 @@ impl DebugDelegate { id: _, directory_in_worktree: dir, id_base: _, - } => !(hide_vscode && dir.ends_with(".vscode")), + } => { + !(hide_vscode + && dir.ends_with(RelPath::new(".vscode").unwrap())) + } _ => true, }) .filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter)) diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index 0ede5879aeb6f406191eb6ee1fb83cb6ea67a3f2..f0f86b124a711922a452afc1a74cd4cab9fe28fd 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -26,6 +26,7 @@ use ui::{ Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*, }; +use util::rel_path::RelPath; use workspace::Workspace; use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint}; @@ -663,6 +664,7 @@ impl Render for BreakpointList { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx); self.breakpoints.clear(); + let path_style = self.worktree_store.read(cx).path_style(); let weak = cx.weak_entity(); let breakpoints = breakpoints.into_iter().flat_map(|(path, mut breakpoints)| { let relative_worktree_path = self @@ -673,7 +675,7 @@ impl Render for BreakpointList { worktree .read(cx) .is_visible() - .then(|| Path::new(worktree.read(cx).root_name()).join(relative_path)) + .then(|| worktree.read(cx).root_name().join(&relative_path)) }); breakpoints.sort_by_key(|breakpoint| breakpoint.row); let weak = weak.clone(); @@ -683,14 +685,9 @@ impl Render for BreakpointList { let dir = relative_worktree_path .clone() - .unwrap_or_else(|| PathBuf::from(&*breakpoint.path)) + .or_else(|| RelPath::from_std_path(&breakpoint.path, path_style).ok())? .parent() - .and_then(|parent| { - parent - .to_str() - .map(ToOwned::to_owned) - .map(SharedString::from) - }); + .map(|parent| SharedString::from(parent.display(path_style).to_string())); let name = file_name .to_str() .map(ToOwned::to_owned) diff --git a/crates/debugger_ui/src/session/running/module_list.rs b/crates/debugger_ui/src/session/running/module_list.rs index 4ea763c92cff18f571f27033174ee0b1163b94f9..545d8392745c636b805cfc1e0743170635ef8abe 100644 --- a/crates/debugger_ui/src/session/running/module_list.rs +++ b/crates/debugger_ui/src/session/running/module_list.rs @@ -87,7 +87,7 @@ impl ModuleList { this.open_buffer( ProjectPath { worktree_id, - path: relative_path.into(), + path: relative_path, }, cx, ) diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index 4271bdcbb83b6a34f3c8e15b7572a9712ffd20fd..ee922dd08eff4ba3e56e07d5542595f70d2da6cf 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -401,7 +401,7 @@ impl StackFrameList { this.open_buffer( ProjectPath { worktree_id, - path: relative_path.into(), + path: relative_path, }, cx, ) diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index aef053df4a1ea930fb09a779e08afecfa08ddde9..7cfb111a2bf0a2ab4c60acbff16825eb9e4cf41d 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -181,7 +181,7 @@ impl StackTraceView { let project_path = ProjectPath { worktree_id: worktree.read_with(cx, |tree, _| tree.id())?, - path: relative_path.into(), + path: relative_path, }; if let Some(buffer) = this diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index ab6d5cb9605d5d774187f836130fdae66a8d3404..05de4a47a4ec3c0762a8ebe1318b37f8a812a6d2 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -32,7 +32,7 @@ use std::{ }; use terminal_view::terminal_panel::TerminalPanel; use tests::{active_debug_session_panel, init_test, init_test_workspace}; -use util::path; +use util::{path, rel_path::rel_path}; use workspace::item::SaveOptions; use workspace::{Item, dock::Panel}; @@ -1114,7 +1114,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved( let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -1276,14 +1276,14 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action( let first = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); let second = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "second.rs"), cx) + project.open_buffer((worktree_id, rel_path("second.rs")), cx) }) .await .unwrap(); @@ -1499,14 +1499,14 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T let main_buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); let second_buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "second.rs"), cx) + project.open_buffer((worktree_id, rel_path("second.rs")), cx) }) .await .unwrap(); diff --git a/crates/debugger_ui/src/tests/inline_values.rs b/crates/debugger_ui/src/tests/inline_values.rs index 9f921ec969debc5247d531469c5132e8485c163b..8ca3061f5763de089c84a9e73850dd9ea858e6bb 100644 --- a/crates/debugger_ui/src/tests/inline_values.rs +++ b/crates/debugger_ui/src/tests/inline_values.rs @@ -7,7 +7,7 @@ use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_python, tr use project::{FakeFs, Project}; use serde_json::json; use unindent::Unindent as _; -use util::path; +use util::{path, rel_path::rel_path}; use crate::{ debugger_panel::DebugPanel, @@ -215,7 +215,7 @@ fn main() { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -1584,7 +1584,7 @@ def process_data(untyped_param, typed_param: int, another_typed: str): let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.py"), cx) + project.open_buffer((worktree_id, rel_path("main.py")), cx) }) .await .unwrap(); @@ -2082,7 +2082,7 @@ async fn test_inline_values_util( let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index a61a31d270c9d599f30185d7da3c825c51bb7898..9caef5ba56d4f915bf25aebb5ab7e806f3edf171 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -13,7 +13,7 @@ use project::{FakeFs, Project}; use serde_json::json; use std::sync::Arc; use unindent::Unindent as _; -use util::path; +use util::{path, rel_path::rel_path}; #[gpui::test] async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( @@ -331,12 +331,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let project_path = editors[0] .update(cx, |editor, cx| editor.project_path(cx)) .unwrap(); - let expected = if cfg!(target_os = "windows") { - "src\\test.js" - } else { - "src/test.js" - }; - assert_eq!(expected, project_path.path.to_string_lossy()); + assert_eq!(rel_path("src/test.js"), project_path.path.as_ref()); assert_eq!(test_file_content, editors[0].read(cx).text(cx)); assert_eq!( vec![2..3], @@ -399,12 +394,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let project_path = editors[0] .update(cx, |editor, cx| editor.project_path(cx)) .unwrap(); - let expected = if cfg!(target_os = "windows") { - "src\\module.js" - } else { - "src/module.js" - }; - assert_eq!(expected, project_path.path.to_string_lossy()); + assert_eq!(rel_path("src/module.js"), project_path.path.as_ref()); assert_eq!(module_file_content, editors[0].read(cx).text(cx)); assert_eq!( vec![0..1], diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 3a245163822fb19c43d11a93bc48c3d276e4d502..0bdc5c4eb72a35b0a55598e059d28a91ad1cc834 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -28,7 +28,6 @@ use std::{ }; use text::{Anchor, BufferSnapshot, OffsetRangeExt}; use ui::{Button, ButtonStyle, Icon, IconName, Label, Tooltip, h_flex, prelude::*}; -use util::paths::PathExt; use workspace::{ ItemHandle, ItemNavHistory, ToolbarItemLocation, Workspace, item::{BreadcrumbText, Item, ItemEvent, TabContentParams}, @@ -783,15 +782,16 @@ impl Item for BufferDiagnosticsEditor { } // Builds the content to be displayed in the tab. - fn tab_content(&self, params: TabContentParams, _window: &Window, _cx: &App) -> AnyElement { + fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { + let path_style = self.project.read(cx).path_style(cx); let error_count = self.summary.error_count; let warning_count = self.summary.warning_count; let label = Label::new( self.project_path .path .file_name() - .map(|f| f.to_sanitized_string()) - .unwrap_or_else(|| self.project_path.path.to_sanitized_string()), + .map(|s| s.to_string()) + .unwrap_or_else(|| self.project_path.path.display(path_style).to_string()), ); h_flex() @@ -827,11 +827,12 @@ impl Item for BufferDiagnosticsEditor { "Buffer Diagnostics".into() } - fn tab_tooltip_text(&self, _: &App) -> Option { + fn tab_tooltip_text(&self, cx: &App) -> Option { + let path_style = self.project.read(cx).path_style(cx); Some( format!( "Buffer Diagnostics - {}", - self.project_path.path.to_sanitized_string() + self.project_path.path.display(path_style) ) .into(), ) @@ -848,7 +849,8 @@ impl Item for BufferDiagnosticsEditor { impl Render for BufferDiagnosticsEditor { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let filename = self.project_path.path.to_sanitized_string(); + let path_style = self.project.read(cx).path_style(cx); + let filename = self.project_path.path.display(path_style).to_string(); let error_count = self.summary.error_count; let warning_count = match self.include_warnings { true => self.summary.warning_count, diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index a50e20f579e67010819de0fdb7273d4c9912b8b8..7fe88c8243a12629d6ececfa4d5d38901ed2fd42 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -27,7 +27,7 @@ use std::{ str::FromStr, }; use unindent::Unindent as _; -use util::{RandomCharIter, path, post_inc}; +use util::{RandomCharIter, path, post_inc, rel_path::rel_path}; #[ctor::ctor] fn init_logger() { @@ -1609,7 +1609,7 @@ async fn test_buffer_diagnostics(cx: &mut TestAppContext) { worktree_id: project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }), - path: Arc::from(Path::new("main.rs")), + path: rel_path("main.rs").into(), }; let buffer = project .update(cx, |project, cx| { @@ -1763,7 +1763,7 @@ async fn test_buffer_diagnostics_without_warnings(cx: &mut TestAppContext) { worktree_id: project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }), - path: Arc::from(Path::new("main.rs")), + path: rel_path("main.rs").into(), }; let buffer = project .update(cx, |project, cx| { @@ -1892,7 +1892,7 @@ async fn test_buffer_diagnostics_multiple_servers(cx: &mut TestAppContext) { worktree_id: project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }), - path: Arc::from(Path::new("main.rs")), + path: rel_path("main.rs").into(), }; let buffer = project .update(cx, |project, cx| { diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index c8c3dc54b76085707c0491eab683ff954a483bf9..67ac5fe7983af7369bd86d41c4d45542c06f2007 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -9,7 +9,6 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Read}; use std::process; use std::sync::{LazyLock, OnceLock}; -use util::paths::PathExt; static KEYMAP_MACOS: LazyLock = LazyLock::new(|| { load_keymap("keymaps/default-macos.json").expect("Failed to load MacOS keymap") @@ -345,7 +344,7 @@ fn handle_postprocessing() -> Result<()> { let mut queue = Vec::with_capacity(64); queue.push(root_dir.clone()); while let Some(dir) = queue.pop() { - for entry in std::fs::read_dir(&dir).context(dir.to_sanitized_string())? { + for entry in std::fs::read_dir(&dir).context("failed to read docs dir")? { let Ok(entry) = entry else { continue; }; diff --git a/crates/edit_prediction_context/src/syntax_index.rs b/crates/edit_prediction_context/src/syntax_index.rs index 1b5e4268ccec74b9eea52c1001c7854dd746c5cf..4e890bd4230c05679b56f56d6a089fd0e5ebb0a4 100644 --- a/crates/edit_prediction_context/src/syntax_index.rs +++ b/crates/edit_prediction_context/src/syntax_index.rs @@ -324,7 +324,7 @@ impl SyntaxIndex { cx.spawn(async move |_this, cx| { let loaded_file = load_task.await?; let language = language_registry - .language_for_file_path(&project_path.path) + .language_for_file_path(&project_path.path.as_std_path()) .await .ok(); @@ -549,7 +549,7 @@ impl SyntaxIndexState { #[cfg(test)] mod tests { use super::*; - use std::{path::Path, sync::Arc}; + use std::sync::Arc; use gpui::TestAppContext; use indoc::indoc; @@ -558,7 +558,7 @@ mod tests { use serde_json::json; use settings::SettingsStore; use text::OffsetRangeExt as _; - use util::path; + use util::{path, rel_path::rel_path}; use crate::syntax_index::SyntaxIndex; @@ -739,7 +739,7 @@ mod tests { .read(cx) .path_for_entry(*project_entry_id, cx) .unwrap(); - assert_eq!(project_path.path.as_ref(), Path::new(path),); + assert_eq!(project_path.path.as_ref(), rel_path(path),); declaration } else { panic!("Expected a buffer declaration, found {:?}", declaration); @@ -764,7 +764,7 @@ mod tests { .unwrap() .path .as_ref(), - Path::new(path), + rel_path(path), ); declaration } else { diff --git a/crates/editor/src/clangd_ext.rs b/crates/editor/src/clangd_ext.rs index c78d4c83c01c49e6b1ff947d3cd53bc887424a16..276464d7ccafac55d7abfbf8eeb3d1f36dfa77d6 100644 --- a/crates/editor/src/clangd_ext.rs +++ b/crates/editor/src/clangd_ext.rs @@ -4,6 +4,7 @@ use language::Language; use project::lsp_store::lsp_ext_command::SwitchSourceHeaderResult; use rpc::proto; use url::Url; +use util::paths::PathStyle; use workspace::{OpenOptions, OpenVisible}; use crate::lsp_ext::find_specific_language_server_in_selection; @@ -38,7 +39,11 @@ pub fn switch_source_header( let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); cx.spawn_in(window, async move |_editor, cx| { let source_file = buffer.read_with(cx, |buffer, _| { - buffer.file().map(|file| file.path()).map(|path| path.to_string_lossy().to_string()).unwrap_or_else(|| "Unknown".to_string()) + buffer + .file() + .map(|file| file.path()) + .map(|path| path.display(PathStyle::local()).to_string()) + .unwrap_or_else(|| "Unknown".to_string()) })?; let switch_source_header = if let Some((client, project_id)) = upstream_client { @@ -53,18 +58,22 @@ pub fn switch_source_header( .context("lsp ext switch source header proto request")?; SwitchSourceHeaderResult(response.target_file) } else { - project.update(cx, |project, cx| { - project.request_lsp( - buffer, - project::LanguageServerToQuery::Other(server_to_query), - project::lsp_store::lsp_ext_command::SwitchSourceHeader, - cx, - ) - })?.await.with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))? + project + .update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + project::lsp_store::lsp_ext_command::SwitchSourceHeader, + cx, + ) + })? + .await + .with_context(|| { + format!("Switch source/header LSP request for path \"{source_file}\" failed") + })? }; if switch_source_header.0.is_empty() { - log::info!("Clangd returned an empty string when requesting to switch source/header from \"{source_file}\"" ); return Ok(()); } @@ -75,18 +84,24 @@ pub fn switch_source_header( ) })?; - let path = goto.to_file_path().map_err(|()| { - anyhow::anyhow!("URL conversion to file path failed for \"{goto}\"") - })?; + let path = goto + .to_file_path() + .map_err(|()| anyhow::anyhow!("URL conversion to file path failed for \"{goto}\""))?; workspace .update_in(cx, |workspace, window, cx| { - workspace.open_abs_path(path, OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx) + workspace.open_abs_path( + path, + OpenOptions { + visible: Some(OpenVisible::None), + ..Default::default() + }, + window, + cx, + ) }) .with_context(|| { - format!( - "Switch source/header could not open \"{goto}\" in workspace" - ) + format!("Switch source/header could not open \"{goto}\" in workspace") })? .await .map(|_| ()) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index eec6e367dc597eb61a37ad33e116fd6688ba7c66..e3e11a483a76696b1e257049f6792e705dfad5ad 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2494,7 +2494,7 @@ impl Editor { if let Some(extension) = singleton_buffer .read(cx) .file() - .and_then(|file| file.path().extension()?.to_str()) + .and_then(|file| file.path().extension()) { key_context.set("extension", extension.to_string()); } @@ -7603,7 +7603,7 @@ impl Editor { let extension = buffer .read(cx) .file() - .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string())); + .and_then(|file| Some(file.path().extension()?.to_string())); let event_type = match accepted { true => "Edit Prediction Accepted", @@ -19263,10 +19263,6 @@ impl Editor { { return Some(dir.to_owned()); } - - if let Some(project_path) = buffer.read(cx).project_path(cx) { - return Some(project_path.path.to_path_buf()); - } } None @@ -19294,16 +19290,6 @@ impl Editor { }) } - fn target_file_path(&self, cx: &mut Context) -> Option { - self.active_excerpt(cx).and_then(|(_, buffer, _)| { - let project_path = buffer.read(cx).project_path(cx)?; - let project = self.project()?.read(cx); - let entry = project.entry_for_path(&project_path, cx)?; - let path = entry.path.to_path_buf(); - Some(path) - }) - } - pub fn reveal_in_finder( &mut self, _: &RevealInFileManager, @@ -19336,9 +19322,12 @@ impl Editor { _window: &mut Window, cx: &mut Context, ) { - if let Some(path) = self.target_file_path(cx) - && let Some(path) = path.to_str() - { + if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + let project = self.project()?.read(cx); + let path = buffer.read(cx).file()?.path(); + let path = path.display(project.path_style(cx)); + Some(path) + }) { cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); } else { cx.propagate(); @@ -19414,16 +19403,14 @@ impl Editor { ) { 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())); + cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string())); } } pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context) { if let Some(file) = self.target_file(cx) - && let Some(file_name) = file.path().file_name() - && let Some(name) = file_name.to_str() + && let Some(name) = file.path().file_name() { cx.write_to_clipboard(ClipboardItem::new_string(name.to_string())); } @@ -19691,9 +19678,8 @@ impl Editor { cx: &mut Context, ) { let selection = self.selections.newest::(cx).start.row + 1; - if let Some(file) = self.target_file(cx) - && let Some(path) = file.path().to_str() - { + if let Some(file) = self.target_file(cx) { + let path = file.path().display(file.path_style(cx)); cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}"))); } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 7aa441b4c7083aa4e39b006bfd03aea47fec23f0..7a1a6f49830d657d2c3aa0f1e2eeabe205031075 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -56,6 +56,7 @@ use text::ToPoint as _; use unindent::Unindent; use util::{ assert_set_eq, path, + rel_path::rel_path, test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text}, uri, }; @@ -11142,19 +11143,19 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) { let buffer_1 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); let buffer_2 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "other.rs"), cx) + project.open_buffer((worktree_id, rel_path("other.rs")), cx) }) .await .unwrap(); let buffer_3 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "lib.rs"), cx) + project.open_buffer((worktree_id, rel_path("lib.rs")), cx) }) .await .unwrap(); @@ -11329,19 +11330,19 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) { // Open three buffers let buffer_1 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "file1.rs"), cx) + project.open_buffer((worktree_id, rel_path("file1.rs")), cx) }) .await .unwrap(); let buffer_2 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "file2.rs"), cx) + project.open_buffer((worktree_id, rel_path("file2.rs")), cx) }) .await .unwrap(); let buffer_3 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "file3.rs"), cx) + project.open_buffer((worktree_id, rel_path("file3.rs")), cx) }) .await .unwrap(); @@ -14677,7 +14678,7 @@ async fn test_multiline_completion(cx: &mut TestAppContext) { .unwrap(); let editor = workspace .update(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.ts"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx) }) .unwrap() .await @@ -16394,7 +16395,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) { leader.update(cx, |leader, cx| { leader.buffer.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( - PathKey::namespaced(1, Arc::from(Path::new("b.txt"))), + PathKey::namespaced(1, "b.txt".into()), buffer_1.clone(), vec![ Point::row_range(0..3), @@ -16405,7 +16406,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) { cx, ); multibuffer.set_excerpts_for_path( - PathKey::namespaced(1, Arc::from(Path::new("a.txt"))), + PathKey::namespaced(1, "a.txt".into()), buffer_2.clone(), vec![Point::row_range(0..6), Point::row_range(8..12)], 0, @@ -16897,7 +16898,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) { .unwrap(); let editor_handle = workspace .update(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx) }) .unwrap() .await @@ -20878,9 +20879,9 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) { fs.set_head_for_repo( path!("/test/.git").as_ref(), &[ - ("file-1".into(), "one\n".into()), - ("file-2".into(), "two\n".into()), - ("file-3".into(), "three\n".into()), + ("file-1", "one\n".into()), + ("file-2", "two\n".into()), + ("file-3", "three\n".into()), ], "deadbeef", ); @@ -20904,7 +20905,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) { for buffer in &buffers { let snapshot = buffer.read(cx).snapshot(); multibuffer.set_excerpts_for_path( - PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()), + PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().as_str().into()), buffer.clone(), vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)], 2, @@ -21657,19 +21658,19 @@ async fn test_folding_buffers(cx: &mut TestAppContext) { let buffer_1 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "first.rs"), cx) + project.open_buffer((worktree_id, rel_path("first.rs")), cx) }) .await .unwrap(); let buffer_2 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "second.rs"), cx) + project.open_buffer((worktree_id, rel_path("second.rs")), cx) }) .await .unwrap(); let buffer_3 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "third.rs"), cx) + project.open_buffer((worktree_id, rel_path("third.rs")), cx) }) .await .unwrap(); @@ -21825,19 +21826,19 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) { let buffer_1 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "first.rs"), cx) + project.open_buffer((worktree_id, rel_path("first.rs")), cx) }) .await .unwrap(); let buffer_2 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "second.rs"), cx) + project.open_buffer((worktree_id, rel_path("second.rs")), cx) }) .await .unwrap(); let buffer_3 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "third.rs"), cx) + project.open_buffer((worktree_id, rel_path("third.rs")), cx) }) .await .unwrap(); @@ -21960,7 +21961,7 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test let buffer_1 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -22499,7 +22500,7 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -22613,7 +22614,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -22783,7 +22784,7 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "main.rs"), cx) + project.open_buffer((worktree_id, rel_path("main.rs")), cx) }) .await .unwrap(); @@ -23371,7 +23372,7 @@ println!("5"); let editor_1 = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane_1.downgrade()), true, window, @@ -23414,7 +23415,7 @@ println!("5"); let editor_2 = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane_2.downgrade()), true, window, @@ -23453,7 +23454,7 @@ println!("5"); let _other_editor_1 = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "lib.rs"), + (worktree_id, rel_path("lib.rs")), Some(pane_1.downgrade()), true, window, @@ -23489,7 +23490,7 @@ println!("5"); let _other_editor_2 = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "lib.rs"), + (worktree_id, rel_path("lib.rs")), Some(pane_2.downgrade()), true, window, @@ -23526,7 +23527,7 @@ println!("5"); let _editor_1_reopened = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane_1.downgrade()), true, window, @@ -23540,7 +23541,7 @@ println!("5"); let _editor_2_reopened = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane_2.downgrade()), true, window, @@ -23634,7 +23635,7 @@ println!("5"); let editor = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane.downgrade()), true, window, @@ -23693,7 +23694,7 @@ println!("5"); let _editor_reopened = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane.downgrade()), true, window, @@ -23860,7 +23861,7 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { .unwrap(); let editor = workspace .update(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "file.html"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx) }) .unwrap() .await @@ -24054,7 +24055,7 @@ async fn test_invisible_worktree_servers(cx: &mut TestAppContext) { let main_editor = workspace .update_in(cx, |workspace, window, cx| { workspace.open_path( - (worktree_id, "main.rs"), + (worktree_id, rel_path("main.rs")), Some(pane.downgrade()), true, window, @@ -25636,7 +25637,7 @@ async fn test_document_colors(cx: &mut TestAppContext) { .path(); assert_eq!( editor_file.as_ref(), - Path::new("first.rs"), + rel_path("first.rs"), "Both editors should be opened for the same file" ) } @@ -25816,7 +25817,7 @@ async fn test_non_utf_8_opens(cx: &mut TestAppContext) { let handle = workspace .update_in(cx, |workspace, window, cx| { - let project_path = (worktree_id, "one.pdf"); + let project_path = (worktree_id, rel_path("one.pdf")); workspace.open_path(project_path, None, true, window, cx) }) .await diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 65da1d76b78b9099073a9abd655a7f54608bd848..90dd2a599a16705e0eb49319ec562d505ca58399 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3779,13 +3779,15 @@ impl EditorElement { .as_ref() .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) .unwrap_or_default(); - let can_open_excerpts = Editor::can_open_excerpts_in_file(for_excerpt.buffer.file()); + let file = for_excerpt.buffer.file(); + let can_open_excerpts = Editor::can_open_excerpts_in_file(file); + let path_style = file.map(|file| file.path_style(cx)); let relative_path = for_excerpt.buffer.resolve_file_path(cx, include_root); let filename = relative_path .as_ref() .and_then(|path| Some(path.file_name()?.to_string_lossy().to_string())); let parent_path = relative_path.as_ref().and_then(|path| { - Some(path.parent()?.to_string_lossy().to_string() + std::path::MAIN_SEPARATOR_STR) + Some(path.parent()?.to_string_lossy().to_string() + path_style?.separator()) }); let focus_handle = editor.focus_handle(cx); let colors = cx.theme().colors(); @@ -3990,12 +3992,13 @@ impl EditorElement { && let Some(worktree) = project.read(cx).worktree_for_id(file.worktree_id(cx), cx) { + let path_style = file.path_style(cx); let worktree = worktree.read(cx); let relative_path = file.path(); let entry_for_path = worktree.entry_for_path(relative_path); let abs_path = entry_for_path.map(|e| { e.canonical_path.as_deref().map_or_else( - || worktree.abs_path().join(relative_path), + || worktree.absolutize(relative_path), Path::to_path_buf, ) }); @@ -4031,7 +4034,7 @@ impl EditorElement { Some(Box::new(zed_actions::workspace::CopyRelativePath)), window.handler_for(&editor, move |_, _, cx| { cx.write_to_clipboard(ClipboardItem::new_string( - relative_path.to_string_lossy().to_string(), + relative_path.display(path_style).to_string(), )); }), ) diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index 48a6da74467ea91630b4954fe9af38d34b8a7e96..35cab36a5568023db9ff77fd692074d43df56b91 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -698,6 +698,7 @@ async fn parse_commit_messages( #[cfg(test)] mod tests { use super::*; + use git::repository::repo_path; use gpui::Context; use language::{Point, Rope}; use project::FakeFs; @@ -850,7 +851,7 @@ mod tests { fs.set_blame_for_repo( Path::new("/my-repo/.git"), vec![( - "file.txt".into(), + repo_path("file.txt"), Blame { entries: vec![ blame_entry("1b1b1b", 0..1), @@ -967,7 +968,7 @@ mod tests { fs.set_blame_for_repo( Path::new(path!("/my-repo/.git")), vec![( - "file.txt".into(), + repo_path("file.txt"), Blame { entries: vec![blame_entry("1b1b1b", 0..4)], ..Default::default() @@ -1135,7 +1136,7 @@ mod tests { fs.set_blame_for_repo( Path::new(path!("/my-repo/.git")), vec![( - "file.txt".into(), + repo_path("file.txt"), Blame { entries: blame_entries, ..Default::default() @@ -1178,7 +1179,7 @@ mod tests { fs.set_blame_for_repo( Path::new(path!("/my-repo/.git")), vec![( - "file.txt".into(), + repo_path("file.txt"), Blame { entries: blame_entries, ..Default::default() diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index b5ae47bbdf0fc13a87b6bdac63f9f2a85594aca0..3e592e4bdcbca2a0e9679f9e79e7d3f65e95895a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -651,7 +651,7 @@ impl Item for Editor { fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString { if let Some(path) = path_for_buffer(&self.buffer, detail, true, cx) { - path.to_string_lossy().to_string().into() + path.to_string().into() } else { // Use the same logic as the displayed title for consistency self.buffer.read(cx).title(cx).to_string().into() @@ -667,7 +667,7 @@ impl Item for Editor { .file_icons .then(|| { path_for_buffer(&self.buffer, 0, true, cx) - .and_then(|path| FileIcons::get_icon(path.as_ref(), cx)) + .and_then(|path| FileIcons::get_icon(Path::new(&*path), cx)) }) .flatten() .map(Icon::from_path) @@ -703,8 +703,7 @@ impl Item for Editor { let description = params.detail.and_then(|detail| { let path = path_for_buffer(&self.buffer, detail, false, cx)?; - let description = path.to_string_lossy(); - let description = description.trim(); + let description = path.trim(); if description.is_empty() { return None; @@ -898,10 +897,7 @@ impl Item for Editor { .as_singleton() .expect("cannot call save_as on an excerpt list"); - let file_extension = path - .path - .extension() - .map(|a| a.to_string_lossy().to_string()); + let file_extension = path.path.extension().map(|a| a.to_string()); self.report_editor_event( ReportEditorEvent::Saved { auto_saved: false }, file_extension, @@ -1167,7 +1163,7 @@ impl SerializableItem for Editor { let (worktree, path) = project.find_worktree(&abs_path, cx)?; let project_path = ProjectPath { worktree_id: worktree.read(cx).id(), - path: path.into(), + path: path, }; Some(project.open_path(project_path, cx)) }); @@ -1288,7 +1284,7 @@ impl SerializableItem for Editor { project .read(cx) .worktree_for_id(worktree_id, cx) - .and_then(|worktree| worktree.read(cx).absolutize(file.path()).ok()) + .map(|worktree| worktree.read(cx).absolutize(file.path())) .or_else(|| { let full_path = file.full_path(cx); let project_path = project.read(cx).find_project_path(&full_path, cx)?; @@ -1882,7 +1878,7 @@ fn path_for_buffer<'a>( height: usize, include_filename: bool, cx: &'a App, -) -> Option> { +) -> Option> { let file = buffer.read(cx).as_singleton()?.read(cx).file()?; path_for_file(file.as_ref(), height, include_filename, cx) } @@ -1892,7 +1888,7 @@ fn path_for_file<'a>( mut height: usize, include_filename: bool, cx: &'a App, -) -> Option> { +) -> Option> { // Ensure we always render at least the filename. height += 1; @@ -1906,22 +1902,21 @@ fn path_for_file<'a>( } } - // Here we could have just always used `full_path`, but that is very - // allocation-heavy and so we try to use a `Cow` if we haven't - // traversed all the way up to the worktree's root. + // The full_path method allocates, so avoid calling it if height is zero. if height > 0 { - let full_path = file.full_path(cx); - if include_filename { - Some(full_path.into()) - } else { - Some(full_path.parent()?.to_path_buf().into()) + let mut full_path = file.full_path(cx); + if !include_filename { + if !full_path.pop() { + return None; + } } + Some(full_path.to_string_lossy().to_string().into()) } else { let mut path = file.path().strip_prefix(prefix).ok()?; if !include_filename { path = path.parent()?; } - Some(path.into()) + Some(path.display(file.path_style(cx))) } } @@ -1936,12 +1931,12 @@ mod tests { use language::{LanguageMatcher, TestFile}; use project::FakeFs; use std::path::{Path, PathBuf}; - use util::path; + use util::{path, rel_path::RelPath}; #[gpui::test] fn test_path_for_file(cx: &mut App) { let file = TestFile { - path: Path::new("").into(), + path: RelPath::empty().into(), root_name: String::new(), local_root: None, }; diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 03e99b9fff9a89fcac28605fe6bf7a08b23f8f02..be4a5575b7761e5c81acb215d13344d8779ddfdf 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -217,15 +217,7 @@ pub fn editor_content_with_blocks(editor: &Entity, cx: &mut VisualTestCo height, } => { lines[row.0 as usize].push_str(&cx.update(|_, cx| { - format!( - "§ {}", - first_excerpt - .buffer - .file() - .unwrap() - .file_name(cx) - .to_string_lossy() - ) + format!("§ {}", first_excerpt.buffer.file().unwrap().file_name(cx)) })); for row in row.0 + 1..row.0 + height { lines[row as usize].push_str("§ -----"); @@ -237,17 +229,11 @@ pub fn editor_content_with_blocks(editor: &Entity, cx: &mut VisualTestCo } } Block::BufferHeader { excerpt, height } => { - lines[row.0 as usize].push_str(&cx.update(|_, cx| { - format!( - "§ {}", - excerpt - .buffer - .file() - .unwrap() - .file_name(cx) - .to_string_lossy() - ) - })); + lines[row.0 as usize].push_str( + &cx.update(|_, cx| { + format!("§ {}", excerpt.buffer.file().unwrap().file_name(cx)) + }), + ); for row in row.0 + 1..row.0 + height { lines[row as usize].push_str("§ -----"); } diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index fbf7a312fe56600ad78e13c278c85e29b8ca5aa5..06fd01c85f8d2371a9ff181b6720e25353a396c7 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -296,7 +296,7 @@ impl EditorTestContext { let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone()); fs.set_head_for_repo( &Self::root_path().join(".git"), - &[(path.into(), diff_base.to_string())], + &[(path.as_str(), diff_base.to_string())], "deadbeef", ); self.cx.run_until_parked(); @@ -317,7 +317,7 @@ impl EditorTestContext { let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone()); fs.set_index_for_repo( &Self::root_path().join(".git"), - &[(path.into(), diff_base.to_string())], + &[(path.as_str(), diff_base.to_string())], ); self.cx.run_until_parked(); } @@ -329,7 +329,7 @@ impl EditorTestContext { let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone()); let mut found = None; fs.with_git_state(&Self::root_path().join(".git"), false, |git_state| { - found = git_state.index_contents.get(path.as_ref()).cloned(); + found = git_state.index_contents.get(&path.into()).cloned(); }) .unwrap(); assert_eq!(expected, found.as_deref()); diff --git a/crates/eval/src/example.rs b/crates/eval/src/example.rs index 457b62e98ca4cabf83fb379cbaa70f07957ac6b7..c0f0900a6cfa5dd942bd27eed852ee4a52896c2c 100644 --- a/crates/eval/src/example.rs +++ b/crates/eval/src/example.rs @@ -1,7 +1,6 @@ use std::{ error::Error, fmt::{self, Debug}, - path::Path, sync::{Arc, Mutex}, time::Duration, }; @@ -20,6 +19,7 @@ use collections::HashMap; use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased}; use gpui::{App, AppContext, AsyncApp, Entity}; use language_model::{LanguageModel, Role, StopReason}; +use util::rel_path::RelPath; pub const THREAD_EVENT_TIMEOUT: Duration = Duration::from_secs(60 * 2); @@ -354,7 +354,7 @@ impl ExampleContext { Ok(response) } - pub fn edits(&self) -> HashMap, FileEdits> { + pub fn edits(&self) -> HashMap, FileEdits> { self.agent_thread .read_with(&self.app, |thread, cx| { let action_log = thread.action_log().read(cx); 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 084f12bc6263da030d313c362cc3d051dfdb8ea8..0626be5a4e2d620337e1bd8896e25f519de86811 100644 --- a/crates/eval/src/examples/add_arg_to_trait_method.rs +++ b/crates/eval/src/examples/add_arg_to_trait_method.rs @@ -1,8 +1,7 @@ -use std::path::Path; - use agent_settings::AgentProfileId; use anyhow::Result; use async_trait::async_trait; +use util::rel_path::RelPath; use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion, LanguageServer}; @@ -68,7 +67,7 @@ impl Example for AddArgToTraitMethod { for tool_name in add_ignored_window_paths { let path_str = format!("crates/assistant_tools/src/{}.rs", tool_name); - let edits = edits.get(Path::new(&path_str)); + let edits = edits.get(RelPath::new(&path_str).unwrap()); let ignored = edits.is_some_and(|edits| { edits.has_added_line(" _window: Option,\n") @@ -86,7 +85,8 @@ impl Example for AddArgToTraitMethod { // Adds unignored argument to `batch_tool` - let batch_tool_edits = edits.get(Path::new("crates/assistant_tools/src/batch_tool.rs")); + let batch_tool_edits = + edits.get(RelPath::new("crates/assistant_tools/src/batch_tool.rs").unwrap()); cx.assert( batch_tool_edits.is_some_and(|edits| { diff --git a/crates/eval/src/examples/code_block_citations.rs b/crates/eval/src/examples/code_block_citations.rs index 2239ccdfddcc023fdae6f56bd91fd73c1f851ac6..8150d68ac3e54772e35fe52f086fb942d8923ffb 100644 --- a/crates/eval/src/examples/code_block_citations.rs +++ b/crates/eval/src/examples/code_block_citations.rs @@ -65,7 +65,7 @@ impl Example for CodeBlockCitations { thread .project() .read(cx) - .find_project_path(path_range.path, cx) + .find_project_path(path_range.path.as_ref(), cx) }) .ok() .flatten(); diff --git a/crates/eval/src/instance.rs b/crates/eval/src/instance.rs index c6e4e0b6ec683b63b90920861f3cd023069666e6..d0fd98d0280c2f539a67de009a0335b5ae479027 100644 --- a/crates/eval/src/instance.rs +++ b/crates/eval/src/instance.rs @@ -250,7 +250,7 @@ impl ExampleInstance { worktree .files(false, 0) .find_map(|e| { - if e.path.clone().extension().and_then(|ext| ext.to_str()) + if e.path.clone().extension() == Some(&language_server.file_extension) { Some(ProjectPath { diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index 6af793253bce2d122a5361f6b83f33cb39d45253..bd2b37c337dcaca448e2175472ea46c126d2f9a3 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -16,6 +16,7 @@ use gpui::{App, Task}; use language::LanguageName; use semantic_version::SemanticVersion; use task::{SpawnInTerminal, ZedDebugConfig}; +use util::rel_path::RelPath; pub use crate::capabilities::*; pub use crate::extension_events::*; @@ -33,7 +34,7 @@ pub fn init(cx: &mut App) { pub trait WorktreeDelegate: Send + Sync + 'static { fn id(&self) -> u64; fn root_path(&self) -> String; - async fn read_text_file(&self, path: PathBuf) -> Result; + async fn read_text_file(&self, path: &RelPath) -> Result; async fn which(&self, binary_name: String) -> Option; async fn shell_env(&self) -> Vec<(String, String)>; } diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 2d5839e96a1cf5c7550300e762bb97d357864fc6..cd1eaa9855351630e13185962738b1e0d5fb9489 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1752,7 +1752,14 @@ impl ExtensionStore { })? .await?; let dest_dir = RemotePathBuf::new( - PathBuf::from(&response.tmp_dir).join(missing_extension.clone().id), + path_style + .join(&response.tmp_dir, &missing_extension.id) + .with_context(|| { + format!( + "failed to construct destination path: {:?}, {:?}", + response.tmp_dir, missing_extension.id, + ) + })?, path_style, ); log::info!("Uploading extension {}", missing_extension.clone().id); diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index a6305118cd3355f69a42914ec86bb5edcfc74810..f14bb811a6742a60899ac4301cfac096bb41a07f 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -1,10 +1,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{Context as _, Result}; -use client::{ - TypedEnvelope, - proto::{self, FromProto}, -}; +use client::{TypedEnvelope, proto}; use collections::{HashMap, HashSet}; use extension::{ Extension, ExtensionDebugAdapterProviderProxy, ExtensionHostProxy, ExtensionLanguageProxy, @@ -342,7 +339,7 @@ impl HeadlessExtensionStore { version: extension.version, dev: extension.dev, }, - PathBuf::from_proto(envelope.payload.tmp_dir), + PathBuf::from(envelope.payload.tmp_dir), cx, ) })? diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs index 9c726ebd1c45d868d0794f26044cbc53d87eb00f..16e695f04fc52b307a08ffce48bfcca77ba816c0 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs @@ -16,6 +16,7 @@ use std::{ path::{Path, PathBuf}, sync::{Arc, OnceLock}, }; +use util::rel_path::RelPath; use util::{archive::extract_zip, fs::make_file_executable, maybe}; use wasmtime::component::{Linker, Resource}; @@ -421,12 +422,12 @@ impl ExtensionImports for WasmState { ) -> wasmtime::Result> { self.on_main_thread(|cx| { async move { - let location = location - .as_ref() - .map(|location| ::settings::SettingsLocation { + let location = location.as_ref().and_then(|location| { + Some(::settings::SettingsLocation { worktree_id: WorktreeId::from_proto(location.worktree_id), - path: Path::new(&location.path), - }); + path: RelPath::new(&location.path).ok()?, + }) + }); cx.update(|cx| match category.as_str() { "language" => { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index 790a75e896dc0a440bc27d8972c09b879020e9c2..9e608b9e8e68ee93cfcb47c39708a8a0c2499d71 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -31,7 +31,7 @@ use std::{ }; use task::{SpawnInTerminal, ZedDebugConfig}; use url::Url; -use util::{archive::extract_zip, fs::make_file_executable, maybe}; +use util::{archive::extract_zip, fs::make_file_executable, maybe, rel_path::RelPath}; use wasmtime::component::{Linker, Resource}; pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0); @@ -564,7 +564,7 @@ impl HostWorktree for WasmState { ) -> wasmtime::Result> { let delegate = self.table.get(&delegate)?; Ok(delegate - .read_text_file(path.into()) + .read_text_file(RelPath::new(&path)?) .await .map_err(|error| error.to_string())) } @@ -914,12 +914,12 @@ impl ExtensionImports for WasmState { ) -> wasmtime::Result> { self.on_main_thread(|cx| { async move { - let location = location - .as_ref() - .map(|location| ::settings::SettingsLocation { + let location = location.as_ref().and_then(|location| { + Some(::settings::SettingsLocation { worktree_id: WorktreeId::from_proto(location.worktree_id), - path: Path::new(&location.path), - }); + path: RelPath::new(&location.path).ok()?, + }) + }); cx.update(|cx| match category.as_str() { "language" => { diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index 65572eb0241be52dc0fb2ebf9891d3a5858caa83..5dcd1e210527ee89a35a3b89008a901cf1f9f036 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::path::Path; use std::sync::{Arc, OnceLock}; use db::kvp::KEY_VALUE_STORE; @@ -8,6 +7,7 @@ use extension_host::ExtensionStore; use gpui::{AppContext as _, Context, Entity, SharedString, Window}; use language::Buffer; use ui::prelude::*; +use util::rel_path::RelPath; use workspace::notifications::simple_message_notification::MessageNotification; use workspace::{Workspace, notifications::NotificationId}; @@ -100,15 +100,9 @@ struct SuggestedExtension { } /// Returns the suggested extension for the given [`Path`]. -fn suggested_extension(path: impl AsRef) -> Option { - let path = path.as_ref(); - - let file_extension: Option> = path - .extension() - .and_then(|extension| Some(extension.to_str()?.into())); - let file_name: Option> = path - .file_name() - .and_then(|file_name| Some(file_name.to_str()?.into())); +fn suggested_extension(path: &RelPath) -> Option { + let file_extension: Option> = path.extension().map(|extension| extension.into()); + let file_name: Option> = path.file_name().map(|name| name.into()); let (file_name_or_extension, extension_id) = None // We suggest against file names first, as these suggestions will be more @@ -210,39 +204,40 @@ pub(crate) fn suggest(buffer: Entity, window: &mut Window, cx: &mut Cont #[cfg(test)] mod tests { use super::*; + use util::rel_path::rel_path; #[test] pub fn test_suggested_extension() { assert_eq!( - suggested_extension("Cargo.toml"), + suggested_extension(rel_path("Cargo.toml")), Some(SuggestedExtension { extension_id: "toml".into(), file_name_or_extension: "toml".into() }) ); assert_eq!( - suggested_extension("Cargo.lock"), + suggested_extension(rel_path("Cargo.lock")), Some(SuggestedExtension { extension_id: "toml".into(), file_name_or_extension: "Cargo.lock".into() }) ); assert_eq!( - suggested_extension("Dockerfile"), + suggested_extension(rel_path("Dockerfile")), Some(SuggestedExtension { extension_id: "dockerfile".into(), file_name_or_extension: "Dockerfile".into() }) ); assert_eq!( - suggested_extension("a/b/c/d/.gitignore"), + suggested_extension(rel_path("a/b/c/d/.gitignore")), Some(SuggestedExtension { extension_id: "git-firefly".into(), file_name_or_extension: ".gitignore".into() }) ); assert_eq!( - suggested_extension("a/b/c/d/test.gleam"), + suggested_extension(rel_path("a/b/c/d/test.gleam")), Some(SuggestedExtension { extension_id: "gleam".into(), file_name_or_extension: "gleam".into() diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index dadd3ea299304e845bbc0f412c3962d14e2006e4..4126d37a3fb60893474b5e090a6d2a83fab1b50e 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -39,7 +39,12 @@ use ui::{ ButtonLike, ContextMenu, HighlightedLabel, Indicator, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, prelude::*, }; -use util::{ResultExt, maybe, paths::PathWithPosition, post_inc}; +use util::{ + ResultExt, maybe, + paths::{PathStyle, PathWithPosition}, + post_inc, + rel_path::RelPath, +}; use workspace::{ ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace, item::PreviewTabsSettings, notifications::NotifyResultExt, pane, @@ -126,38 +131,34 @@ impl FileFinder { let project = workspace.project().read(cx); let fs = project.fs(); - let currently_opened_path = workspace - .active_item(cx) - .and_then(|item| item.project_path(cx)) - .map(|project_path| { - let abs_path = project - .worktree_for_id(project_path.worktree_id, cx) - .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path)); - FoundPath::new(project_path, abs_path) - }); + let currently_opened_path = workspace.active_item(cx).and_then(|item| { + let project_path = item.project_path(cx)?; + let abs_path = project + .worktree_for_id(project_path.worktree_id, cx)? + .read(cx) + .absolutize(&project_path.path); + Some(FoundPath::new(project_path, abs_path)) + }); let history_items = workspace .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx) .into_iter() .filter_map(|(project_path, abs_path)| { if project.entry_for_path(&project_path, cx).is_some() { - return Some(Task::ready(Some(FoundPath::new(project_path, abs_path)))); + return Some(Task::ready(Some(FoundPath::new(project_path, abs_path?)))); } let abs_path = abs_path?; if project.is_local() { let fs = fs.clone(); Some(cx.background_spawn(async move { if fs.is_file(&abs_path).await { - Some(FoundPath::new(project_path, Some(abs_path))) + Some(FoundPath::new(project_path, abs_path)) } else { None } })) } else { - Some(Task::ready(Some(FoundPath::new( - project_path, - Some(abs_path), - )))) + Some(Task::ready(Some(FoundPath::new(project_path, abs_path)))) } }) .collect::>(); @@ -465,7 +466,7 @@ enum Match { } impl Match { - fn relative_path(&self) -> Option<&Arc> { + fn relative_path(&self) -> Option<&Arc> { match self { Match::History { path, .. } => Some(&path.project.path), Match::Search(panel_match) => Some(&panel_match.0.path), @@ -475,20 +476,14 @@ impl Match { fn abs_path(&self, project: &Entity, cx: &App) -> Option { match self { - Match::History { path, .. } => path.absolute.clone().or_else(|| { + Match::History { path, .. } => Some(path.absolute.clone()), + Match::Search(ProjectPanelOrdMatch(path_match)) => Some( project .read(cx) - .worktree_for_id(path.project.worktree_id, cx)? + .worktree_for_id(WorktreeId::from_usize(path_match.worktree_id), cx)? .read(cx) - .absolutize(&path.project.path) - .ok() - }), - Match::Search(ProjectPanelOrdMatch(path_match)) => project - .read(cx) - .worktree_for_id(WorktreeId::from_usize(path_match.worktree_id), cx)? - .read(cx) - .absolutize(&path_match.path) - .ok(), + .absolutize(&path_match.path), + ), Match::CreateNew(_) => None, } } @@ -671,10 +666,9 @@ impl Matches { } if let Some(filename) = panel_match.0.path.file_name() { - let path_str = panel_match.0.path.to_string_lossy(); - let filename_str = filename.to_string_lossy(); + let path_str = panel_match.0.path.as_str(); - if let Some(filename_pos) = path_str.rfind(&*filename_str) + if let Some(filename_pos) = path_str.rfind(filename) && panel_match.0.positions[0] >= filename_pos { let mut prev_position = panel_match.0.positions[0]; @@ -696,7 +690,7 @@ fn matching_history_items<'a>( history_items: impl IntoIterator, currently_opened: Option<&'a FoundPath>, query: &FileSearchQuery, -) -> HashMap, Match> { +) -> HashMap, Match> { let mut candidates_paths = HashMap::default(); let history_items_by_worktrees = history_items @@ -714,7 +708,7 @@ fn matching_history_items<'a>( .project .path .file_name()? - .to_string_lossy() + .to_string() .to_lowercase() .chars(), ), @@ -768,11 +762,11 @@ fn matching_history_items<'a>( #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct FoundPath { project: ProjectPath, - absolute: Option, + absolute: PathBuf, } impl FoundPath { - fn new(project: ProjectPath, absolute: Option) -> Self { + fn new(project: ProjectPath, absolute: PathBuf) -> Self { Self { project, absolute } } } @@ -944,47 +938,44 @@ impl FileFinderDelegate { extend_old_matches, ); - let filename = &query.raw_query; - let mut query_path = Path::new(filename); - // add option of creating new file only if path is relative - let available_worktree = self - .project - .read(cx) - .visible_worktrees(cx) - .filter(|worktree| !worktree.read(cx).is_single_file()) - .collect::>(); - let worktree_count = available_worktree.len(); - let mut expect_worktree = available_worktree.first().cloned(); - for worktree in available_worktree { - let worktree_root = worktree + let path_style = self.project.read(cx).path_style(cx); + let query_path = query.raw_query.as_str(); + if let Ok(mut query_path) = RelPath::from_std_path(Path::new(query_path), path_style) { + let available_worktree = self + .project .read(cx) - .abs_path() - .file_name() - .map_or(String::new(), |f| f.to_string_lossy().to_string()); - if worktree_count > 1 && query_path.starts_with(&worktree_root) { - query_path = query_path - .strip_prefix(&worktree_root) - .unwrap_or(query_path); - expect_worktree = Some(worktree); - break; + .visible_worktrees(cx) + .filter(|worktree| !worktree.read(cx).is_single_file()) + .collect::>(); + let worktree_count = available_worktree.len(); + let mut expect_worktree = available_worktree.first().cloned(); + for worktree in available_worktree { + let worktree_root = worktree.read(cx).root_name(); + if worktree_count > 1 { + if let Ok(suffix) = query_path.strip_prefix(worktree_root) { + query_path = suffix.into(); + expect_worktree = Some(worktree); + break; + } + } } - } - if let Some(FoundPath { ref project, .. }) = self.currently_opened_path { - let worktree_id = project.worktree_id; - expect_worktree = self.project.read(cx).worktree_for_id(worktree_id, cx); - } + if let Some(FoundPath { ref project, .. }) = self.currently_opened_path { + let worktree_id = project.worktree_id; + expect_worktree = self.project.read(cx).worktree_for_id(worktree_id, cx); + } - if let Some(worktree) = expect_worktree { - let worktree = worktree.read(cx); - if query_path.is_relative() - && worktree.entry_for_path(&query_path).is_none() - && !filename.ends_with("/") - { - self.matches.matches.push(Match::CreateNew(ProjectPath { - worktree_id: worktree.id(), - path: Arc::from(query_path), - })); + if let Some(worktree) = expect_worktree { + let worktree = worktree.read(cx); + if worktree.entry_for_path(&query_path).is_none() + && !query.raw_query.ends_with("/") + && !(path_style.is_windows() && query.raw_query.ends_with("\\")) + { + self.matches.matches.push(Match::CreateNew(ProjectPath { + worktree_id: worktree.id(), + path: query_path, + })); + } } } @@ -1009,8 +1000,8 @@ impl FileFinderDelegate { path_match: &Match, window: &mut Window, cx: &App, - ix: usize, ) -> (HighlightedLabel, HighlightedLabel) { + let path_style = self.project.read(cx).path_style(cx); let (file_name, file_name_positions, mut full_path, mut full_path_positions) = match &path_match { Match::History { @@ -1018,68 +1009,52 @@ impl FileFinderDelegate { panel_match, } => { let worktree_id = entry_path.project.worktree_id; - let project_relative_path = &entry_path.project.path; - let has_worktree = self + let worktree = self .project .read(cx) .worktree_for_id(worktree_id, cx) - .is_some(); - - if let Some(absolute_path) = - entry_path.absolute.as_ref().filter(|_| !has_worktree) - { + .filter(|worktree| worktree.read(cx).is_visible()); + + if let Some(panel_match) = panel_match { + self.labels_for_path_match(&panel_match.0, path_style) + } else if let Some(worktree) = worktree { + let full_path = + worktree.read(cx).root_name().join(&entry_path.project.path); + let mut components = full_path.components(); + let filename = components.next_back().unwrap_or(""); + let prefix = components.rest(); ( - absolute_path - .file_name() - .map_or_else( - || project_relative_path.to_string_lossy(), - |file_name| file_name.to_string_lossy(), - ) - .to_string(), + filename.to_string(), Vec::new(), - absolute_path.to_string_lossy().to_string(), + prefix.display(path_style).to_string() + path_style.separator(), Vec::new(), ) } else { - let mut path = Arc::clone(project_relative_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 { - score: ix as f64, - positions: Vec::new(), - worktree_id: worktree_id.to_usize(), - path, - is_dir: false, // File finder doesn't support directories - path_prefix: "".into(), - distance_to_relative_ancestor: usize::MAX, - }; - if let Some(found_path_match) = &panel_match { - path_match - .positions - .extend(found_path_match.0.positions.iter()) - } - - self.labels_for_path_match(&path_match) + ( + entry_path + .absolute + .file_name() + .map_or(String::new(), |f| f.to_string_lossy().into_owned()), + Vec::new(), + entry_path.absolute.parent().map_or(String::new(), |path| { + path.to_string_lossy().into_owned() + path_style.separator() + }), + Vec::new(), + ) } } - Match::Search(path_match) => self.labels_for_path_match(&path_match.0), + Match::Search(path_match) => self.labels_for_path_match(&path_match.0, path_style), Match::CreateNew(project_path) => ( - format!("Create file: {}", project_path.path.display()), + format!("Create file: {}", project_path.path.display(path_style)), vec![], String::from(""), vec![], ), }; - 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) { + if file_name_positions.is_empty() { + let user_home_path = util::paths::home_dir().to_string_lossy(); + 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() { @@ -1147,17 +1122,13 @@ impl FileFinderDelegate { fn labels_for_path_match( &self, path_match: &PathMatch, + path_style: PathStyle, ) -> (String, Vec, String, Vec) { - let path = &path_match.path; - let path_string = path.to_string_lossy(); - let full_path = [path_match.path_prefix.as_ref(), path_string.as_ref()].join(""); + let full_path = path_match.path_prefix.join(&path_match.path); let mut path_positions = path_match.positions.clone(); - let file_name = path.file_name().map_or_else( - || path_match.path_prefix.to_string(), - |file_name| file_name.to_string_lossy().to_string(), - ); - let file_name_start = path_match.path_prefix.len() + path_string.len() - file_name.len(); + let file_name = full_path.file_name().unwrap_or(""); + let file_name_start = full_path.as_str().len() - file_name.len(); let file_name_positions = path_positions .iter() .filter_map(|pos| { @@ -1167,12 +1138,33 @@ impl FileFinderDelegate { None } }) - .collect(); + .collect::>(); - let full_path = full_path.trim_end_matches(&file_name).to_string(); + let full_path = full_path + .display(path_style) + .trim_end_matches(&file_name) + .to_string(); path_positions.retain(|idx| *idx < full_path.len()); - (file_name, file_name_positions, full_path, path_positions) + debug_assert!( + file_name_positions + .iter() + .all(|ix| file_name[*ix..].chars().next().is_some()), + "invalid file name positions {file_name:?} {file_name_positions:?}" + ); + debug_assert!( + path_positions + .iter() + .all(|ix| full_path[*ix..].chars().next().is_some()), + "invalid path positions {full_path:?} {path_positions:?}" + ); + + ( + file_name.to_string(), + file_name_positions, + full_path, + path_positions, + ) } fn lookup_absolute_path( @@ -1210,8 +1202,8 @@ impl FileFinderDelegate { score: 1.0, positions: Vec::new(), worktree_id: worktree.read(cx).id().to_usize(), - path: Arc::from(relative_path), - path_prefix: "".into(), + path: relative_path, + path_prefix: RelPath::empty().into(), is_dir: false, // File finder doesn't support directories distance_to_relative_ancestor: usize::MAX, })); @@ -1333,7 +1325,7 @@ impl PickerDelegate for FileFinderDelegate { .all(|worktree| { worktree .read(cx) - .entry_for_path(Path::new("a")) + .entry_for_path(RelPath::new("a").unwrap()) .is_none_or(|entry| !entry.is_dir()) }) { @@ -1351,7 +1343,7 @@ impl PickerDelegate for FileFinderDelegate { .all(|worktree| { worktree .read(cx) - .entry_for_path(Path::new("b")) + .entry_for_path(RelPath::new("b").unwrap()) .is_none_or(|entry| !entry.is_dir()) }) { @@ -1381,8 +1373,8 @@ impl PickerDelegate for FileFinderDelegate { project .worktree_for_id(history_item.project.worktree_id, cx) .is_some() - || ((project.is_local() || project.is_via_remote_server()) - && history_item.absolute.is_some()) + || project.is_local() + || project.is_via_remote_server() }), self.currently_opened_path.as_ref(), None, @@ -1397,13 +1389,7 @@ impl PickerDelegate for FileFinderDelegate { Task::ready(()) } else { let path_position = PathWithPosition::parse_str(raw_query); - - #[cfg(windows)] - let raw_query = raw_query.trim().to_owned().replace("/", "\\"); - #[cfg(not(windows))] - let raw_query = raw_query.trim(); - - let raw_query = raw_query.trim_end_matches(':').to_owned(); + let raw_query = raw_query.trim().trim_end_matches(':').to_owned(); let path = path_position.path.to_str(); let path_trimmed = path.unwrap_or(&raw_query).trim_end_matches(':'); let file_query_end = if path_trimmed == raw_query { @@ -1505,38 +1491,18 @@ impl PickerDelegate for FileFinderDelegate { window, cx, ) + } else if secondary { + workspace.split_abs_path(path.absolute.clone(), false, 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, - ), - } + workspace.open_abs_path( + path.absolute.clone(), + OpenOptions { + visible: Some(OpenVisible::None), + ..Default::default() + }, + window, + cx, + ) } } Match::Search(m) => split_or_open( @@ -1615,7 +1581,7 @@ impl PickerDelegate for FileFinderDelegate { .size(IconSize::Small) .into_any_element(), }; - let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx, ix); + let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx); let file_icon = maybe!({ if !settings.file_icons { diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index cd0f203d6a300b4039df74a646bf0a9d56818347..75b2101101bcdddf4112f6ea1f3d864f71924aa2 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -4,10 +4,10 @@ use super::*; use editor::Editor; use gpui::{Entity, TestAppContext, VisualTestContext}; use menu::{Confirm, SelectNext, SelectPrevious}; -use pretty_assertions::assert_eq; +use pretty_assertions::{assert_eq, assert_matches}; use project::{FS_WATCH_LATENCY, RemoveOptions}; use serde_json::json; -use util::path; +use util::{path, rel_path::rel_path}; use workspace::{AppState, CloseActiveItem, OpenOptions, ToggleFileFinder, Workspace}; #[ctor::ctor] @@ -77,8 +77,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 0.5, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("b0.5")), - path_prefix: Arc::default(), + path: rel_path("b0.5").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -86,8 +86,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 1.0, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("c1.0")), - path_prefix: Arc::default(), + path: rel_path("c1.0").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -95,8 +95,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 1.0, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("a1.0")), - path_prefix: Arc::default(), + path: rel_path("a1.0").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -104,8 +104,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 0.5, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("a0.5")), - path_prefix: Arc::default(), + path: rel_path("a0.5").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -113,8 +113,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 1.0, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("b1.0")), - path_prefix: Arc::default(), + path: rel_path("b1.0").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -128,8 +128,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 1.0, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("a1.0")), - path_prefix: Arc::default(), + path: rel_path("a1.0").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -137,8 +137,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 1.0, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("b1.0")), - path_prefix: Arc::default(), + path: rel_path("b1.0").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -146,8 +146,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 1.0, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("c1.0")), - path_prefix: Arc::default(), + path: rel_path("c1.0").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -155,8 +155,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 0.5, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("a0.5")), - path_prefix: Arc::default(), + path: rel_path("a0.5").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -164,8 +164,8 @@ fn test_custom_project_search_ordering_in_file_finder() { score: 0.5, positions: Vec::new(), worktree_id: 0, - path: Arc::from(Path::new("b0.5")), - path_prefix: Arc::default(), + path: rel_path("b0.5").into(), + path_prefix: rel_path("").into(), distance_to_relative_ancestor: 0, is_dir: false, }), @@ -366,7 +366,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) { picker.update(cx, |picker, _| { assert_eq!( collect_search_matches(picker).search_paths_only(), - vec![PathBuf::from("a/b/file2.txt")], + vec![rel_path("a/b/file2.txt").into()], "Matching abs path should be the only match" ) }); @@ -388,7 +388,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) { picker.update(cx, |picker, _| { assert_eq!( collect_search_matches(picker).search_paths_only(), - Vec::::new(), + Vec::new(), "Mismatching abs path should produce no matches" ) }); @@ -421,7 +421,7 @@ async fn test_complex_path(cx: &mut TestAppContext) { assert_eq!(picker.delegate.matches.len(), 2); assert_eq!( collect_search_matches(picker).search_paths_only(), - vec![PathBuf::from("其他/S数据表格/task.xlsx")], + vec![rel_path("其他/S数据表格/task.xlsx").into()], ) }); cx.dispatch_action(Confirm); @@ -713,13 +713,13 @@ async fn test_ignored_root(cx: &mut TestAppContext) { assert_eq!( matches.search, vec![ - PathBuf::from("ignored-root/hi"), - PathBuf::from("tracked-root/hi"), - PathBuf::from("ignored-root/hiccup"), - PathBuf::from("tracked-root/hiccup"), - PathBuf::from("ignored-root/height"), - PathBuf::from("ignored-root/happiness"), - PathBuf::from("tracked-root/happiness"), + rel_path("ignored-root/hi").into(), + rel_path("tracked-root/hi").into(), + rel_path("ignored-root/hiccup").into(), + rel_path("tracked-root/hiccup").into(), + rel_path("ignored-root/height").into(), + rel_path("ignored-root/happiness").into(), + rel_path("tracked-root/happiness").into(), ], "All ignored files that were indexed are found for default ignored mode" ); @@ -738,14 +738,14 @@ async fn test_ignored_root(cx: &mut TestAppContext) { assert_eq!( matches.search, vec![ - PathBuf::from("ignored-root/hi"), - PathBuf::from("tracked-root/hi"), - PathBuf::from("ignored-root/hiccup"), - PathBuf::from("tracked-root/hiccup"), - PathBuf::from("ignored-root/height"), - PathBuf::from("tracked-root/height"), - PathBuf::from("ignored-root/happiness"), - PathBuf::from("tracked-root/happiness"), + rel_path("ignored-root/hi").into(), + rel_path("tracked-root/hi").into(), + rel_path("ignored-root/hiccup").into(), + rel_path("tracked-root/hiccup").into(), + rel_path("ignored-root/height").into(), + rel_path("tracked-root/height").into(), + rel_path("ignored-root/happiness").into(), + rel_path("tracked-root/happiness").into(), ], "All ignored files should be found, for the toggled on ignored mode" ); @@ -765,9 +765,9 @@ async fn test_ignored_root(cx: &mut TestAppContext) { assert_eq!( matches.search, vec![ - PathBuf::from("tracked-root/hi"), - PathBuf::from("tracked-root/hiccup"), - PathBuf::from("tracked-root/happiness"), + rel_path("tracked-root/hi").into(), + rel_path("tracked-root/hiccup").into(), + rel_path("tracked-root/happiness").into(), ], "Only non-ignored files should be found for the turned off ignored mode" ); @@ -812,13 +812,13 @@ async fn test_ignored_root(cx: &mut TestAppContext) { assert_eq!( matches.search, vec![ - PathBuf::from("ignored-root/hi"), - PathBuf::from("tracked-root/hi"), - PathBuf::from("ignored-root/hiccup"), - PathBuf::from("tracked-root/hiccup"), - PathBuf::from("ignored-root/height"), - PathBuf::from("ignored-root/happiness"), - PathBuf::from("tracked-root/happiness"), + rel_path("ignored-root/hi").into(), + rel_path("tracked-root/hi").into(), + rel_path("ignored-root/hiccup").into(), + rel_path("tracked-root/hiccup").into(), + rel_path("ignored-root/height").into(), + rel_path("ignored-root/happiness").into(), + rel_path("tracked-root/happiness").into(), ], "Only for the worktree with the ignored root, all indexed ignored files are found in the auto ignored mode" ); @@ -838,16 +838,16 @@ async fn test_ignored_root(cx: &mut TestAppContext) { assert_eq!( matches.search, vec![ - PathBuf::from("ignored-root/hi"), - PathBuf::from("tracked-root/hi"), - PathBuf::from("ignored-root/hiccup"), - PathBuf::from("tracked-root/hiccup"), - PathBuf::from("ignored-root/height"), - PathBuf::from("tracked-root/height"), - PathBuf::from("tracked-root/heights/height_1"), - PathBuf::from("tracked-root/heights/height_2"), - PathBuf::from("ignored-root/happiness"), - PathBuf::from("tracked-root/happiness"), + rel_path("ignored-root/hi").into(), + rel_path("tracked-root/hi").into(), + rel_path("ignored-root/hiccup").into(), + rel_path("tracked-root/hiccup").into(), + rel_path("ignored-root/height").into(), + rel_path("tracked-root/height").into(), + rel_path("tracked-root/heights/height_1").into(), + rel_path("tracked-root/heights/height_2").into(), + rel_path("ignored-root/happiness").into(), + rel_path("tracked-root/happiness").into(), ], "All ignored files that were indexed are found in the turned on ignored mode" ); @@ -867,9 +867,9 @@ async fn test_ignored_root(cx: &mut TestAppContext) { assert_eq!( matches.search, vec![ - PathBuf::from("tracked-root/hi"), - PathBuf::from("tracked-root/hiccup"), - PathBuf::from("tracked-root/happiness"), + rel_path("tracked-root/hi").into(), + rel_path("tracked-root/hiccup").into(), + rel_path("tracked-root/happiness").into(), ], "Only non-ignored files should be found for the turned off ignored mode" ); @@ -910,7 +910,7 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) { assert_eq!(matches.len(), 1); let (file_name, file_name_positions, full_path, full_path_positions) = - delegate.labels_for_path_match(&matches[0]); + delegate.labels_for_path_match(&matches[0], PathStyle::local()); assert_eq!(file_name, "the-file"); assert_eq!(file_name_positions, &[0, 1, 4]); assert_eq!(full_path, ""); @@ -968,7 +968,7 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) { let b_path = ProjectPath { worktree_id: worktree_id2, - path: Arc::from(Path::new(path!("the-parent-dirb/fileb"))), + path: rel_path("the-parent-dirb/fileb").into(), }; workspace .update_in(cx, |workspace, window, cx| { @@ -1001,7 +1001,7 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) { project_path, Some(ProjectPath { worktree_id: worktree_id2, - path: Arc::from(Path::new(path!("the-parent-dirb/filec"))) + path: rel_path("the-parent-dirb/filec").into() }) ); }); @@ -1038,10 +1038,7 @@ async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppCon let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (_worktree_id1, worktree_id2) = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); - ( - WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize), - WorktreeId::from_usize(worktrees[1].entity_id().as_u64() as usize), - ) + (worktrees[0].read(cx).id(), worktrees[1].read(cx).id()) }); let finder = open_file_picker(&workspace, cx); @@ -1065,7 +1062,7 @@ async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppCon project_path, Some(ProjectPath { worktree_id: worktree_id2, - path: Arc::from(Path::new("filec")) + path: rel_path("filec").into() }) ); }); @@ -1103,7 +1100,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) { // so that one should be sorted earlier let b_path = ProjectPath { worktree_id, - path: Arc::from(Path::new("dir2/b.txt")), + path: rel_path("dir2/b.txt").into(), }; workspace .update_in(cx, |workspace, window, cx| { @@ -1121,8 +1118,8 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) { finder.update(cx, |picker, _| { let matches = collect_search_matches(picker).search_paths_only(); - assert_eq!(matches[0].as_path(), Path::new("dir2/a.txt")); - assert_eq!(matches[1].as_path(), Path::new("dir1/a.txt")); + assert_eq!(matches[0].as_ref(), rel_path("dir2/a.txt")); + assert_eq!(matches[1].as_ref(), rel_path("dir1/a.txt")); }); } @@ -1207,9 +1204,9 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) { vec![FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/first.rs")), + path: rel_path("test/first.rs").into(), }, - Some(PathBuf::from(path!("/src/test/first.rs"))) + PathBuf::from(path!("/src/test/first.rs")) )], "Should show 1st opened item in the history when opening the 2nd item" ); @@ -1222,16 +1219,16 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) { FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/second.rs")), + path: rel_path("test/second.rs").into(), }, - Some(PathBuf::from(path!("/src/test/second.rs"))) + PathBuf::from(path!("/src/test/second.rs")) ), FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/first.rs")), + path: rel_path("test/first.rs").into(), }, - Some(PathBuf::from(path!("/src/test/first.rs"))) + PathBuf::from(path!("/src/test/first.rs")) ), ], "Should show 1st and 2nd opened items in the history when opening the 3rd item. \ @@ -1246,23 +1243,23 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) { FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/third.rs")), + path: rel_path("test/third.rs").into(), }, - Some(PathBuf::from(path!("/src/test/third.rs"))) + PathBuf::from(path!("/src/test/third.rs")) ), FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/second.rs")), + path: rel_path("test/second.rs").into(), }, - Some(PathBuf::from(path!("/src/test/second.rs"))) + PathBuf::from(path!("/src/test/second.rs")) ), FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/first.rs")), + path: rel_path("test/first.rs").into(), }, - Some(PathBuf::from(path!("/src/test/first.rs"))) + PathBuf::from(path!("/src/test/first.rs")) ), ], "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \ @@ -1277,23 +1274,23 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) { FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/second.rs")), + path: rel_path("test/second.rs").into(), }, - Some(PathBuf::from(path!("/src/test/second.rs"))) + PathBuf::from(path!("/src/test/second.rs")) ), FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/third.rs")), + path: rel_path("test/third.rs").into(), }, - Some(PathBuf::from(path!("/src/test/third.rs"))) + PathBuf::from(path!("/src/test/third.rs")) ), FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/first.rs")), + path: rel_path("test/first.rs").into(), }, - Some(PathBuf::from(path!("/src/test/first.rs"))) + PathBuf::from(path!("/src/test/first.rs")) ), ], "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \ @@ -1301,6 +1298,62 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_history_match_positions(cx: &mut gpui::TestAppContext) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + path!("/src"), + json!({ + "test": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await; + let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + + workspace.update_in(cx, |_workspace, window, cx| window.focused(cx)); + + open_close_queried_buffer("efir", 1, "first.rs", &workspace, cx).await; + let history = open_close_queried_buffer("second", 1, "second.rs", &workspace, cx).await; + assert_eq!(history.len(), 1); + + let picker = open_file_picker(&workspace, cx); + cx.simulate_input("fir"); + picker.update_in(cx, |finder, window, cx| { + let matches = &finder.delegate.matches.matches; + assert_matches!( + matches.as_slice(), + [Match::History { .. }, Match::CreateNew { .. }] + ); + assert_eq!( + matches[0].panel_match().unwrap().0.path.as_ref(), + rel_path("test/first.rs") + ); + assert_eq!(matches[0].panel_match().unwrap().0.positions, &[5, 6, 7]); + + let (file_label, path_label) = + finder + .delegate + .labels_for_match(&finder.delegate.matches.matches[0], window, cx); + assert_eq!(file_label.text(), "first.rs"); + assert_eq!(file_label.highlight_indices(), &[0, 1, 2]); + assert_eq!( + path_label.text(), + format!("test{}", PathStyle::local().separator()) + ); + assert_eq!(path_label.highlight_indices(), &[] as &[usize]); + }); +} + #[gpui::test] async fn test_external_files_history(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); @@ -1392,9 +1445,9 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) { vec![FoundPath::new( ProjectPath { worktree_id: external_worktree_id, - path: Arc::from(Path::new("")), + path: rel_path("").into(), }, - Some(PathBuf::from(path!("/external-src/test/third.rs"))) + PathBuf::from(path!("/external-src/test/third.rs")) )], "Should show external file with its full path in the history after it was open" ); @@ -1407,16 +1460,16 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) { FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/second.rs")), + path: rel_path("test/second.rs").into(), }, - Some(PathBuf::from(path!("/src/test/second.rs"))) + PathBuf::from(path!("/src/test/second.rs")) ), FoundPath::new( ProjectPath { worktree_id: external_worktree_id, - path: Arc::from(Path::new("")), + path: rel_path("").into(), }, - Some(PathBuf::from(path!("/external-src/test/third.rs"))) + PathBuf::from(path!("/external-src/test/third.rs")) ), ], "Should keep external file with history updates", @@ -1529,12 +1582,12 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) { assert_eq!(history_match, &FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/first.rs")), + path: rel_path("test/first.rs").into(), }, - Some(PathBuf::from(path!("/src/test/first.rs"))) + PathBuf::from(path!("/src/test/first.rs")), )); assert_eq!(matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present"); - assert_eq!(matches.search.first().unwrap(), Path::new("test/fourth.rs")); + assert_eq!(matches.search.first().unwrap().as_ref(), rel_path("test/fourth.rs")); }); let second_query = "fsdasdsa"; @@ -1572,12 +1625,12 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) { assert_eq!(history_match, &FoundPath::new( ProjectPath { worktree_id, - path: Arc::from(Path::new("test/first.rs")), + path: rel_path("test/first.rs").into(), }, - Some(PathBuf::from(path!("/src/test/first.rs"))) + PathBuf::from(path!("/src/test/first.rs")) )); assert_eq!(matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query"); - assert_eq!(matches.search.first().unwrap(), Path::new("test/fourth.rs")); + assert_eq!(matches.search.first().unwrap().as_ref(), rel_path("test/fourth.rs")); }); } @@ -1626,13 +1679,16 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) { let search_matches = collect_search_matches(finder); assert_eq!( search_matches.history, - vec![PathBuf::from("test/1_qw"), PathBuf::from("test/6_qwqwqw"),], + vec![ + rel_path("test/1_qw").into(), + rel_path("test/6_qwqwqw").into() + ], ); assert_eq!( search_matches.search, vec![ - PathBuf::from("test/5_qwqwqw"), - PathBuf::from("test/7_qwqwqw"), + rel_path("test/5_qwqwqw").into(), + rel_path("test/7_qwqwqw").into() ], ); }); @@ -2083,10 +2139,10 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo assert_eq!( search_entries, vec![ - PathBuf::from("collab_ui/collab_ui.rs"), - PathBuf::from("collab_ui/first.rs"), - PathBuf::from("collab_ui/third.rs"), - PathBuf::from("collab_ui/second.rs"), + rel_path("collab_ui/collab_ui.rs").into(), + rel_path("collab_ui/first.rs").into(), + rel_path("collab_ui/third.rs").into(), + rel_path("collab_ui/second.rs").into(), ], "Despite all search results having the same directory name, the most matching one should be on top" ); @@ -2135,8 +2191,8 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext) assert_eq!( collect_search_matches(picker).history, vec![ - PathBuf::from("test/first.rs"), - PathBuf::from("test/third.rs"), + rel_path("test/first.rs").into(), + rel_path("test/third.rs").into(), ], "Should have all opened files in the history, except the ones that do not exist on disk" ); @@ -2766,15 +2822,15 @@ fn active_file_picker( #[derive(Debug, Default)] struct SearchEntries { - history: Vec, + history: Vec>, history_found_paths: Vec, - search: Vec, + search: Vec>, search_matches: Vec, } impl SearchEntries { #[track_caller] - fn search_paths_only(self) -> Vec { + fn search_paths_only(self) -> Vec> { assert!( self.history.is_empty(), "Should have no history matches, but got: {:?}", @@ -2802,20 +2858,15 @@ fn collect_search_matches(picker: &Picker) -> SearchEntries path: history_path, panel_match: path_match, } => { - search_entries.history.push( - path_match - .as_ref() - .map(|path_match| { - Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path) - }) - .unwrap_or_else(|| { - history_path - .absolute - .as_deref() - .unwrap_or_else(|| &history_path.project.path) - .to_path_buf() - }), - ); + if let Some(path_match) = path_match.as_ref() { + search_entries + .history + .push(path_match.0.path_prefix.join(&path_match.0.path)); + } else { + // This occurs when the query is empty and we show history matches + // that are outside the project. + panic!("currently not exercised in tests"); + } search_entries .history_found_paths .push(history_path.clone()); @@ -2823,7 +2874,7 @@ fn collect_search_matches(picker: &Picker) -> SearchEntries Match::Search(path_match) => { search_entries .search - .push(Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path)); + .push(path_match.0.path_prefix.join(&path_match.0.path)); search_entries.search_matches.push(path_match.0.clone()); } Match::CreateNew(_) => {} @@ -2858,12 +2909,11 @@ fn assert_match_at_position( .get(match_index) .unwrap_or_else(|| panic!("Finder has no match for index {match_index}")); let match_file_name = match &match_item { - Match::History { path, .. } => path.absolute.as_deref().unwrap().file_name(), + Match::History { path, .. } => path.absolute.file_name().and_then(|s| s.to_str()), Match::Search(path_match) => path_match.0.path.file_name(), Match::CreateNew(project_path) => project_path.path.file_name(), } - .unwrap() - .to_string_lossy(); + .unwrap(); assert_eq!(match_file_name, expected_file_name); } @@ -2901,11 +2951,11 @@ async fn test_filename_precedence(cx: &mut TestAppContext) { assert_eq!( search_matches, vec![ - PathBuf::from("routes/+layout.svelte"), - PathBuf::from("layout/app.css"), - PathBuf::from("layout/app.d.ts"), - PathBuf::from("layout/app.html"), - PathBuf::from("layout/+page.svelte"), + rel_path("routes/+layout.svelte").into(), + rel_path("layout/app.css").into(), + rel_path("layout/app.d.ts").into(), + rel_path("layout/app.html").into(), + rel_path("layout/+page.svelte").into(), ], "File with 'layout' in filename should be prioritized over files in 'layout' directory" ); diff --git a/crates/file_finder/src/open_path_prompt.rs b/crates/file_finder/src/open_path_prompt.rs index 63f2f37ab18cbd1ab210685eeda01ac535a8118f..b0417b1d13fc4f82c8b16b0ac87249405b6f4129 100644 --- a/crates/file_finder/src/open_path_prompt.rs +++ b/crates/file_finder/src/open_path_prompt.rs @@ -7,7 +7,7 @@ use picker::{Picker, PickerDelegate}; use project::{DirectoryItem, DirectoryLister}; use settings::Settings; use std::{ - path::{self, MAIN_SEPARATOR_STR, Path, PathBuf}, + path::{self, Path, PathBuf}, sync::{ Arc, atomic::{self, AtomicBool}, @@ -217,7 +217,7 @@ impl OpenPathPrompt { ) { workspace.toggle_modal(window, cx, |window, cx| { let delegate = - OpenPathDelegate::new(tx, lister.clone(), creating_path, PathStyle::current()); + OpenPathDelegate::new(tx, lister.clone(), creating_path, PathStyle::local()); let picker = Picker::uniform_list(delegate, window, cx).width(rems(34.)); let query = lister.default_query(cx); picker.set_query(query, window, cx); @@ -822,7 +822,7 @@ impl PickerDelegate for OpenPathDelegate { } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { - Arc::from(format!("[directory{MAIN_SEPARATOR_STR}]filename.ext")) + Arc::from(format!("[directory{}]filename.ext", self.path_style.separator()).as_str()) } fn separators_after_indices(&self) -> Vec { diff --git a/crates/file_finder/src/open_path_prompt_tests.rs b/crates/file_finder/src/open_path_prompt_tests.rs index fd7cc1c6c612d28b1cc8f2352f6dbb0a254e7e98..5e8874cd01e06bb05f4ff6918bc02ea6883ea064 100644 --- a/crates/file_finder/src/open_path_prompt_tests.rs +++ b/crates/file_finder/src/open_path_prompt_tests.rs @@ -37,7 +37,7 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) { let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await; - let (picker, cx) = build_open_path_prompt(project, false, PathStyle::current(), cx); + let (picker, cx) = build_open_path_prompt(project, false, PathStyle::local(), cx); insert_query(path!("sadjaoislkdjasldj"), &picker, cx).await; assert_eq!(collect_match_candidates(&picker, cx), Vec::::new()); @@ -119,7 +119,7 @@ async fn test_open_path_prompt_completion(cx: &mut TestAppContext) { let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await; - let (picker, cx) = build_open_path_prompt(project, false, PathStyle::current(), cx); + let (picker, cx) = build_open_path_prompt(project, false, PathStyle::local(), cx); // Confirm completion for the query "/root", since it's a directory, it should add a trailing slash. let query = path!("/root"); @@ -227,7 +227,7 @@ async fn test_open_path_prompt_on_windows(cx: &mut TestAppContext) { let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await; - let (picker, cx) = build_open_path_prompt(project, false, PathStyle::current(), cx); + let (picker, cx) = build_open_path_prompt(project, false, PathStyle::local(), cx); // Support both forward and backward slashes. let query = "C:/root/"; @@ -372,7 +372,7 @@ async fn test_new_path_prompt(cx: &mut TestAppContext) { let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await; - let (picker, cx) = build_open_path_prompt(project, true, PathStyle::current(), cx); + let (picker, cx) = build_open_path_prompt(project, true, PathStyle::local(), cx); insert_query(path!("/root"), &picker, cx).await; assert_eq!(collect_match_candidates(&picker, cx), vec!["root"]); diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index b608d0fec65a80057445fb3598102297f445ad4f..940210a7105a38baf472eafc4638f955b4acc6ae 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -17,6 +17,7 @@ use parking_lot::Mutex; use rope::Rope; use smol::future::FutureExt as _; use std::{path::PathBuf, sync::Arc}; +use util::{paths::PathStyle, rel_path::RelPath}; #[derive(Clone)] pub struct FakeGitRepository { @@ -82,7 +83,7 @@ impl GitRepository for FakeGitRepository { self.with_state_async(false, move |state| { state .index_contents - .get(path.as_ref()) + .get(&path) .context("not present in index") .cloned() }) @@ -97,7 +98,7 @@ impl GitRepository for FakeGitRepository { self.with_state_async(false, move |state| { state .head_contents - .get(path.as_ref()) + .get(&path) .context("not present in HEAD") .cloned() }) @@ -225,6 +226,7 @@ impl GitRepository for FakeGitRepository { .read_file_sync(path) .ok() .map(|content| String::from_utf8(content).unwrap())?; + let repo_path = RelPath::from_std_path(repo_path, PathStyle::local()).ok()?; Some((repo_path.into(), (content, is_ignored))) }) .collect(); @@ -386,7 +388,11 @@ impl GitRepository for FakeGitRepository { let contents = paths .into_iter() .map(|path| { - let abs_path = self.dot_git_path.parent().unwrap().join(&path); + let abs_path = self + .dot_git_path + .parent() + .unwrap() + .join(&path.as_std_path()); Box::pin(async move { (path.clone(), self.fs.load(&abs_path).await.ok()) }) }) .collect::>(); diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 198299617619363fa9d486042d1b803c3ede6f88..a1ee23cf5f33ea20d479d534d87596701dac1a16 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -47,7 +47,7 @@ use collections::{BTreeMap, btree_map}; use fake_git_repo::FakeGitRepositoryState; #[cfg(any(test, feature = "test-support"))] use git::{ - repository::RepoPath, + repository::{RepoPath, repo_path}, status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus}, }; #[cfg(any(test, feature = "test-support"))] @@ -1608,13 +1608,13 @@ impl FakeFs { .unwrap(); } - pub fn set_index_for_repo(&self, dot_git: &Path, index_state: &[(RepoPath, String)]) { + pub fn set_index_for_repo(&self, dot_git: &Path, index_state: &[(&str, String)]) { self.with_git_state(dot_git, true, |state| { state.index_contents.clear(); state.index_contents.extend( index_state .iter() - .map(|(path, content)| (path.clone(), content.clone())), + .map(|(path, content)| (repo_path(path), content.clone())), ); }) .unwrap(); @@ -1623,7 +1623,7 @@ impl FakeFs { pub fn set_head_for_repo( &self, dot_git: &Path, - head_state: &[(RepoPath, String)], + head_state: &[(&str, String)], sha: impl Into, ) { self.with_git_state(dot_git, true, |state| { @@ -1631,50 +1631,22 @@ impl FakeFs { state.head_contents.extend( head_state .iter() - .map(|(path, content)| (path.clone(), content.clone())), + .map(|(path, content)| (repo_path(path), content.clone())), ); state.refs.insert("HEAD".into(), sha.into()); }) .unwrap(); } - pub fn set_git_content_for_repo( - &self, - dot_git: &Path, - head_state: &[(RepoPath, String, Option)], - ) { + pub fn set_head_and_index_for_repo(&self, dot_git: &Path, contents_by_path: &[(&str, String)]) { self.with_git_state(dot_git, true, |state| { state.head_contents.clear(); state.head_contents.extend( - head_state + contents_by_path .iter() - .map(|(path, head_content, _)| (path.clone(), head_content.clone())), + .map(|(path, contents)| (repo_path(path), contents.clone())), ); - state.index_contents.clear(); - state.index_contents.extend(head_state.iter().map( - |(path, head_content, index_content)| { - ( - path.clone(), - index_content.as_ref().unwrap_or(head_content).clone(), - ) - }, - )); - }) - .unwrap(); - } - - pub fn set_head_and_index_for_repo( - &self, - dot_git: &Path, - contents_by_path: &[(RepoPath, String)], - ) { - self.with_git_state(dot_git, true, |state| { - state.head_contents.clear(); - state.index_contents.clear(); - state.head_contents.extend(contents_by_path.iter().cloned()); - state - .index_contents - .extend(contents_by_path.iter().cloned()); + state.index_contents = state.head_contents.clone(); }) .unwrap(); } @@ -1689,7 +1661,7 @@ impl FakeFs { /// Put the given git repository into a state with the given status, /// by mutating the head, index, and unmerged state. - pub fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, FileStatus)]) { + pub fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&str, FileStatus)]) { let workdir_path = dot_git.parent().unwrap(); let workdir_contents = self.files_with_contents(workdir_path); self.with_git_state(dot_git, true, |state| { @@ -1697,10 +1669,12 @@ impl FakeFs { state.head_contents.clear(); state.unmerged_paths.clear(); for (path, content) in workdir_contents { - let repo_path: RepoPath = path.strip_prefix(&workdir_path).unwrap().into(); + use util::{paths::PathStyle, rel_path::RelPath}; + + let repo_path: RepoPath = RelPath::from_std_path(path.strip_prefix(&workdir_path).unwrap(), PathStyle::local()).unwrap().into(); let status = statuses .iter() - .find_map(|(p, status)| (**p == *repo_path.0).then_some(status)); + .find_map(|(p, status)| (*p == repo_path.as_str()).then_some(status)); let mut content = String::from_utf8_lossy(&content).to_string(); let mut index_content = None; diff --git a/crates/fuzzy/Cargo.toml b/crates/fuzzy/Cargo.toml index 534d7d4db5bc2637f7b093f67cead7a3fa52b416..35e134236d619e51467ef96a204df3fc8cc7681c 100644 --- a/crates/fuzzy/Cargo.toml +++ b/crates/fuzzy/Cargo.toml @@ -17,3 +17,6 @@ gpui.workspace = true util.workspace = true log.workspace = true workspace-hack.workspace = true + +[dev-dependencies] +util = {workspace = true, features = ["test-support"]} diff --git a/crates/fuzzy/src/matcher.rs b/crates/fuzzy/src/matcher.rs index 88253d4848b4b3866b9380256eccf1826213cfd1..eb844e349821394785bb61a34600f04a6fa985eb 100644 --- a/crates/fuzzy/src/matcher.rs +++ b/crates/fuzzy/src/matcher.rs @@ -1,5 +1,5 @@ use std::{ - borrow::{Borrow, Cow}, + borrow::Borrow, collections::BTreeMap, sync::atomic::{self, AtomicBool}, }; @@ -27,7 +27,7 @@ pub struct Matcher<'a> { pub trait MatchCandidate { fn has_chars(&self, bag: CharBag) -> bool; - fn to_string(&self) -> Cow<'_, str>; + fn candidate_chars(&self) -> impl Iterator; } impl<'a> Matcher<'a> { @@ -83,7 +83,7 @@ impl<'a> Matcher<'a> { candidate_chars.clear(); lowercase_candidate_chars.clear(); extra_lowercase_chars.clear(); - for (i, c) in candidate.borrow().to_string().chars().enumerate() { + for (i, c) in candidate.borrow().candidate_chars().enumerate() { candidate_chars.push(c); let mut char_lowercased = c.to_lowercase().collect::>(); if char_lowercased.len() > 1 { @@ -202,8 +202,6 @@ impl<'a> Matcher<'a> { cur_score: f64, extra_lowercase_chars: &BTreeMap, ) -> f64 { - use std::path::MAIN_SEPARATOR; - if query_idx == self.query.len() { return 1.0; } @@ -245,17 +243,11 @@ impl<'a> Matcher<'a> { None => continue, } }; - let is_path_sep = path_char == MAIN_SEPARATOR; + let is_path_sep = path_char == '/'; if query_idx == 0 && is_path_sep { last_slash = j_regular; } - - #[cfg(not(target_os = "windows"))] - let need_to_score = - query_char == path_char || (is_path_sep && query_char == '_' || query_char == '\\'); - // `query_char == '\\'` breaks `test_match_path_entries` on Windows, `\` is only used as a path separator on Windows. - #[cfg(target_os = "windows")] let need_to_score = query_char == path_char || (is_path_sep && query_char == '_'); if need_to_score { let curr = match prefix.get(j_regular) { @@ -270,7 +262,7 @@ impl<'a> Matcher<'a> { None => path[j_regular - 1 - prefix.len()], }; - if last == MAIN_SEPARATOR { + if last == '/' { char_score = 0.9; } else if (last == '-' || last == '_' || last == ' ' || last.is_numeric()) || (last.is_lowercase() && curr.is_uppercase()) @@ -291,7 +283,7 @@ impl<'a> Matcher<'a> { // Apply a severe penalty if the case doesn't match. // This will make the exact matches have higher score than the case-insensitive and the // path insensitive matches. - if (self.smart_case || curr == MAIN_SEPARATOR) && self.query[query_idx] != curr { + if (self.smart_case || curr == '/') && self.query[query_idx] != curr { char_score *= 0.001; } @@ -348,13 +340,12 @@ impl<'a> Matcher<'a> { #[cfg(test)] mod tests { + use util::rel_path::{RelPath, rel_path}; + use crate::{PathMatch, PathMatchCandidate}; use super::*; - use std::{ - path::{Path, PathBuf}, - sync::Arc, - }; + use std::sync::Arc; #[test] fn test_get_last_positions() { @@ -376,7 +367,6 @@ mod tests { assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]); } - #[cfg(not(target_os = "windows"))] #[test] fn test_match_path_entries() { let paths = vec![ @@ -388,9 +378,9 @@ mod tests { "alphabravocharlie", "AlphaBravoCharlie", "thisisatestdir", - "/////ThisIsATestDir", - "/this/is/a/test/dir", - "/test/tiatd", + "ThisIsATestDir", + "this/is/a/test/dir", + "test/tiatd", ]; assert_eq!( @@ -404,63 +394,15 @@ mod tests { ); assert_eq!( match_single_path_query("t/i/a/t/d", false, &paths), - vec![("/this/is/a/test/dir", vec![1, 5, 6, 8, 9, 10, 11, 15, 16]),] - ); - - assert_eq!( - match_single_path_query("tiatd", false, &paths), - vec![ - ("/test/tiatd", vec![6, 7, 8, 9, 10]), - ("/this/is/a/test/dir", vec![1, 6, 9, 11, 16]), - ("/////ThisIsATestDir", vec![5, 9, 11, 12, 16]), - ("thisisatestdir", vec![0, 2, 6, 7, 11]), - ] - ); - } - - /// todo(windows) - /// Now, on Windows, users can only use the backslash as a path separator. - /// I do want to support both the backslash and the forward slash as path separators on Windows. - #[cfg(target_os = "windows")] - #[test] - fn test_match_path_entries() { - let paths = vec![ - "", - "a", - "ab", - "abC", - "abcd", - "alphabravocharlie", - "AlphaBravoCharlie", - "thisisatestdir", - "\\\\\\\\\\ThisIsATestDir", - "\\this\\is\\a\\test\\dir", - "\\test\\tiatd", - ]; - - assert_eq!( - match_single_path_query("abc", false, &paths), - vec![ - ("abC", vec![0, 1, 2]), - ("abcd", vec![0, 1, 2]), - ("AlphaBravoCharlie", vec![0, 5, 10]), - ("alphabravocharlie", vec![4, 5, 10]), - ] - ); - assert_eq!( - match_single_path_query("t\\i\\a\\t\\d", false, &paths), - vec![( - "\\this\\is\\a\\test\\dir", - vec![1, 5, 6, 8, 9, 10, 11, 15, 16] - ),] + vec![("this/is/a/test/dir", vec![0, 4, 5, 7, 8, 9, 10, 14, 15]),] ); assert_eq!( match_single_path_query("tiatd", false, &paths), vec![ - ("\\test\\tiatd", vec![6, 7, 8, 9, 10]), - ("\\this\\is\\a\\test\\dir", vec![1, 6, 9, 11, 16]), - ("\\\\\\\\\\ThisIsATestDir", vec![5, 9, 11, 12, 16]), + ("test/tiatd", vec![5, 6, 7, 8, 9]), + ("ThisIsATestDir", vec![0, 4, 6, 7, 11]), + ("this/is/a/test/dir", vec![0, 5, 8, 10, 15]), ("thisisatestdir", vec![0, 2, 6, 7, 11]), ] ); @@ -491,7 +433,7 @@ mod tests { "aαbβ/cγdδ", "αβγδ/bcde", "c1️⃣2️⃣3️⃣/d4️⃣5️⃣6️⃣/e7️⃣8️⃣9️⃣/f", - "/d/🆒/h", + "d/🆒/h", ]; assert_eq!("1️⃣".len(), 7); assert_eq!( @@ -602,9 +544,9 @@ mod tests { let query = query.chars().collect::>(); let query_chars = CharBag::from(&lowercase_query[..]); - let path_arcs: Vec> = paths + let path_arcs: Vec> = paths .iter() - .map(|path| Arc::from(PathBuf::from(path))) + .map(|path| Arc::from(rel_path(path))) .collect::>(); let mut path_entries = Vec::new(); for (i, path) in paths.iter().enumerate() { @@ -632,8 +574,8 @@ mod tests { score, worktree_id: 0, positions: positions.clone(), - path: Arc::from(candidate.path), - path_prefix: "".into(), + path: candidate.path.into(), + path_prefix: RelPath::empty().into(), distance_to_relative_ancestor: usize::MAX, is_dir: false, }, @@ -647,7 +589,7 @@ mod tests { paths .iter() .copied() - .find(|p| result.path.as_ref() == Path::new(p)) + .find(|p| result.path.as_ref() == rel_path(p)) .unwrap(), result.positions, ) diff --git a/crates/fuzzy/src/paths.rs b/crates/fuzzy/src/paths.rs index de6284e957a5320b5eac15ad4ff23a8c4ff5b420..fa6d3f850465e62106d2b84f8f0a9be56c4ca19d 100644 --- a/crates/fuzzy/src/paths.rs +++ b/crates/fuzzy/src/paths.rs @@ -1,13 +1,12 @@ use gpui::BackgroundExecutor; use std::{ - borrow::Cow, cmp::{self, Ordering}, - path::Path, sync::{ Arc, atomic::{self, AtomicBool}, }, }; +use util::{paths::PathStyle, rel_path::RelPath}; use crate::{ CharBag, @@ -17,7 +16,7 @@ use crate::{ #[derive(Clone, Debug)] pub struct PathMatchCandidate<'a> { pub is_dir: bool, - pub path: &'a Path, + pub path: &'a RelPath, pub char_bag: CharBag, } @@ -26,8 +25,8 @@ pub struct PathMatch { pub score: f64, pub positions: Vec, pub worktree_id: usize, - pub path: Arc, - pub path_prefix: Arc, + pub path: Arc, + pub path_prefix: Arc, pub is_dir: bool, /// Number of steps removed from a shared parent with the relative path /// Used to order closer paths first in the search list @@ -41,8 +40,10 @@ pub trait PathMatchCandidateSet<'a>: Send + Sync { fn is_empty(&self) -> bool { self.len() == 0 } - fn prefix(&self) -> Arc; + fn root_is_file(&self) -> bool; + fn prefix(&self) -> Arc; fn candidates(&'a self, start: usize) -> Self::Candidates; + fn path_style(&self) -> PathStyle; } impl<'a> MatchCandidate for PathMatchCandidate<'a> { @@ -50,8 +51,8 @@ impl<'a> MatchCandidate for PathMatchCandidate<'a> { self.char_bag.is_superset(bag) } - fn to_string(&self) -> Cow<'a, str> { - self.path.to_string_lossy() + fn candidate_chars(&self) -> impl Iterator { + self.path.as_str().chars() } } @@ -109,8 +110,8 @@ pub fn match_fixed_path_set( worktree_id, positions: positions.clone(), is_dir: candidate.is_dir, - path: Arc::from(candidate.path), - path_prefix: Arc::default(), + path: candidate.path.into(), + path_prefix: RelPath::empty().into(), distance_to_relative_ancestor: usize::MAX, }, ); @@ -121,7 +122,7 @@ pub fn match_fixed_path_set( pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( candidate_sets: &'a [Set], query: &str, - relative_to: &Option>, + relative_to: &Option>, smart_case: bool, max_results: usize, cancel_flag: &AtomicBool, @@ -132,12 +133,27 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( return Vec::new(); } - let lowercase_query = query.to_lowercase().chars().collect::>(); - let query = query.chars().collect::>(); + let path_style = candidate_sets[0].path_style(); + + let query = query + .chars() + .map(|char| { + if path_style.is_windows() && char == '\\' { + '/' + } else { + char + } + }) + .collect::>(); + + let lowercase_query = query + .iter() + .map(|query| query.to_ascii_lowercase()) + .collect::>(); - let lowercase_query = &lowercase_query; let query = &query; - let query_char_bag = CharBag::from(&lowercase_query[..]); + let lowercase_query = &lowercase_query; + let query_char_bag = CharBag::from_iter(lowercase_query.iter().copied()); let num_cpus = executor.num_cpus().min(path_count); let segment_size = path_count.div_ceil(num_cpus); @@ -168,7 +184,11 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( let candidates = candidate_set.candidates(start).take(end - start); let worktree_id = candidate_set.id(); - let prefix = candidate_set.prefix().chars().collect::>(); + let mut prefix = + candidate_set.prefix().as_str().chars().collect::>(); + if !candidate_set.root_is_file() && !prefix.is_empty() { + prefix.push('/'); + } let lowercase_prefix = prefix .iter() .map(|c| c.to_ascii_lowercase()) @@ -219,7 +239,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( /// Compute the distance from a given path to some other path /// If there is no shared path, returns usize::MAX -fn distance_between_paths(path: &Path, relative_to: &Path) -> usize { +fn distance_between_paths(path: &RelPath, relative_to: &RelPath) -> usize { let mut path_components = path.components(); let mut relative_components = relative_to.components(); @@ -234,12 +254,12 @@ fn distance_between_paths(path: &Path, relative_to: &Path) -> usize { #[cfg(test)] mod tests { - use std::path::Path; + use util::rel_path::RelPath; use super::distance_between_paths; #[test] fn test_distance_between_paths_empty() { - distance_between_paths(Path::new(""), Path::new("")); + distance_between_paths(RelPath::empty(), RelPath::empty()); } } diff --git a/crates/fuzzy/src/strings.rs b/crates/fuzzy/src/strings.rs index 7c866de05c4566c060fa01a362931e1355cd8c37..54539840cfb0ca251428d9f78d5d134f16afdf4c 100644 --- a/crates/fuzzy/src/strings.rs +++ b/crates/fuzzy/src/strings.rs @@ -4,7 +4,7 @@ use crate::{ }; use gpui::BackgroundExecutor; use std::{ - borrow::{Borrow, Cow}, + borrow::Borrow, cmp::{self, Ordering}, iter, ops::Range, @@ -28,13 +28,13 @@ impl StringMatchCandidate { } } -impl<'a> MatchCandidate for &'a StringMatchCandidate { +impl MatchCandidate for &StringMatchCandidate { fn has_chars(&self, bag: CharBag) -> bool { self.char_bag.is_superset(bag) } - fn to_string(&self) -> Cow<'a, str> { - self.string.as_str().into() + fn candidate_chars(&self) -> impl Iterator { + self.string.chars() } } diff --git a/crates/git/src/blame.rs b/crates/git/src/blame.rs index 24b2c44218120b1237fb42e04edc9b6784356c57..a06d5081b77672a7578e6fc74c6db56dd9705471 100644 --- a/crates/git/src/blame.rs +++ b/crates/git/src/blame.rs @@ -1,4 +1,5 @@ use crate::commit::get_messages; +use crate::repository::RepoPath; use crate::{GitRemote, Oid}; use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet}; @@ -33,7 +34,7 @@ impl Blame { pub async fn for_path( git_binary: &Path, working_directory: &Path, - path: &Path, + path: &RepoPath, content: &Rope, remote_url: Option, ) -> Result { @@ -66,7 +67,7 @@ const GIT_BLAME_NO_PATH: &str = "fatal: no such path"; async fn run_git_blame( git_binary: &Path, working_directory: &Path, - path: &Path, + path: &RepoPath, contents: &Rope, ) -> Result { let mut child = util::command::new_smol_command(git_binary) @@ -76,7 +77,7 @@ async fn run_git_blame( .arg("-w") .arg("--contents") .arg("-") - .arg(path.as_os_str()) + .arg(path.as_str()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/crates/git/src/commit.rs b/crates/git/src/commit.rs index 1a2aa7b43331aca97e6d9d26bac3569448aafc4a..50fd0ad2484c77bf227d6138a20481e11ec2e7f9 100644 --- a/crates/git/src/commit.rs +++ b/crates/git/src/commit.rs @@ -39,7 +39,7 @@ pub async fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result impl Iterator { +pub fn parse_git_diff_name_status(content: &str) -> impl Iterator { let mut parts = content.split('\0'); std::iter::from_fn(move || { loop { @@ -51,13 +51,14 @@ pub fn parse_git_diff_name_status(content: &str) -> impl Iterator StatusCode::Deleted, _ => continue, }; - return Some((Path::new(path), status)); + return Some((path, status)); } }) } #[cfg(test)] mod tests { + use super::*; #[test] @@ -78,31 +79,19 @@ mod tests { assert_eq!( output, &[ - (Path::new("Cargo.lock"), StatusCode::Modified), - (Path::new("crates/project/Cargo.toml"), StatusCode::Modified), - ( - Path::new("crates/project/src/buffer_store.rs"), - StatusCode::Modified - ), - (Path::new("crates/project/src/git.rs"), StatusCode::Deleted), - ( - Path::new("crates/project/src/git_store.rs"), - StatusCode::Added - ), + ("Cargo.lock", StatusCode::Modified), + ("crates/project/Cargo.toml", StatusCode::Modified), + ("crates/project/src/buffer_store.rs", StatusCode::Modified), + ("crates/project/src/git.rs", StatusCode::Deleted), + ("crates/project/src/git_store.rs", StatusCode::Added), ( - Path::new("crates/project/src/git_store/git_traversal.rs"), + "crates/project/src/git_store/git_traversal.rs", StatusCode::Added, ), + ("crates/project/src/project.rs", StatusCode::Modified), + ("crates/project/src/worktree_store.rs", StatusCode::Modified), ( - Path::new("crates/project/src/project.rs"), - StatusCode::Modified - ), - ( - Path::new("crates/project/src/worktree_store.rs"), - StatusCode::Modified - ), - ( - Path::new("crates/project_panel/src/project_panel.rs"), + "crates/project_panel/src/project_panel.rs", StatusCode::Modified ), ] diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index 2028a0f374578d0c0f35bdc8c80ec09462ab0875..354614e32cd43aaf8bd677b0303d08b312045df0 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -12,22 +12,17 @@ use anyhow::{Context as _, Result}; pub use git2 as libgit; use gpui::{Action, actions}; pub use repository::RemoteCommandOutput; -pub use repository::WORK_DIRECTORY_REPO_PATH; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::ffi::OsStr; use std::fmt; use std::str::FromStr; -use std::sync::LazyLock; - -pub static DOT_GIT: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".git")); -pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore")); -pub static FSMONITOR_DAEMON: LazyLock<&'static OsStr> = - LazyLock::new(|| OsStr::new("fsmonitor--daemon")); -pub static LFS_DIR: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("lfs")); -pub static COMMIT_MESSAGE: LazyLock<&'static OsStr> = - LazyLock::new(|| OsStr::new("COMMIT_EDITMSG")); -pub static INDEX_LOCK: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("index.lock")); + +pub const DOT_GIT: &str = ".git"; +pub const GITIGNORE: &str = ".gitignore"; +pub const FSMONITOR_DAEMON: &str = "fsmonitor--daemon"; +pub const LFS_DIR: &str = "lfs"; +pub const COMMIT_MESSAGE: &str = "COMMIT_EDITMSG"; +pub const INDEX_LOCK: &str = "index.lock"; actions!( git, diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 88bfc41dfee4184a733e733eea77817c32f796e0..b455f5b14c7b1bb90fe93ecd94e51c4907a5d36f 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -12,12 +12,9 @@ use parking_lot::Mutex; use rope::Rope; use schemars::JsonSchema; use serde::Deserialize; -use std::borrow::{Borrow, Cow}; use std::ffi::{OsStr, OsString}; use std::io::prelude::*; -use std::path::Component; use std::process::{ExitStatus, Stdio}; -use std::sync::LazyLock; use std::{ cmp::Ordering, future, @@ -28,6 +25,8 @@ use std::{ use sum_tree::MapSeekTarget; use thiserror::Error; use util::command::{new_smol_command, new_std_command}; +use util::paths::PathStyle; +use util::rel_path::RelPath; use util::{ResultExt, paths}; use uuid::Uuid; @@ -719,16 +718,21 @@ impl GitRepository for RealGitRepository { let mut info_line = String::new(); let mut newline = [b'\0']; for (path, status_code) in changes { + // git-show outputs `/`-delimited paths even on Windows. + let Ok(rel_path) = RelPath::new(path) else { + continue; + }; + match status_code { StatusCode::Modified => { - writeln!(&mut stdin, "{commit}:{}", path.display())?; - writeln!(&mut stdin, "{parent_sha}:{}", path.display())?; + writeln!(&mut stdin, "{commit}:{path}")?; + writeln!(&mut stdin, "{parent_sha}:{path}")?; } StatusCode::Added => { - writeln!(&mut stdin, "{commit}:{}", path.display())?; + writeln!(&mut stdin, "{commit}:{path}")?; } StatusCode::Deleted => { - writeln!(&mut stdin, "{parent_sha}:{}", path.display())?; + writeln!(&mut stdin, "{parent_sha}:{path}")?; } _ => continue, } @@ -766,7 +770,7 @@ impl GitRepository for RealGitRepository { } files.push(CommitFile { - path: path.into(), + path: rel_path.into(), old_text, new_text, }) @@ -824,7 +828,7 @@ impl GitRepository for RealGitRepository { .current_dir(&working_directory?) .envs(env.iter()) .args(["checkout", &commit, "--"]) - .args(paths.iter().map(|path| path.as_ref())) + .args(paths.iter().map(|path| path.as_str())) .output() .await?; anyhow::ensure!( @@ -846,13 +850,11 @@ impl GitRepository for RealGitRepository { .spawn(async move { fn logic(repo: &git2::Repository, path: &RepoPath) -> Result> { // This check is required because index.get_path() unwraps internally :( - check_path_to_repo_path_errors(path)?; - let mut index = repo.index()?; index.read(false)?; const STAGE_NORMAL: i32 = 0; - let oid = match index.get_path(path, STAGE_NORMAL) { + let oid = match index.get_path(path.as_std_path(), STAGE_NORMAL) { Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id, _ => return Ok(None), }; @@ -876,7 +878,7 @@ impl GitRepository for RealGitRepository { .spawn(async move { let repo = repo.lock(); let head = repo.head().ok()?.peel_to_tree().log_err()?; - let entry = head.get_path(&path).ok()?; + let entry = head.get_path(path.as_std_path()).ok()?; if entry.filemode() == i32::from(git2::FileMode::Link) { return None; } @@ -918,7 +920,7 @@ impl GitRepository for RealGitRepository { .current_dir(&working_directory) .envs(env.iter()) .args(["update-index", "--add", "--cacheinfo", "100644", sha]) - .arg(path.to_unix_style()) + .arg(path.as_str()) .output() .await?; @@ -933,7 +935,7 @@ impl GitRepository for RealGitRepository { .current_dir(&working_directory) .envs(env.iter()) .args(["update-index", "--force-remove"]) - .arg(path.to_unix_style()) + .arg(path.as_str()) .output() .await?; anyhow::ensure!( @@ -1251,7 +1253,7 @@ impl GitRepository for RealGitRepository { .current_dir(&working_directory?) .envs(env.iter()) .args(["update-index", "--add", "--remove", "--"]) - .args(paths.iter().map(|p| p.to_unix_style())) + .args(paths.iter().map(|p| p.as_str())) .output() .await?; anyhow::ensure!( @@ -1812,7 +1814,7 @@ fn git_status_args(path_prefixes: &[RepoPath]) -> Vec { OsString::from("-z"), ]; args.extend(path_prefixes.iter().map(|path_prefix| { - if path_prefix.0.as_ref() == Path::new("") { + if path_prefix.is_empty() { Path::new(".").into() } else { path_prefix.as_os_str().into() @@ -2066,99 +2068,65 @@ async fn run_askpass_command( } } -pub static WORK_DIRECTORY_REPO_PATH: LazyLock = - LazyLock::new(|| RepoPath(Path::new("").into())); - #[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] -pub struct RepoPath(pub Arc); +pub struct RepoPath(pub Arc); impl RepoPath { - pub fn new(path: PathBuf) -> Self { - debug_assert!(path.is_relative(), "Repo paths must be relative"); - - RepoPath(path.into()) + pub fn new + ?Sized>(s: &S) -> Result { + let rel_path = RelPath::new(s)?; + Ok(rel_path.into()) } - pub fn from_str(path: &str) -> Self { - let path = Path::new(path); - debug_assert!(path.is_relative(), "Repo paths must be relative"); - - RepoPath(path.into()) + pub fn from_proto(proto: &str) -> Result { + let rel_path = RelPath::from_proto(proto)?; + Ok(rel_path.into()) } - pub fn to_unix_style(&self) -> Cow<'_, OsStr> { - #[cfg(target_os = "windows")] - { - use std::ffi::OsString; - - let path = self.0.as_os_str().to_string_lossy().replace("\\", "/"); - Cow::Owned(OsString::from(path)) - } - #[cfg(not(target_os = "windows"))] - { - Cow::Borrowed(self.0.as_os_str()) - } + pub fn from_std_path(path: &Path, path_style: PathStyle) -> Result { + let rel_path = RelPath::from_std_path(path, path_style)?; + Ok(rel_path.into()) } } -impl std::fmt::Display for RepoPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.to_string_lossy().fmt(f) - } +#[cfg(any(test, feature = "test-support"))] +pub fn repo_path + ?Sized>(s: &S) -> RepoPath { + RepoPath(RelPath::new(s).unwrap().into()) } -impl From<&Path> for RepoPath { - fn from(value: &Path) -> Self { - RepoPath::new(value.into()) +impl From<&RelPath> for RepoPath { + fn from(value: &RelPath) -> Self { + RepoPath(value.into()) } } -impl From> for RepoPath { - fn from(value: Arc) -> Self { +impl From> for RepoPath { + fn from(value: Arc) -> Self { RepoPath(value) } } -impl From for RepoPath { - fn from(value: PathBuf) -> Self { - RepoPath::new(value) - } -} - -impl From<&str> for RepoPath { - fn from(value: &str) -> Self { - Self::from_str(value) - } -} - impl Default for RepoPath { fn default() -> Self { - RepoPath(Path::new("").into()) - } -} - -impl AsRef for RepoPath { - fn as_ref(&self) -> &Path { - self.0.as_ref() + RepoPath(RelPath::empty().into()) } } impl std::ops::Deref for RepoPath { - type Target = Path; + type Target = RelPath; fn deref(&self) -> &Self::Target { &self.0 } } -impl Borrow for RepoPath { - fn borrow(&self) -> &Path { - self.0.as_ref() +impl AsRef for RepoPath { + fn as_ref(&self) -> &Path { + RelPath::as_ref(&self.0) } } #[derive(Debug)] -pub struct RepoPathDescendants<'a>(pub &'a Path); +pub struct RepoPathDescendants<'a>(pub &'a RepoPath); impl MapSeekTarget for RepoPathDescendants<'_> { fn cmp_cursor(&self, key: &RepoPath) -> Ordering { @@ -2244,35 +2212,6 @@ fn parse_upstream_track(upstream_track: &str) -> Result { })) } -fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { - match relative_file_path.components().next() { - None => anyhow::bail!("repo path should not be empty"), - Some(Component::Prefix(_)) => anyhow::bail!( - "repo path `{}` should be relative, not a windows prefix", - relative_file_path.to_string_lossy() - ), - Some(Component::RootDir) => { - anyhow::bail!( - "repo path `{}` should be relative", - relative_file_path.to_string_lossy() - ) - } - Some(Component::CurDir) => { - anyhow::bail!( - "repo path `{}` should not start with `.`", - relative_file_path.to_string_lossy() - ) - } - Some(Component::ParentDir) => { - anyhow::bail!( - "repo path `{}` should not start with `..`", - relative_file_path.to_string_lossy() - ) - } - _ => Ok(()), - } -} - fn checkpoint_author_envs() -> HashMap { HashMap::from_iter([ ("GIT_AUTHOR_NAME".to_string(), "Zed".to_string()), @@ -2299,12 +2238,9 @@ mod tests { let repo = RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap(); - repo.stage_paths( - vec![RepoPath::from_str("file")], - Arc::new(HashMap::default()), - ) - .await - .unwrap(); + repo.stage_paths(vec![repo_path("file")], Arc::new(HashMap::default())) + .await + .unwrap(); repo.commit( "Initial commit".into(), None, @@ -2328,12 +2264,9 @@ mod tests { smol::fs::write(&file_path, "modified after checkpoint") .await .unwrap(); - repo.stage_paths( - vec![RepoPath::from_str("file")], - Arc::new(HashMap::default()), - ) - .await - .unwrap(); + repo.stage_paths(vec![repo_path("file")], Arc::new(HashMap::default())) + .await + .unwrap(); repo.commit( "Commit after checkpoint".into(), None, @@ -2466,12 +2399,9 @@ mod tests { RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap(); // initial commit - repo.stage_paths( - vec![RepoPath::from_str("main.rs")], - Arc::new(HashMap::default()), - ) - .await - .unwrap(); + repo.stage_paths(vec![repo_path("main.rs")], Arc::new(HashMap::default())) + .await + .unwrap(); repo.commit( "Initial commit".into(), None, diff --git a/crates/git/src/status.rs b/crates/git/src/status.rs index d0399a137aa9af7fc400a13105119b897a9dec1c..2bcd0809dbb1ac4bf41c8b7e861ed906f086d1ad 100644 --- a/crates/git/src/status.rs +++ b/crates/git/src/status.rs @@ -1,8 +1,8 @@ use crate::repository::RepoPath; use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::{path::Path, str::FromStr, sync::Arc}; -use util::ResultExt; +use std::{str::FromStr, sync::Arc}; +use util::{ResultExt, rel_path::RelPath}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum FileStatus { @@ -447,7 +447,8 @@ impl FromStr for GitStatus { } let status = entry.as_bytes()[0..2].try_into().unwrap(); let status = FileStatus::from_bytes(status).log_err()?; - let path = RepoPath(Path::new(path).into()); + // git-status outputs `/`-delimited repo paths, even on Windows. + let path = RepoPath(RelPath::new(path).log_err()?.into()); Some((path, status)) }) .collect::>(); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index ac51cee8e42567a607891dd242c2bf103ae7fc0e..038a5beaac5fcb7b1d96ff5f1e63d307bce6595c 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -14,13 +14,12 @@ use multi_buffer::PathKey; use project::{Project, WorktreeId, git_store::Repository}; use std::{ any::{Any, TypeId}, - ffi::OsStr, fmt::Write as _, - path::{Path, PathBuf}, + path::PathBuf, sync::Arc, }; use ui::{Color, Icon, IconName, Label, LabelCommon as _, SharedString}; -use util::{ResultExt, truncate_and_trailoff}; +use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff}; use workspace::{ Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace, item::{BreadcrumbText, ItemEvent, TabContentParams}, @@ -40,7 +39,7 @@ struct GitBlob { } struct CommitMetadataFile { - title: Arc, + title: Arc, worktree_id: WorktreeId, } @@ -129,7 +128,9 @@ impl CommitView { let mut metadata_buffer_id = None; if let Some(worktree_id) = first_worktree_id { let file = Arc::new(CommitMetadataFile { - title: PathBuf::from(format!("commit {}", commit.sha)).into(), + title: RelPath::new(&format!("commit {}", commit.sha)) + .unwrap() + .into(), worktree_id, }); let buffer = cx.new(|cx| { @@ -144,7 +145,7 @@ impl CommitView { }); multibuffer.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( - PathKey::namespaced(COMMIT_METADATA_NAMESPACE, file.title.clone()), + PathKey::namespaced(COMMIT_METADATA_NAMESPACE, file.title.as_str().into()), buffer.clone(), vec![Point::zero()..buffer.read(cx).max_point()], 0, @@ -192,7 +193,7 @@ impl CommitView { .collect::>(); let path = snapshot.file().unwrap().path().clone(); let _is_newly_added = multibuffer.set_excerpts_for_path( - PathKey::namespaced(FILE_NAMESPACE, path), + PathKey::namespaced(FILE_NAMESPACE, path.as_str().into()), buffer, diff_hunk_ranges, multibuffer_context_lines(cx), @@ -227,15 +228,19 @@ impl language::File for GitBlob { } } - fn path(&self) -> &Arc { + fn path_style(&self, _: &App) -> PathStyle { + PathStyle::Posix + } + + fn path(&self) -> &Arc { &self.path.0 } fn full_path(&self, _: &App) -> PathBuf { - self.path.to_path_buf() + self.path.as_std_path().to_path_buf() } - fn file_name<'a>(&'a self, _: &'a App) -> &'a OsStr { + fn file_name<'a>(&'a self, _: &'a App) -> &'a str { self.path.file_name().unwrap() } @@ -261,15 +266,19 @@ impl language::File for CommitMetadataFile { DiskState::New } - fn path(&self) -> &Arc { + fn path_style(&self, _: &App) -> PathStyle { + PathStyle::Posix + } + + fn path(&self) -> &Arc { &self.title } fn full_path(&self, _: &App) -> PathBuf { - self.title.as_ref().into() + PathBuf::from(self.title.as_str().to_owned()) } - fn file_name<'a>(&'a self, _: &'a App) -> &'a OsStr { + fn file_name<'a>(&'a self, _: &'a App) -> &'a str { self.title.file_name().unwrap() } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index b2c855f0fa341c19e7f44446e37240ee5a70e5e6..e66da36576429e8a1d768681e35015db034dff39 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -53,7 +53,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore, StatusStyle}; use std::future::Future; use std::ops::Range; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::{collections::HashSet, sync::Arc, time::Duration, usize}; use strum::{IntoEnumIterator, VariantNames}; use time::OffsetDateTime; @@ -61,6 +61,7 @@ use ui::{ Checkbox, CommonAnimationExt, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu, ScrollAxes, Scrollbars, SplitButton, Tooltip, WithScrollbar, prelude::*, }; +use util::paths::PathStyle; use util::{ResultExt, TryFutureExt, maybe}; use workspace::SERIALIZATION_THROTTLE_TIME; @@ -251,23 +252,22 @@ impl GitListEntry { #[derive(Debug, PartialEq, Eq, Clone)] pub struct GitStatusEntry { pub(crate) repo_path: RepoPath, - pub(crate) abs_path: PathBuf, pub(crate) status: FileStatus, pub(crate) staging: StageStatus, } impl GitStatusEntry { - fn display_name(&self) -> String { + fn display_name(&self, path_style: PathStyle) -> String { self.repo_path .file_name() - .map(|name| name.to_string_lossy().into_owned()) - .unwrap_or_else(|| self.repo_path.to_string_lossy().into_owned()) + .map(|name| name.to_owned()) + .unwrap_or_else(|| self.repo_path.display(path_style).to_string()) } - fn parent_dir(&self) -> Option { + fn parent_dir(&self, path_style: PathStyle) -> Option { self.repo_path .parent() - .map(|parent| parent.to_string_lossy().into_owned()) + .map(|parent| parent.display(path_style).to_string()) } } @@ -826,6 +826,7 @@ impl GitPanel { window: &mut Window, cx: &mut Context, ) { + let path_style = self.project.read(cx).path_style(cx); maybe!({ let list_entry = self.entries.get(self.selected_entry?)?.clone(); let entry = list_entry.status_entry()?.to_owned(); @@ -841,8 +842,7 @@ impl GitPanel { entry .repo_path .file_name() - .unwrap_or(entry.repo_path.as_os_str()) - .to_string_lossy() + .unwrap_or(entry.repo_path.display(path_style).as_ref()), ), None, &["Restore", "Cancel"], @@ -885,7 +885,7 @@ impl GitPanel { if entry.status.staging().has_staged() { self.change_file_stage(false, vec![entry.clone()], cx); } - let filename = path.path.file_name()?.to_string_lossy(); + let filename = path.path.file_name()?.to_string(); if !entry.status.is_created() { self.perform_checkout(vec![entry.clone()], window, cx); @@ -1028,7 +1028,7 @@ impl GitPanel { let mut details = entries .iter() .filter_map(|entry| entry.repo_path.0.file_name()) - .map(|filename| filename.to_string_lossy()) + .map(|filename| filename.to_string()) .take(5) .join("\n"); if entries.len() > 5 { @@ -1084,7 +1084,7 @@ impl GitPanel { .repo_path .0 .file_name() - .map(|f| f.to_string_lossy()) + .map(|f| f.to_string()) .unwrap_or_default() }) .take(5) @@ -1721,7 +1721,7 @@ impl GitPanel { .repo_path .file_name() .unwrap_or_default() - .to_string_lossy(); + .to_string(); Some(format!("{} {}", action_text, file_name)) } @@ -1973,11 +1973,7 @@ impl GitPanel { cx.spawn_in(window, async move |this, cx| { let mut paths = path.await.ok()?.ok()??; let mut path = paths.pop()?; - let repo_name = repo - .split(std::path::MAIN_SEPARATOR_STR) - .last()? - .strip_suffix(".git")? - .to_owned(); + let repo_name = repo.split("/").last()?.strip_suffix(".git")?.to_owned(); let fs = this.read_with(cx, |this, _| this.fs.clone()).ok()?; @@ -2558,6 +2554,7 @@ impl GitPanel { } fn update_visible_entries(&mut self, window: &mut Window, cx: &mut Context) { + let path_style = self.project.read(cx).path_style(cx); let bulk_staging = self.bulk_staging.take(); let last_staged_path_prev_index = bulk_staging .as_ref() @@ -2609,10 +2606,8 @@ impl GitPanel { continue; } - let abs_path = repo.work_directory_abs_path.join(&entry.repo_path.0); let entry = GitStatusEntry { repo_path: entry.repo_path.clone(), - abs_path, status: entry.status, staging, }; @@ -2623,8 +2618,8 @@ impl GitPanel { } let width_estimate = Self::item_width_estimate( - entry.parent_dir().map(|s| s.len()).unwrap_or(0), - entry.display_name().len(), + entry.parent_dir(path_style).map(|s| s.len()).unwrap_or(0), + entry.display_name(path_style).len(), ); match max_width_item.as_mut() { @@ -3634,7 +3629,7 @@ impl GitPanel { cx: &App, ) -> Option { let repo = self.active_repository.as_ref()?.read(cx); - let project_path = (file.worktree_id(cx), file.path()).into(); + let project_path = (file.worktree_id(cx), file.path().clone()).into(); let repo_path = repo.project_path_to_repo_path(&project_path, cx)?; let ix = self.entry_by_path(&repo_path, cx)?; let entry = self.entries.get(ix)?; @@ -3887,7 +3882,8 @@ impl GitPanel { window: &Window, cx: &Context, ) -> AnyElement { - let display_name = entry.display_name(); + let path_style = self.project.read(cx).path_style(cx); + let display_name = entry.display_name(path_style); let selected = self.selected_entry == Some(ix); let marked = self.marked_entries.contains(&ix); @@ -4060,11 +4056,14 @@ impl GitPanel { .items_center() .flex_1() // .overflow_hidden() - .when_some(entry.parent_dir(), |this, parent| { + .when_some(entry.parent_dir(path_style), |this, parent| { if !parent.is_empty() { this.child( - self.entry_label(format!("{}/", parent), path_color) - .when(status.is_deleted(), |this| this.strikethrough()), + self.entry_label( + format!("{parent}{}", path_style.separator()), + path_color, + ) + .when(status.is_deleted(), |this| this.strikethrough()), ) } else { this @@ -4889,7 +4888,10 @@ impl Component for PanelRepoFooter { #[cfg(test)] mod tests { - use git::status::{StatusCode, UnmergedStatus, UnmergedStatusCode}; + use git::{ + repository::repo_path, + status::{StatusCode, UnmergedStatus, UnmergedStatusCode}, + }; use gpui::{TestAppContext, VisualTestContext}; use project::{FakeFs, WorktreeSettings}; use serde_json::json; @@ -4941,14 +4943,8 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/zed/.git")), &[ - ( - Path::new("crates/gpui/gpui.rs"), - StatusCode::Modified.worktree(), - ), - ( - Path::new("crates/util/util.rs"), - StatusCode::Modified.worktree(), - ), + ("crates/gpui/gpui.rs", StatusCode::Modified.worktree()), + ("crates/util/util.rs", StatusCode::Modified.worktree()), ], ); @@ -4989,14 +4985,12 @@ mod tests { header: Section::Tracked }), GitListEntry::Status(GitStatusEntry { - abs_path: path!("/root/zed/crates/gpui/gpui.rs").into(), - repo_path: "crates/gpui/gpui.rs".into(), + repo_path: repo_path("crates/gpui/gpui.rs"), status: StatusCode::Modified.worktree(), staging: StageStatus::Unstaged, }), GitListEntry::Status(GitStatusEntry { - abs_path: path!("/root/zed/crates/util/util.rs").into(), - repo_path: "crates/util/util.rs".into(), + repo_path: repo_path("crates/util/util.rs"), status: StatusCode::Modified.worktree(), staging: StageStatus::Unstaged, },), @@ -5016,14 +5010,12 @@ mod tests { header: Section::Tracked }), GitListEntry::Status(GitStatusEntry { - abs_path: path!("/root/zed/crates/gpui/gpui.rs").into(), - repo_path: "crates/gpui/gpui.rs".into(), + repo_path: repo_path("crates/gpui/gpui.rs"), status: StatusCode::Modified.worktree(), staging: StageStatus::Unstaged, }), GitListEntry::Status(GitStatusEntry { - abs_path: path!("/root/zed/crates/util/util.rs").into(), - repo_path: "crates/util/util.rs".into(), + repo_path: repo_path("crates/util/util.rs"), status: StatusCode::Modified.worktree(), staging: StageStatus::Unstaged, },), @@ -5061,14 +5053,14 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/project/.git")), &[ - (Path::new("src/main.rs"), StatusCode::Modified.worktree()), - (Path::new("src/lib.rs"), StatusCode::Modified.worktree()), - (Path::new("tests/test.rs"), StatusCode::Modified.worktree()), - (Path::new("new_file.txt"), FileStatus::Untracked), - (Path::new("another_new.rs"), FileStatus::Untracked), - (Path::new("src/utils.rs"), FileStatus::Untracked), + ("src/main.rs", StatusCode::Modified.worktree()), + ("src/lib.rs", StatusCode::Modified.worktree()), + ("tests/test.rs", StatusCode::Modified.worktree()), + ("new_file.txt", FileStatus::Untracked), + ("another_new.rs", FileStatus::Untracked), + ("src/utils.rs", FileStatus::Untracked), ( - Path::new("conflict.txt"), + "conflict.txt", UnmergedStatus { first_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Updated, @@ -5242,7 +5234,7 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/project/.git")), - &[(Path::new("src/main.rs"), StatusCode::Modified.worktree())], + &[("src/main.rs", StatusCode::Modified.worktree())], ); let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await; diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 9d0a575247427ec5fe674b342d0f2660e40e2299..a226caab34662c44c20a8bccb02d1149102befc7 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -243,7 +243,7 @@ impl ProjectDiff { TRACKED_NAMESPACE }; - let path_key = PathKey::namespaced(namespace, entry.repo_path.0); + let path_key = PathKey::namespaced(namespace, entry.repo_path.as_str().into()); self.move_to_path(path_key, window, cx) } @@ -397,7 +397,7 @@ impl ProjectDiff { } else { TRACKED_NAMESPACE }; - let path_key = PathKey::namespaced(namespace, entry.repo_path.0.clone()); + let path_key = PathKey::namespaced(namespace, entry.repo_path.as_str().into()); previous_paths.remove(&path_key); let load_buffer = self @@ -535,7 +535,7 @@ impl ProjectDiff { self.multibuffer .read(cx) .excerpt_paths() - .map(|key| key.path().to_string_lossy().to_string()) + .map(|key| key.path().to_string()) .collect() } } @@ -1406,12 +1406,12 @@ mod tests { fs.set_head_for_repo( path!("/project/.git").as_ref(), - &[("foo.txt".into(), "foo\n".into())], + &[("foo.txt", "foo\n".into())], "deadbeef", ); fs.set_index_for_repo( path!("/project/.git").as_ref(), - &[("foo.txt".into(), "foo\n".into())], + &[("foo.txt", "foo\n".into())], ); cx.run_until_parked(); @@ -1461,16 +1461,13 @@ mod tests { fs.set_head_and_index_for_repo( path!("/project/.git").as_ref(), - &[ - ("bar".into(), "bar\n".into()), - ("foo".into(), "foo\n".into()), - ], + &[("bar", "bar\n".into()), ("foo", "foo\n".into())], ); cx.run_until_parked(); let editor = cx.update_window_entity(&diff, |diff, window, cx| { diff.move_to_path( - PathKey::namespaced(TRACKED_NAMESPACE, Path::new("foo").into()), + PathKey::namespaced(TRACKED_NAMESPACE, "foo".into()), window, cx, ); @@ -1491,7 +1488,7 @@ mod tests { let editor = cx.update_window_entity(&diff, |diff, window, cx| { diff.move_to_path( - PathKey::namespaced(TRACKED_NAMESPACE, Path::new("bar").into()), + PathKey::namespaced(TRACKED_NAMESPACE, "bar".into()), window, cx, ); @@ -1543,7 +1540,7 @@ mod tests { fs.set_head_for_repo( path!("/project/.git").as_ref(), - &[("foo".into(), "original\n".into())], + &[("foo", "original\n".into())], "deadbeef", ); cx.run_until_parked(); @@ -1646,12 +1643,12 @@ mod tests { ) .await; - fs.set_git_content_for_repo( + fs.set_head_and_index_for_repo( Path::new("/a/.git"), &[ - ("b.txt".into(), "before\n".to_string(), None), - ("c.txt".into(), "unchanged\n".to_string(), None), - ("d.txt".into(), "deleted\n".to_string(), None), + ("b.txt", "before\n".to_string()), + ("c.txt", "unchanged\n".to_string()), + ("d.txt", "deleted\n".to_string()), ], ); @@ -1764,9 +1761,9 @@ mod tests { ) .await; - fs.set_git_content_for_repo( + fs.set_head_and_index_for_repo( Path::new("/a/.git"), - &[("main.rs".into(), git_contents.to_owned(), None)], + &[("main.rs", git_contents.to_owned())], ); let project = Project::test(fs, [Path::new("/a")], cx).await; @@ -1816,7 +1813,7 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/project/.git")), &[( - Path::new("foo"), + "foo", UnmergedStatus { first_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Updated, diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 9b573d7071b64c7470e81079e7be5a5f048fc5eb..321ec09d0d745db6a95c498db166639820688345 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -311,7 +311,7 @@ mod tests { use project::{FakeFs, Project}; use serde_json::json; use std::{num::NonZeroU32, sync::Arc, time::Duration}; - use util::path; + use util::{path, rel_path::rel_path}; use workspace::{AppState, Workspace}; #[gpui::test] @@ -356,7 +356,7 @@ mod tests { .unwrap(); let editor = workspace .update_in(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "a.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("a.rs")), None, true, window, cx) }) .await .unwrap() @@ -460,7 +460,7 @@ mod tests { .unwrap(); let editor = workspace .update_in(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "a.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("a.rs")), None, true, window, cx) }) .await .unwrap() @@ -545,7 +545,7 @@ mod tests { .unwrap(); let editor = workspace .update_in(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "a.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("a.rs")), None, true, window, cx) }) .await .unwrap() @@ -623,7 +623,7 @@ mod tests { .unwrap(); let editor = workspace .update_in(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "a.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("a.rs")), None, true, window, cx) }) .await .unwrap() diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 2dca57424b86e2221acc271efac19cdf39a3f79f..a9ec858ea3b5e32e093bc5476a4cadc1e64b8501 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -1,8 +1,6 @@ mod image_info; mod image_viewer_settings; -use std::path::PathBuf; - use anyhow::Context as _; use editor::{EditorSettings, items::entry_git_aware_label_color}; use file_icons::FileIcons; @@ -144,7 +142,6 @@ impl Item for ImageView { .read(cx) .file .file_name(cx) - .to_string_lossy() .to_string() .into() } @@ -198,20 +195,14 @@ impl Item for ImageView { } fn breadcrumbs_text_for_image(project: &Project, image: &ImageItem, cx: &App) -> String { - let path = image.file.file_name(cx); - if project.visible_worktrees(cx).count() <= 1 { - return path.to_string_lossy().to_string(); + let mut path = image.file.path().clone(); + if project.visible_worktrees(cx).count() > 1 + && let Some(worktree) = project.worktree_for_id(image.project_path(cx).worktree_id, cx) + { + path = worktree.read(cx).root_name().join(&path); } - project - .worktree_for_id(image.project_path(cx).worktree_id, cx) - .map(|worktree| { - PathBuf::from(worktree.read(cx).root_name()) - .join(path) - .to_string_lossy() - .to_string() - }) - .unwrap_or_else(|| path.to_string_lossy().to_string()) + path.display(project.path_style(cx)).to_string() } impl SerializableItem for ImageView { @@ -242,7 +233,7 @@ impl SerializableItem for ImageView { let project_path = ProjectPath { worktree_id, - path: relative_path.into(), + path: relative_path, }; let image_item = project diff --git a/crates/inspector_ui/src/div_inspector.rs b/crates/inspector_ui/src/div_inspector.rs index fa8b76517f0125e7319f035b41996e445451510a..5f0786c885b5636363c6f5f153c7db48d7b6e432 100644 --- a/crates/inspector_ui/src/div_inspector.rs +++ b/crates/inspector_ui/src/div_inspector.rs @@ -24,6 +24,7 @@ use std::path::Path; use std::rc::Rc; use std::sync::LazyLock; use ui::{Label, LabelSize, Tooltip, prelude::*, styled_ext_reflection, v_flex}; +use util::rel_path::RelPath; use util::split_str_with_ranges; /// Path used for unsaved buffer that contains style json. To support the json language server, this @@ -466,7 +467,7 @@ impl DivInspector { let project_path = worktree.read_with(cx, |worktree, _cx| ProjectPath { worktree_id: worktree.id(), - path: Path::new("").into(), + path: RelPath::empty().into(), })?; let buffer = project diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 52d93ba21a828b076141dd8d21a1b8f88bc20be8..9dc724f1234d79619ea1347e6747ce286aa42ca3 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -94,7 +94,7 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap break; } for directory in worktree.read(cx).directories(true, 1) { - let full_directory_path = worktree_root.join(&directory.path); + let full_directory_path = worktree_root.join(directory.path.as_std_path()); if full_directory_path.ends_with(&journal_dir_clone) { open_new_workspace = false; break 'outer; diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 8e104fc8cb8a2a1cd211cbd59a0caa4d2f79f18e..b76bb7521b403d7c8900d8ac9963f7e908aa6ea3 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -41,13 +41,12 @@ use std::{ cell::Cell, cmp::{self, Ordering, Reverse}, collections::{BTreeMap, BTreeSet}, - ffi::OsStr, future::Future, iter::{self, Iterator, Peekable}, mem, num::NonZeroU32, ops::{Deref, Range}, - path::{Path, PathBuf}, + path::PathBuf, rc, sync::{Arc, LazyLock}, time::{Duration, Instant}, @@ -65,7 +64,7 @@ pub use text::{ use theme::{ActiveTheme as _, SyntaxTheme}; #[cfg(any(test, feature = "test-support"))] use util::RandomCharIter; -use util::{RangeExt, debug_panic, maybe}; +use util::{RangeExt, debug_panic, maybe, paths::PathStyle, rel_path::RelPath}; #[cfg(any(test, feature = "test-support"))] pub use {tree_sitter_python, tree_sitter_rust, tree_sitter_typescript}; @@ -349,15 +348,18 @@ pub trait File: Send + Sync + Any { fn disk_state(&self) -> DiskState; /// Returns the path of this file relative to the worktree's root directory. - fn path(&self) -> &Arc; + fn path(&self) -> &Arc; /// Returns the path of this file relative to the worktree's parent directory (this means it /// includes the name of the worktree's root folder). fn full_path(&self, cx: &App) -> PathBuf; + /// Returns the path style of this file. + fn path_style(&self, cx: &App) -> PathStyle; + /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. - fn file_name<'a>(&'a self, cx: &'a App) -> &'a OsStr; + fn file_name<'a>(&'a self, cx: &'a App) -> &'a str; /// Returns the id of the worktree to which this file belongs. /// @@ -4626,13 +4628,12 @@ impl BufferSnapshot { self.file.as_ref() } - /// Resolves the file path (relative to the worktree root) associated with the underlying file. pub fn resolve_file_path(&self, cx: &App, include_root: bool) -> Option { if let Some(file) = self.file() { if file.path().file_name().is_none() || include_root { Some(file.full_path(cx)) } else { - Some(file.path().to_path_buf()) + Some(file.path().as_std_path().to_owned()) } } else { None @@ -5117,19 +5118,19 @@ impl IndentSize { #[cfg(any(test, feature = "test-support"))] pub struct TestFile { - pub path: Arc, + pub path: Arc, pub root_name: String, pub local_root: Option, } #[cfg(any(test, feature = "test-support"))] impl File for TestFile { - fn path(&self) -> &Arc { + fn path(&self) -> &Arc { &self.path } fn full_path(&self, _: &gpui::App) -> PathBuf { - PathBuf::from(&self.root_name).join(self.path.as_ref()) + PathBuf::from(self.root_name.clone()).join(self.path.as_std_path()) } fn as_local(&self) -> Option<&dyn LocalFile> { @@ -5144,7 +5145,7 @@ impl File for TestFile { unimplemented!() } - fn file_name<'a>(&'a self, _: &'a gpui::App) -> &'a std::ffi::OsStr { + fn file_name<'a>(&'a self, _: &'a gpui::App) -> &'a str { self.path().file_name().unwrap_or(self.root_name.as_ref()) } @@ -5159,6 +5160,10 @@ impl File for TestFile { fn is_private(&self) -> bool { false } + + fn path_style(&self, _cx: &App) -> PathStyle { + PathStyle::local() + } } #[cfg(any(test, feature = "test-support"))] @@ -5166,7 +5171,7 @@ impl LocalFile for TestFile { fn abs_path(&self, _cx: &App) -> PathBuf { PathBuf::from(self.local_root.as_ref().unwrap()) .join(&self.root_name) - .join(self.path.as_ref()) + .join(self.path.as_std_path()) } fn load(&self, _cx: &App) -> Task> { diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index b416099e3cd6f887e16f688594c41273be5cd582..6c87ec5b5183bd5b37e9dd52d53c8fa0f8f28db1 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -24,6 +24,7 @@ use text::{BufferId, LineEnding}; use text::{Point, ToPoint}; use theme::ActiveTheme; use unindent::Unindent as _; +use util::rel_path::rel_path; use util::test::marked_text_offsets; use util::{RandomCharIter, assert_set_eq, post_inc, test::marked_text_ranges}; @@ -380,7 +381,7 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) fn file(path: &str) -> Arc { Arc::new(TestFile { - path: Path::new(path).into(), + path: Arc::from(rel_path(path)), root_name: "zed".into(), local_root: None, }) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3e9f3bf1bd0cb4719f5442e1b1bd9e357ac9efca..daa39788ee18e41543931d06c9309f1020825b8c 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -70,6 +70,7 @@ pub use toolchain::{ ToolchainMetadata, ToolchainScope, }; use tree_sitter::{self, Query, QueryCursor, WasmStore, wasmtime}; +use util::rel_path::RelPath; use util::serde::default_true; pub use buffer::Operation; @@ -307,7 +308,7 @@ pub trait LspAdapterDelegate: Send + Sync { ) -> Result>; async fn which(&self, command: &OsStr) -> Option; async fn shell_env(&self) -> HashMap; - async fn read_text_file(&self, path: PathBuf) -> Result; + async fn read_text_file(&self, path: &RelPath) -> Result; async fn try_exec(&self, binary: LanguageServerBinary) -> Result<()>; } diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 92efe122aa5a13e0d4b1c196c019d9090ce3aa22..ed186062d825553660ce83a386897e742f75aca5 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -753,7 +753,7 @@ impl LanguageRegistry { content: Option<&Rope>, user_file_types: Option<&FxHashMap, GlobSet>>, ) -> Option { - let filename = path.file_name().and_then(|name| name.to_str()); + let filename = path.file_name().and_then(|filename| filename.to_str()); // `Path.extension()` returns None for files with a leading '.' // and no other extension which is not the desired behavior here, // as we want `.zshrc` to result in extension being `Some("zshrc")` diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index bae356614b56f5017415937dbaccb83723313c9d..73904516f982ee31ada88f6bee486d5512853a11 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -390,7 +390,7 @@ impl EditPredictionSettings { file.as_local() .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx))) } else { - glob.matcher.is_match(file.path()) + glob.matcher.is_match(file.path().as_std_path()) } }) } @@ -798,6 +798,7 @@ pub struct JsxTagAutoCloseSettings { mod tests { use super::*; use gpui::TestAppContext; + use util::rel_path::rel_path; #[gpui::test] fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) { @@ -839,11 +840,11 @@ mod tests { const WORKTREE_NAME: &str = "project"; let make_test_file = |segments: &[&str]| -> Arc { - let mut path_buf = PathBuf::new(); - path_buf.extend(segments); + let path = segments.join("/"); + let path = rel_path(&path); Arc::new(TestFile { - path: path_buf.as_path().into(), + path: path.into(), root_name: WORKTREE_NAME.to_string(), local_root: Some(PathBuf::from(if cfg!(windows) { "C:\\absolute\\" @@ -896,7 +897,7 @@ mod tests { assert!(!settings.enabled_for_file(&test_file, &cx)); let test_file_root: Arc = Arc::new(TestFile { - path: PathBuf::from("file.rs").as_path().into(), + path: rel_path("file.rs").into(), root_name: WORKTREE_NAME.to_string(), local_root: Some(PathBuf::from("/absolute/")), }); @@ -928,8 +929,12 @@ mod tests { // Test tilde expansion let home = shellexpand::tilde("~").into_owned(); - let home_file = make_test_file(&[&home, "test.rs"]); - let settings = build_settings(&["~/test.rs"]); + let home_file = Arc::new(TestFile { + path: rel_path("test.rs").into(), + root_name: "the-dir".to_string(), + local_root: Some(PathBuf::from(home)), + }) as Arc; + let settings = build_settings(&["~/the-dir/test.rs"]); assert!(!settings.enabled_for_file(&home_file, &cx)); } diff --git a/crates/language/src/manifest.rs b/crates/language/src/manifest.rs index 3ca0ddf71da20f69d5d6440189d4a656bfbe7c9d..82ed164a032cb18d2d011f59938a0cd1410ba60f 100644 --- a/crates/language/src/manifest.rs +++ b/crates/language/src/manifest.rs @@ -1,7 +1,8 @@ -use std::{borrow::Borrow, path::Path, sync::Arc}; +use std::{borrow::Borrow, sync::Arc}; use gpui::SharedString; use settings::WorktreeId; +use util::rel_path::RelPath; #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ManifestName(SharedString); @@ -42,17 +43,17 @@ impl AsRef for ManifestName { /// For example, given a path like `foo/bar/baz`, a depth of 2 would explore `foo/bar/baz` and `foo/bar`, but not `foo`. pub struct ManifestQuery { /// Path to the file, relative to worktree root. - pub path: Arc, + pub path: Arc, pub depth: usize, pub delegate: Arc, } pub trait ManifestProvider { fn name(&self) -> ManifestName; - fn search(&self, query: ManifestQuery) -> Option>; + fn search(&self, query: ManifestQuery) -> Option>; } pub trait ManifestDelegate: Send + Sync { fn worktree_id(&self) -> WorktreeId; - fn exists(&self, path: &Path, is_dir: Option) -> bool; + fn exists(&self, path: &RelPath, is_dir: Option) -> bool; } diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index 2cc86881fbd515317d4d6f5949e82eb3da63a1bb..d3466307f368e7008eedbc8881aa78ab854bc08b 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -4,10 +4,7 @@ //! which is a set of tools used to interact with the projects written in said language. //! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override. -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{path::PathBuf, sync::Arc}; use async_trait::async_trait; use collections::HashMap; @@ -15,6 +12,7 @@ use fs::Fs; use gpui::{AsyncApp, SharedString}; use settings::WorktreeId; use task::ShellKind; +use util::rel_path::RelPath; use crate::{LanguageName, ManifestName}; @@ -23,6 +21,7 @@ use crate::{LanguageName, ManifestName}; pub struct Toolchain { /// User-facing label pub name: SharedString, + /// Absolute path pub path: SharedString, pub language_name: LanguageName, /// Full toolchain data (including language-specific details) @@ -37,7 +36,7 @@ pub struct Toolchain { /// - Only in the subproject they're currently in. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ToolchainScope { - Subproject(WorktreeId, Arc), + Subproject(WorktreeId, Arc), Project, /// Available in all projects on this box. It wouldn't make sense to show suggestions across machines. Global, @@ -97,7 +96,7 @@ pub trait ToolchainLister: Send + Sync + 'static { async fn list( &self, worktree_root: PathBuf, - subroot_relative_path: Arc, + subroot_relative_path: Arc, project_env: Option>, ) -> ToolchainList; @@ -134,7 +133,7 @@ pub trait LanguageToolchainStore: Send + Sync + 'static { async fn active_toolchain( self: Arc, worktree_id: WorktreeId, - relative_path: Arc, + relative_path: Arc, language_name: LanguageName, cx: &mut AsyncApp, ) -> Option; @@ -144,7 +143,7 @@ pub trait LocalLanguageToolchainStore: Send + Sync + 'static { fn active_toolchain( self: Arc, worktree_id: WorktreeId, - relative_path: &Arc, + relative_path: &Arc, language_name: LanguageName, cx: &mut AsyncApp, ) -> Option; @@ -155,7 +154,7 @@ impl LanguageToolchainStore for T { async fn active_toolchain( self: Arc, worktree_id: WorktreeId, - relative_path: Arc, + relative_path: Arc, language_name: LanguageName, cx: &mut AsyncApp, ) -> Option { diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index dccd33ffb6b90381d7d86c38e80ec95effd4daf7..002b6a15b40575d71e7828ed9fa55dd4c5d1b30a 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -19,7 +19,7 @@ use lsp::{ }; use serde::Serialize; use serde_json::Value; -use util::{ResultExt, fs::make_file_executable, maybe}; +use util::{ResultExt, fs::make_file_executable, maybe, rel_path::RelPath}; use crate::{LanguageServerRegistryProxy, LspAccess}; @@ -36,7 +36,7 @@ impl WorktreeDelegate for WorktreeDelegateAdapter { self.0.worktree_root_path().to_string_lossy().to_string() } - async fn read_text_file(&self, path: PathBuf) -> Result { + async fn read_text_file(&self, path: &RelPath) -> Result { self.0.read_text_file(path).await } diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index b750ad1621a4711ccbf827d6054ce50d168d6b29..8bf40ccc1a7f05ffd3c8ff971638d84ae4b7150c 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/crates/language_tools/src/lsp_button.rs @@ -21,6 +21,7 @@ use ui::{ DocumentationSide, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, Window, prelude::*, }; +use util::{ResultExt, rel_path::RelPath}; use workspace::{StatusItemView, Workspace}; use crate::lsp_log_view; @@ -148,6 +149,7 @@ impl LanguageServerState { return; }; let project = workspace.read(cx).project().clone(); + let path_style = project.read(cx).path_style(cx); let buffer_store = project.read(cx).buffer_store().clone(); let buffers = state .read(cx) @@ -159,6 +161,9 @@ impl LanguageServerState { servers.worktree.as_ref()?.upgrade()?.read(cx); let relative_path = abs_path.strip_prefix(&worktree.abs_path()).ok()?; + let relative_path = + RelPath::from_std_path(relative_path, path_style) + .log_err()?; let entry = worktree.entry_for_path(&relative_path)?; let project_path = project.read(cx).path_for_entry(entry.id, cx)?; @@ -767,7 +772,7 @@ impl LspButton { }); servers_with_health_checks.insert(&health.name); let worktree_name = - worktree.map(|worktree| SharedString::new(worktree.read(cx).root_name())); + worktree.map(|worktree| SharedString::new(worktree.read(cx).root_name_str())); let binary_status = state.language_servers.binary_statuses.get(&health.name); let server_data = ServerData::WithHealthCheck { @@ -826,7 +831,7 @@ impl LspButton { { Some((worktree, server_id)) => { let worktree_name = - SharedString::new(worktree.read(cx).root_name()); + SharedString::new(worktree.read(cx).root_name_str()); servers_per_worktree .entry(worktree_name.clone()) .or_default() diff --git a/crates/language_tools/src/lsp_log_view.rs b/crates/language_tools/src/lsp_log_view.rs index fb63ab9a99147328c4987bd80b698ef4a477f013..5c6b4faa14723107644fb22195f12952bba8ccb7 100644 --- a/crates/language_tools/src/lsp_log_view.rs +++ b/crates/language_tools/src/lsp_log_view.rs @@ -376,7 +376,7 @@ impl LspLogView { let worktree_root_name = state .worktree_id .and_then(|id| self.project.read(cx).worktree_for_id(id, cx)) - .map(|worktree| worktree.read(cx).root_name().to_string()) + .map(|worktree| worktree.read(cx).root_name_str().to_string()) .unwrap_or_else(|| "Unknown worktree".to_string()); LogMenuItem { diff --git a/crates/language_tools/src/lsp_log_view_tests.rs b/crates/language_tools/src/lsp_log_view_tests.rs index d572c4375ed09997dc57d6c58e6c90f3e55775b6..2ef915fdc386b69f1af604bb22abad58abb91d3a 100644 --- a/crates/language_tools/src/lsp_log_view_tests.rs +++ b/crates/language_tools/src/lsp_log_view_tests.rs @@ -91,7 +91,7 @@ async fn test_lsp_log_view(cx: &mut TestAppContext) { .next() .unwrap() .read(cx) - .root_name() + .root_name_str() .to_string(), rpc_trace_enabled: false, selected_entry: LogKind::Logs, diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 482c709d453c520486990409a37c27a50132dd77..512653eab7dca97fd1c9ce3fbd790466ca62abd4 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -30,7 +30,10 @@ use std::{ }; use task::{AdapterSchemas, TaskTemplate, TaskTemplates, VariableName}; use theme::ThemeRegistry; -use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into}; +use util::{ + ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into, + rel_path::RelPath, +}; use crate::PackageJsonData; @@ -52,8 +55,8 @@ impl ContextProvider for JsonTaskProvider { let Some(file) = project::File::from_dyn(file.as_ref()).cloned() else { return Task::ready(None); }; - let is_package_json = file.path.ends_with("package.json"); - let is_composer_json = file.path.ends_with("composer.json"); + let is_package_json = file.path.ends_with(RelPath::new("package.json").unwrap()); + let is_composer_json = file.path.ends_with(RelPath::new("composer.json").unwrap()); if !is_package_json && !is_composer_json { return Task::ready(None); } diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index a8824d3776b08bdfdb99d216c8ab75e88e714c6c..8b6abc9c7205a769d0630a3792e37589c0e844c5 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -24,6 +24,7 @@ use smol::lock::OnceCell; use std::cmp::Ordering; use std::env::consts; use util::fs::{make_file_executable, remove_matching}; +use util::rel_path::RelPath; use parking_lot::Mutex; use std::str::FromStr; @@ -52,9 +53,9 @@ impl ManifestProvider for PyprojectTomlManifestProvider { depth, delegate, }: ManifestQuery, - ) -> Option> { + ) -> Option> { for path in path.ancestors().take(depth) { - let p = path.join("pyproject.toml"); + let p = path.join(RelPath::new("pyproject.toml").unwrap()); if delegate.exists(&p, Some(false)) { return Some(path.into()); } @@ -679,7 +680,7 @@ impl ContextProvider for PythonContextProvider { .as_ref() .and_then(|f| f.path().parent()) .map(Arc::from) - .unwrap_or_else(|| Arc::from("".as_ref())); + .unwrap_or_else(|| RelPath::empty().into()); toolchains .active_toolchain(worktree_id, file_path, "Python".into(), cx) @@ -1012,7 +1013,7 @@ impl ToolchainLister for PythonToolchainProvider { async fn list( &self, worktree_root: PathBuf, - subroot_relative_path: Arc, + subroot_relative_path: Arc, project_env: Option>, ) -> ToolchainList { let env = project_env.unwrap_or_default(); @@ -1024,7 +1025,6 @@ impl ToolchainLister for PythonToolchainProvider { ); let mut config = Configuration::default(); - debug_assert!(subroot_relative_path.is_relative()); // `.ancestors()` will yield at least one path, so in case of empty `subroot_relative_path`, we'll just use // worktree root as the workspace directory. config.workspace_directories = Some( diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index f5401f448c324f11c2de3a6379c704df5441b3db..b8f5c78dcef829cf97dcdb5f1698e3fa429c25fa 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -23,6 +23,7 @@ use std::{ use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::fs::{make_file_executable, remove_matching}; use util::merge_json_value_into; +use util::rel_path::RelPath; use util::{ResultExt, maybe}; use crate::github_download::{GithubBinaryMetadata, download_server_binary}; @@ -88,10 +89,10 @@ impl ManifestProvider for CargoManifestProvider { depth, delegate, }: ManifestQuery, - ) -> Option> { + ) -> Option> { let mut outermost_cargo_toml = None; for path in path.ancestors().take(depth) { - let p = path.join("Cargo.toml"); + let p = path.join(RelPath::new("Cargo.toml").unwrap()); if delegate.exists(&p, Some(false)) { outermost_cargo_toml = Some(Arc::from(path)); } diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index edfddd3f76e374e9ae0d1ce71cfb0b7b3c586c4d..6e3661fa6f2807689a0e12580813ed7c7e56c0de 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -22,8 +22,8 @@ use std::{ sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, VariableName}; -use util::merge_json_value_into; use util::{ResultExt, fs::remove_matching, maybe}; +use util::{merge_json_value_into, rel_path::RelPath}; use crate::{PackageJson, PackageJsonData, github_download::download_server_binary}; @@ -264,7 +264,7 @@ impl TypeScriptContextProvider { &self, fs: Arc, worktree_root: &Path, - file_relative_path: &Path, + file_relative_path: &RelPath, cx: &App, ) -> Task> { let new_json_data = file_relative_path @@ -533,7 +533,7 @@ impl TypeScriptLspAdapter { } async fn tsdk_path(&self, adapter: &Arc) -> Option<&'static str> { let is_yarn = adapter - .read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js")) + .read_text_file(RelPath::new(".yarn/sdks/typescript/lib/typescript.js").unwrap()) .await .is_ok(); @@ -1014,7 +1014,7 @@ mod tests { use serde_json::json; use task::TaskTemplates; use unindent::Unindent; - use util::path; + use util::{path, rel_path::rel_path}; use crate::typescript::{ PackageJsonData, TypeScriptContextProvider, replace_test_name_parameters, @@ -1164,7 +1164,7 @@ mod tests { provider.combined_package_json_data( fs.clone(), path!("/root").as_ref(), - "sub/file1.js".as_ref(), + rel_path("sub/file1.js"), cx, ) }) diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index 763a0d5a5ed961d916e3deea44963b0aa9340cb9..053d15775b2f3ea6328b8dca269ed8144b0628b3 100644 --- a/crates/languages/src/vtsls.rs +++ b/crates/languages/src/vtsls.rs @@ -12,7 +12,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::{ResultExt, maybe, merge_json_value_into}; +use util::{ResultExt, maybe, merge_json_value_into, rel_path::RelPath}; fn typescript_server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] @@ -36,7 +36,7 @@ impl VtslsLspAdapter { async fn tsdk_path(&self, adapter: &Arc) -> Option<&'static str> { let is_yarn = adapter - .read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js")) + .read_text_file(RelPath::new(".yarn/sdks/typescript/lib/typescript.js").unwrap()) .await .is_ok(); diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index c629756324cbded19485e7ba9d420db3fd4bd093..45faa142369e6c08817deebfbf8774f228bf70d5 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -16,7 +16,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::{ResultExt, maybe, merge_json_value_into}; +use util::{ResultExt, maybe, merge_json_value_into, rel_path::RelPath}; const SERVER_PATH: &str = "node_modules/yaml-language-server/bin/yaml-language-server"; @@ -141,7 +141,7 @@ impl LspAdapter for YamlLspAdapter { ) -> Result { let location = SettingsLocation { worktree_id: delegate.worktree_id(), - path: delegate.worktree_root_path(), + path: RelPath::empty(), }; let tab_size = cx.update(|cx| { diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index fdf0f2bbf20190d15b533d02b9f0122746439c89..61846717237df76c5fcb0e91c2aad8e91cd683f9 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -334,7 +334,10 @@ impl Markdown { } for path in paths { - if let Ok(language) = registry.language_for_file_path(&path).await { + if let Ok(language) = registry + .language_for_file_path(Path::new(path.as_ref())) + .await + { languages_by_path.insert(path, language); } } @@ -434,7 +437,7 @@ pub struct ParsedMarkdown { pub source: SharedString, pub events: Arc<[(Range, MarkdownEvent)]>, pub languages_by_name: TreeMap>, - pub languages_by_path: TreeMap, Arc>, + pub languages_by_path: TreeMap, Arc>, } impl ParsedMarkdown { diff --git a/crates/markdown/src/parser.rs b/crates/markdown/src/parser.rs index 1b4d5b5755c0b825124f37f68466bae7c0838b1a..62b210f9e33a90a44790c521591ba6f94e8baaef 100644 --- a/crates/markdown/src/parser.rs +++ b/crates/markdown/src/parser.rs @@ -4,7 +4,7 @@ pub use pulldown_cmark::TagEnd as MarkdownTagEnd; use pulldown_cmark::{ Alignment, CowStr, HeadingLevel, LinkType, MetadataBlockKind, Options, Parser, }; -use std::{ops::Range, path::Path, sync::Arc}; +use std::{ops::Range, sync::Arc}; use collections::HashSet; @@ -25,7 +25,7 @@ pub fn parse_markdown( ) -> ( Vec<(Range, MarkdownEvent)>, HashSet, - HashSet>, + HashSet>, ) { let mut events = Vec::new(); let mut language_names = HashSet::default(); diff --git a/crates/markdown/src/path_range.rs b/crates/markdown/src/path_range.rs index 19cfda0d9dfb30c550852f64edcad29e3d1e1de9..f98325c9b5aa45420d4e1990d55888675d160d5f 100644 --- a/crates/markdown/src/path_range.rs +++ b/crates/markdown/src/path_range.rs @@ -1,8 +1,8 @@ -use std::{ops::Range, path::Path, sync::Arc}; +use std::{ops::Range, sync::Arc}; #[derive(Debug, Clone, PartialEq)] pub struct PathWithRange { - pub path: Arc, + pub path: Arc, pub range: Option>, } @@ -78,12 +78,12 @@ impl PathWithRange { }; Self { - path: Path::new(path).into(), + path: path.into(), range, } } None => Self { - path: Path::new(str).into(), + path: str.into(), range: None, }, } @@ -123,7 +123,7 @@ mod tests { #[test] fn test_pathrange_parsing() { let path_range = PathWithRange::new("file.rs#L10-L20"); - assert_eq!(path_range.path.as_ref(), Path::new("file.rs")); + assert_eq!(path_range.path.as_ref(), "file.rs"); assert!(path_range.range.is_some()); if let Some(range) = path_range.range { assert_eq!(range.start.line, 10); @@ -133,7 +133,7 @@ mod tests { } let single_line = PathWithRange::new("file.rs#L15"); - assert_eq!(single_line.path.as_ref(), Path::new("file.rs")); + assert_eq!(single_line.path.as_ref(), "file.rs"); assert!(single_line.range.is_some()); if let Some(range) = single_line.range { assert_eq!(range.start.line, 15); @@ -141,11 +141,11 @@ mod tests { } let no_range = PathWithRange::new("file.rs"); - assert_eq!(no_range.path.as_ref(), Path::new("file.rs")); + assert_eq!(no_range.path.as_ref(), "file.rs"); assert!(no_range.range.is_none()); let lowercase = PathWithRange::new("file.rs#l5-l10"); - assert_eq!(lowercase.path.as_ref(), Path::new("file.rs")); + assert_eq!(lowercase.path.as_ref(), "file.rs"); assert!(lowercase.range.is_some()); if let Some(range) = lowercase.range { assert_eq!(range.start.line, 5); @@ -153,7 +153,7 @@ mod tests { } let complex = PathWithRange::new("src/path/to/file.rs#L100"); - assert_eq!(complex.path.as_ref(), Path::new("src/path/to/file.rs")); + assert_eq!(complex.path.as_ref(), "src/path/to/file.rs"); assert!(complex.range.is_some()); } @@ -161,7 +161,7 @@ mod tests { fn test_pathrange_from_str() { let with_range = PathWithRange::new("file.rs#L10-L20"); assert!(with_range.range.is_some()); - assert_eq!(with_range.path.as_ref(), Path::new("file.rs")); + assert_eq!(with_range.path.as_ref(), "file.rs"); let without_range = PathWithRange::new("file.rs"); assert!(without_range.range.is_none()); @@ -173,18 +173,18 @@ mod tests { #[test] fn test_pathrange_leading_text_trimming() { let with_language = PathWithRange::new("```rust file.rs#L10"); - assert_eq!(with_language.path.as_ref(), Path::new("file.rs")); + assert_eq!(with_language.path.as_ref(), "file.rs"); assert!(with_language.range.is_some()); if let Some(range) = with_language.range { assert_eq!(range.start.line, 10); } let with_spaces = PathWithRange::new("``` file.rs#L10-L20"); - assert_eq!(with_spaces.path.as_ref(), Path::new("file.rs")); + assert_eq!(with_spaces.path.as_ref(), "file.rs"); assert!(with_spaces.range.is_some()); let with_words = PathWithRange::new("```rust code example file.rs#L15:10"); - assert_eq!(with_words.path.as_ref(), Path::new("file.rs")); + assert_eq!(with_words.path.as_ref(), "file.rs"); assert!(with_words.range.is_some()); if let Some(range) = with_words.range { assert_eq!(range.start.line, 15); @@ -192,18 +192,18 @@ mod tests { } let with_whitespace = PathWithRange::new(" file.rs#L5"); - assert_eq!(with_whitespace.path.as_ref(), Path::new("file.rs")); + assert_eq!(with_whitespace.path.as_ref(), "file.rs"); assert!(with_whitespace.range.is_some()); let no_leading = PathWithRange::new("file.rs#L10"); - assert_eq!(no_leading.path.as_ref(), Path::new("file.rs")); + assert_eq!(no_leading.path.as_ref(), "file.rs"); assert!(no_leading.range.is_some()); } #[test] fn test_pathrange_with_line_and_column() { let line_and_col = PathWithRange::new("file.rs#L10:5"); - assert_eq!(line_and_col.path.as_ref(), Path::new("file.rs")); + assert_eq!(line_and_col.path.as_ref(), "file.rs"); assert!(line_and_col.range.is_some()); if let Some(range) = line_and_col.range { assert_eq!(range.start.line, 10); @@ -213,7 +213,7 @@ mod tests { } let full_range = PathWithRange::new("file.rs#L10:5-L20:15"); - assert_eq!(full_range.path.as_ref(), Path::new("file.rs")); + assert_eq!(full_range.path.as_ref(), "file.rs"); assert!(full_range.range.is_some()); if let Some(range) = full_range.range { assert_eq!(range.start.line, 10); @@ -223,7 +223,7 @@ mod tests { } let mixed_range1 = PathWithRange::new("file.rs#L10:5-L20"); - assert_eq!(mixed_range1.path.as_ref(), Path::new("file.rs")); + assert_eq!(mixed_range1.path.as_ref(), "file.rs"); assert!(mixed_range1.range.is_some()); if let Some(range) = mixed_range1.range { assert_eq!(range.start.line, 10); @@ -233,7 +233,7 @@ mod tests { } let mixed_range2 = PathWithRange::new("file.rs#L10-L20:15"); - assert_eq!(mixed_range2.path.as_ref(), Path::new("file.rs")); + assert_eq!(mixed_range2.path.as_ref(), "file.rs"); assert!(mixed_range2.range.is_some()); if let Some(range) = mixed_range2.range { assert_eq!(range.start.line, 10); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index cec1297e6fd52a8f2be273fb1b375b6610190416..15d6132d972b787f4d12ead244586a52b2ef96b2 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -37,7 +37,6 @@ use std::{ iter::{self, FromIterator}, mem, ops::{Range, RangeBounds, Sub}, - path::{Path, PathBuf}, rc::Rc, str, sync::Arc, @@ -169,23 +168,23 @@ impl MultiBufferDiffHunk { #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)] pub struct PathKey { namespace: u32, - path: Arc, + path: Arc, } impl PathKey { - pub fn namespaced(namespace: u32, path: Arc) -> Self { + pub fn namespaced(namespace: u32, path: Arc) -> Self { Self { namespace, path } } pub fn for_buffer(buffer: &Entity, cx: &App) -> Self { if let Some(file) = buffer.read(cx).file() { - Self::namespaced(1, Arc::from(file.full_path(cx))) + Self::namespaced(1, file.full_path(cx).to_string_lossy().to_string().into()) } else { - Self::namespaced(0, Arc::from(PathBuf::from(buffer.entity_id().to_string()))) + Self::namespaced(0, buffer.entity_id().to_string().into()) } } - pub fn path(&self) -> &Arc { + pub fn path(&self) -> &Arc { &self.path } } @@ -2603,7 +2602,7 @@ impl MultiBuffer { let buffer = buffer.read(cx); if let Some(file) = buffer.file() { - return file.file_name(cx).to_string_lossy(); + return file.file_name(cx).into(); } if let Some(title) = self.buffer_content_title(buffer) { diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index 1be82500786b36fc014c2acf4fb49d4e8abc4d6b..a63fcbd60b912e1e6aba885dcacc882efd405117 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -1524,7 +1524,7 @@ fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) { cx, ) }); - let path1: PathKey = PathKey::namespaced(0, Path::new("/").into()); + let path1: PathKey = PathKey::namespaced(0, "/".into()); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); multibuffer.update(cx, |multibuffer, cx| { @@ -1619,7 +1619,7 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) { cx, ) }); - let path1: PathKey = PathKey::namespaced(0, Path::new("/").into()); + let path1: PathKey = PathKey::namespaced(0, "/".into()); let buf2 = cx.new(|cx| { Buffer::local( indoc! { @@ -1638,7 +1638,7 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) { cx, ) }); - let path2 = PathKey::namespaced(1, Path::new("/").into()); + let path2 = PathKey::namespaced(1, "/".into()); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); multibuffer.update(cx, |multibuffer, cx| { @@ -1815,7 +1815,7 @@ fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) { cx, ) }); - let path: PathKey = PathKey::namespaced(0, Path::new("/").into()); + let path: PathKey = PathKey::namespaced(0, "/".into()); let buf2 = cx.new(|cx| { Buffer::local( indoc! { diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 1f85d08cee850f18a30cca9b56061854f7cbc7b1..1d68240f6bcb8d3de042f6906793d0fece705003 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -389,7 +389,7 @@ mod tests { use language::{Language, LanguageConfig, LanguageMatcher}; use project::{FakeFs, Project}; use serde_json::json; - use util::path; + use util::{path, rel_path::rel_path}; use workspace::{AppState, Workspace}; #[gpui::test] @@ -430,7 +430,7 @@ mod tests { .unwrap(); let editor = workspace .update_in(cx, |workspace, window, cx| { - workspace.open_path((worktree_id, "a.rs"), None, true, window, cx) + workspace.open_path((worktree_id, rel_path("a.rs")), None, true, window, cx) }) .await .unwrap() diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 863725ad851d5b2e2e44895d97d59fdb2f062f1a..4ba636921844404590e06ea7c5402fffd9de41f7 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -29,7 +29,7 @@ use std::{ collections::BTreeMap, hash::Hash, ops::Range, - path::{MAIN_SEPARATOR_STR, Path, PathBuf}, + path::{Path, PathBuf}, sync::{ Arc, OnceLock, atomic::{self, AtomicBool}, @@ -51,7 +51,7 @@ use ui::{ IndentGuideLayout, Label, LabelCommon, ListItem, ScrollAxes, Scrollbars, StyledExt, StyledTypography, Toggleable, Tooltip, WithScrollbar, h_flex, v_flex, }; -use util::{RangeExt, ResultExt, TryFutureExt, debug_panic}; +use util::{RangeExt, ResultExt, TryFutureExt, debug_panic, rel_path::RelPath}; use workspace::{ OpenInTerminal, WeakItemHandle, Workspace, dock::{DockPosition, Panel, PanelEvent}, @@ -107,7 +107,7 @@ pub struct OutlinePanel { pending_serialization: Task>, fs_entries_depth: HashMap<(WorktreeId, ProjectEntryId), usize>, fs_entries: Vec, - fs_children_count: HashMap, FsChildren>>, + fs_children_count: HashMap, FsChildren>>, collapsed_entries: HashSet, unfolded_dirs: HashMap>, selected_entry: SelectedEntry, @@ -1905,6 +1905,7 @@ impl OutlinePanel { _: &mut Window, cx: &mut Context, ) { + let path_style = self.project.read(cx).path_style(cx); if let Some(clipboard_text) = self .selected_entry() .and_then(|entry| match entry { @@ -1914,7 +1915,7 @@ impl OutlinePanel { } PanelEntry::Search(_) | PanelEntry::Outline(..) => None, }) - .map(|p| p.to_string_lossy().to_string()) + .map(|p| p.display(path_style).to_string()) { cx.write_to_clipboard(ClipboardItem::new_string(clipboard_text)); } @@ -2272,7 +2273,7 @@ impl OutlinePanel { let color = entry_git_aware_label_color(entry.git_summary, entry.is_ignored, is_active); let icon = if settings.file_icons { - FileIcons::get_icon(&entry.path, cx) + FileIcons::get_icon(entry.path.as_std_path(), cx) .map(|icon_path| Icon::from_path(icon_path).color(color).into_any_element()) } else { None @@ -2303,7 +2304,7 @@ impl OutlinePanel { is_active, ); let icon = if settings.folder_icons { - FileIcons::get_folder_icon(is_expanded, &directory.entry.path, cx) + FileIcons::get_folder_icon(is_expanded, directory.entry.path.as_std_path(), cx) } else { FileIcons::get_chevron_icon(is_expanded, cx) } @@ -2329,13 +2330,13 @@ impl OutlinePanel { Some(file) => { let path = file.path(); let icon = if settings.file_icons { - FileIcons::get_icon(path.as_ref(), cx) + FileIcons::get_icon(path.as_std_path(), cx) } else { None } .map(Icon::from_path) .map(|icon| icon.color(color).into_any_element()); - (icon, file_name(path.as_ref())) + (icon, file_name(path.as_std_path())) } None => (None, "Untitled".to_string()), }, @@ -2615,19 +2616,17 @@ impl OutlinePanel { if root_entry.id == entry.id { file_name(worktree.abs_path().as_ref()) } else { - let path = worktree.absolutize(entry.path.as_ref()).ok(); - let path = path.as_deref().unwrap_or_else(|| entry.path.as_ref()); - file_name(path) + let path = worktree.absolutize(entry.path.as_ref()); + file_name(&path) } } None => { - let path = worktree.absolutize(entry.path.as_ref()).ok(); - let path = path.as_deref().unwrap_or_else(|| entry.path.as_ref()); - file_name(path) + let path = worktree.absolutize(entry.path.as_ref()); + file_name(&path) } } } - None => file_name(entry.path.as_ref()), + None => file_name(entry.path.as_std_path()), } } @@ -2842,7 +2841,7 @@ impl OutlinePanel { } let mut new_children_count = - HashMap::, FsChildren>>::default(); + HashMap::, FsChildren>>::default(); let worktree_entries = new_worktree_entries .into_iter() @@ -3518,17 +3517,17 @@ impl OutlinePanel { .buffer_snapshot_for_id(*buffer_id, cx) .and_then(|buffer_snapshot| { let file = File::from_dyn(buffer_snapshot.file())?; - file.worktree.read(cx).absolutize(&file.path).ok() + Some(file.worktree.read(cx).absolutize(&file.path)) }), PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { worktree_id, entry, .. - })) => self - .project - .read(cx) - .worktree_for_id(*worktree_id, cx)? - .read(cx) - .absolutize(&entry.path) - .ok(), + })) => Some( + self.project + .read(cx) + .worktree_for_id(*worktree_id, cx)? + .read(cx) + .absolutize(&entry.path), + ), PanelEntry::FoldedDirs(FoldedDirsEntry { worktree_id, entries: dirs, @@ -3537,13 +3536,13 @@ impl OutlinePanel { self.project .read(cx) .worktree_for_id(*worktree_id, cx) - .and_then(|worktree| worktree.read(cx).absolutize(&entry.path).ok()) + .map(|worktree| worktree.read(cx).absolutize(&entry.path)) }), PanelEntry::Search(_) | PanelEntry::Outline(..) => None, } } - fn relative_path(&self, entry: &FsEntry, cx: &App) -> Option> { + fn relative_path(&self, entry: &FsEntry, cx: &App) -> Option> { match entry { FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { let buffer_snapshot = self.buffer_snapshot_for_id(*buffer_id, cx)?; @@ -3627,7 +3626,7 @@ impl OutlinePanel { #[derive(Debug)] struct ParentStats { - path: Arc, + path: Arc, folded: bool, expanded: bool, depth: usize, @@ -4023,8 +4022,9 @@ impl OutlinePanel { let id = state.entries.len(); match &entry { PanelEntry::Fs(fs_entry) => { - if let Some(file_name) = - self.relative_path(fs_entry, cx).as_deref().map(file_name) + if let Some(file_name) = self + .relative_path(fs_entry, cx) + .and_then(|path| Some(path.file_name()?.to_string())) { state .match_candidates @@ -4477,21 +4477,19 @@ impl OutlinePanel { let item_text_chars = match entry { PanelEntry::Fs(FsEntry::ExternalFile(external)) => self .buffer_snapshot_for_id(external.buffer_id, cx) - .and_then(|snapshot| { - Some(snapshot.file()?.path().file_name()?.to_string_lossy().len()) - }) + .and_then(|snapshot| Some(snapshot.file()?.path().file_name()?.len())) .unwrap_or_default(), PanelEntry::Fs(FsEntry::Directory(directory)) => directory .entry .path .file_name() - .map(|name| name.to_string_lossy().len()) + .map(|name| name.len()) .unwrap_or_default(), PanelEntry::Fs(FsEntry::File(file)) => file .entry .path .file_name() - .map(|name| name.to_string_lossy().len()) + .map(|name| name.len()) .unwrap_or_default(), PanelEntry::FoldedDirs(folded_dirs) => { folded_dirs @@ -4500,11 +4498,11 @@ impl OutlinePanel { .map(|dir| { dir.path .file_name() - .map(|name| name.to_string_lossy().len()) + .map(|name| name.len()) .unwrap_or_default() }) .sum::() - + folded_dirs.entries.len().saturating_sub(1) * MAIN_SEPARATOR_STR.len() + + folded_dirs.entries.len().saturating_sub(1) * "/".len() } PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => self .excerpt_label(excerpt.buffer_id, &excerpt.range, cx) @@ -4799,7 +4797,7 @@ fn workspace_active_editor( } fn back_to_common_visited_parent( - visited_dirs: &mut Vec<(ProjectEntryId, Arc)>, + visited_dirs: &mut Vec<(ProjectEntryId, Arc)>, worktree_id: &WorktreeId, new_entry: &Entry, ) -> Option<(WorktreeId, ProjectEntryId)> { @@ -5281,16 +5279,15 @@ mod tests { }); }); - let all_matches = format!( - r#"{root}/ + let all_matches = r#"rust-analyzer/ crates/ ide/src/ inlay_hints/ fn_lifetime_fn.rs - search: match config.param_names_for_lifetime_elision_hints {{ - search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {{ - search: Some(it) if config.param_names_for_lifetime_elision_hints => {{ - search: InlayHintsConfig {{ param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }}, + search: match config.param_names_for_lifetime_elision_hints { + search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { + search: Some(it) if config.param_names_for_lifetime_elision_hints => { + search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, inlay_hints.rs search: pub param_names_for_lifetime_elision_hints: bool, search: param_names_for_lifetime_elision_hints: self @@ -5302,7 +5299,7 @@ mod tests { search: param_names_for_lifetime_elision_hints: true, config.rs search: param_names_for_lifetime_elision_hints: self"# - ); + .to_string(); let select_first_in_all_matches = |line_to_select: &str| { assert!(all_matches.contains(line_to_select)); @@ -5360,7 +5357,7 @@ mod tests { cx, ), format!( - r#"{root}/ + r#"rust-analyzer/ crates/ ide/src/ inlay_hints/ @@ -5430,7 +5427,7 @@ mod tests { cx, ), format!( - r#"{root}/ + r#"rust-analyzer/ crates/ ide/src/{SELECTED_MARKER} rust-analyzer/src/ @@ -5513,16 +5510,15 @@ mod tests { ); }); }); - let all_matches = format!( - r#"{root}/ + let all_matches = r#"rust-analyzer/ crates/ ide/src/ inlay_hints/ fn_lifetime_fn.rs - search: match config.param_names_for_lifetime_elision_hints {{ - search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {{ - search: Some(it) if config.param_names_for_lifetime_elision_hints => {{ - search: InlayHintsConfig {{ param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }}, + search: match config.param_names_for_lifetime_elision_hints { + search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { + search: Some(it) if config.param_names_for_lifetime_elision_hints => { + search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, inlay_hints.rs search: pub param_names_for_lifetime_elision_hints: bool, search: param_names_for_lifetime_elision_hints: self @@ -5534,7 +5530,7 @@ mod tests { search: param_names_for_lifetime_elision_hints: true, config.rs search: param_names_for_lifetime_elision_hints: self"# - ); + .to_string(); cx.executor() .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); @@ -5653,16 +5649,15 @@ mod tests { ); }); }); - let all_matches = format!( - r#"{root}/ + let all_matches = r#"rust-analyzer/ crates/ ide/src/ inlay_hints/ fn_lifetime_fn.rs - search: match config.param_names_for_lifetime_elision_hints {{ - search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {{ - search: Some(it) if config.param_names_for_lifetime_elision_hints => {{ - search: InlayHintsConfig {{ param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }}, + search: match config.param_names_for_lifetime_elision_hints { + search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { + search: Some(it) if config.param_names_for_lifetime_elision_hints => { + search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, inlay_hints.rs search: pub param_names_for_lifetime_elision_hints: bool, search: param_names_for_lifetime_elision_hints: self @@ -5674,7 +5669,7 @@ mod tests { search: param_names_for_lifetime_elision_hints: true, config.rs search: param_names_for_lifetime_elision_hints: self"# - ); + .to_string(); let select_first_in_all_matches = |line_to_select: &str| { assert!(all_matches.contains(line_to_select)); all_matches.replacen( @@ -5904,15 +5899,13 @@ mod tests { cx, ), format!( - r#"{}/ + r#"one/ a.txt search: aaa aaa <==== selected search: aaa aaa -{}/ +two/ b.txt search: a aaa"#, - path!("/root/one"), - path!("/root/two"), ), ); }); @@ -5934,13 +5927,11 @@ mod tests { cx, ), format!( - r#"{}/ + r#"one/ a.txt <==== selected -{}/ +two/ b.txt search: a aaa"#, - path!("/root/one"), - path!("/root/two"), ), ); }); @@ -5962,11 +5953,9 @@ mod tests { cx, ), format!( - r#"{}/ + r#"one/ a.txt -{}/ <==== selected"#, - path!("/root/one"), - path!("/root/two"), +two/ <==== selected"#, ), ); }); @@ -5987,13 +5976,11 @@ mod tests { cx, ), format!( - r#"{}/ + r#"one/ a.txt -{}/ <==== selected +two/ <==== selected b.txt search: a aaa"#, - path!("/root/one"), - path!("/root/two"), ) ); }); @@ -6455,7 +6442,7 @@ outline: struct OutlineEntryExcerpt cx, ), format!( - r#"{root}/ + r#"frontend-project/ public/lottie/ syntax-tree.json search: {{ "something": "static" }} <==== selected @@ -6494,7 +6481,7 @@ outline: struct OutlineEntryExcerpt cx, ), format!( - r#"{root}/ + r#"frontend-project/ public/lottie/ syntax-tree.json search: {{ "something": "static" }} @@ -6524,7 +6511,7 @@ outline: struct OutlineEntryExcerpt cx, ), format!( - r#"{root}/ + r#"frontend-project/ public/lottie/ syntax-tree.json search: {{ "something": "static" }} @@ -6558,7 +6545,7 @@ outline: struct OutlineEntryExcerpt cx, ), format!( - r#"{root}/ + r#"frontend-project/ public/lottie/ syntax-tree.json search: {{ "something": "static" }} @@ -6591,7 +6578,7 @@ outline: struct OutlineEntryExcerpt cx, ), format!( - r#"{root}/ + r#"frontend-project/ public/lottie/ syntax-tree.json search: {{ "something": "static" }} @@ -6649,6 +6636,7 @@ outline: struct OutlineEntryExcerpt selected_entry: Option<&PanelEntry>, cx: &mut App, ) -> String { + let project = project.read(cx); let mut display_string = String::new(); for entry in cached_entries { if !display_string.is_empty() { @@ -6663,44 +6651,39 @@ outline: struct OutlineEntryExcerpt panic!("Did not cover external files with tests") } FsEntry::Directory(directory) => { - match project - .read(cx) + let path = if let Some(worktree) = project .worktree_for_id(directory.worktree_id, cx) - .and_then(|worktree| { - if worktree.read(cx).root_entry() == Some(&directory.entry.entry) { - Some(worktree.read(cx).abs_path()) - } else { - None - } + .filter(|worktree| { + worktree.read(cx).root_entry() == Some(&directory.entry.entry) }) { - Some(root_path) => format!( - "{}/{}", - root_path.display(), - directory.entry.path.display(), - ), - None => format!( - "{}/", - directory - .entry - .path - .file_name() - .unwrap_or_default() - .to_string_lossy() - ), - } + worktree + .read(cx) + .root_name() + .join(&directory.entry.path) + .as_str() + .to_string() + } else { + directory + .entry + .path + .file_name() + .unwrap_or_default() + .to_string() + }; + format!("{path}/") } FsEntry::File(file) => file .entry .path .file_name() - .map(|name| name.to_string_lossy().to_string()) + .map(|name| name.to_string()) .unwrap_or_default(), }, PanelEntry::FoldedDirs(folded_dirs) => folded_dirs .entries .iter() .filter_map(|dir| dir.path.file_name()) - .map(|name| name.to_string_lossy().to_string() + "/") + .map(|name| name.to_string() + "/") .collect(), PanelEntry::Outline(outline_entry) => match outline_entry { OutlineEntry::Excerpt(_) => continue, diff --git a/crates/paths/src/paths.rs b/crates/paths/src/paths.rs index af1b97c1cb9367ab73562ae4ebd044a2b79d1604..b860cc1b02207d3d489624028f35d796bfc688fc 100644 --- a/crates/paths/src/paths.rs +++ b/crates/paths/src/paths.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; use std::sync::OnceLock; pub use util::paths::home_dir; +use util::rel_path::RelPath; /// A default editorconfig file name to use when resolving project settings. pub const EDITORCONFIG_NAME: &str = ".editorconfig"; @@ -29,13 +30,13 @@ static CURRENT_DATA_DIR: OnceLock = OnceLock::new(); static CONFIG_DIR: OnceLock = OnceLock::new(); /// Returns the relative path to the zed_server directory on the ssh host. -pub fn remote_server_dir_relative() -> &'static Path { - Path::new(".zed_server") +pub fn remote_server_dir_relative() -> &'static RelPath { + RelPath::new(".zed_server").unwrap() } /// Returns the relative path to the zed_wsl_server directory on the wsl host. -pub fn remote_wsl_server_dir_relative() -> &'static Path { - Path::new(".zed_wsl_server") +pub fn remote_wsl_server_dir_relative() -> &'static RelPath { + RelPath::new(".zed_wsl_server").unwrap() } /// Sets a custom directory for all user data, overriding the default data directory. @@ -398,28 +399,28 @@ pub fn remote_servers_dir() -> &'static PathBuf { } /// Returns the relative path to a `.zed` folder within a project. -pub fn local_settings_folder_relative_path() -> &'static Path { - Path::new(".zed") +pub fn local_settings_folder_name() -> &'static str { + ".zed" } /// Returns the relative path to a `.vscode` folder within a project. -pub fn local_vscode_folder_relative_path() -> &'static Path { - Path::new(".vscode") +pub fn local_vscode_folder_name() -> &'static str { + ".vscode" } /// Returns the relative path to a `settings.json` file within a project. -pub fn local_settings_file_relative_path() -> &'static Path { - Path::new(".zed/settings.json") +pub fn local_settings_file_relative_path() -> &'static RelPath { + RelPath::new(".zed/settings.json").unwrap() } /// Returns the relative path to a `tasks.json` file within a project. -pub fn local_tasks_file_relative_path() -> &'static Path { - Path::new(".zed/tasks.json") +pub fn local_tasks_file_relative_path() -> &'static RelPath { + RelPath::new(".zed/tasks.json").unwrap() } /// Returns the relative path to a `.vscode/tasks.json` file within a project. -pub fn local_vscode_tasks_file_relative_path() -> &'static Path { - Path::new(".vscode/tasks.json") +pub fn local_vscode_tasks_file_relative_path() -> &'static RelPath { + RelPath::new(".vscode/tasks.json").unwrap() } pub fn debug_task_file_name() -> &'static str { @@ -432,13 +433,13 @@ pub fn task_file_name() -> &'static str { /// Returns the relative path to a `debug.json` file within a project. /// .zed/debug.json -pub fn local_debug_file_relative_path() -> &'static Path { - Path::new(".zed/debug.json") +pub fn local_debug_file_relative_path() -> &'static RelPath { + RelPath::new(".zed/debug.json").unwrap() } /// Returns the relative path to a `.vscode/launch.json` file within a project. -pub fn local_vscode_launch_file_relative_path() -> &'static Path { - Path::new(".vscode/launch.json") +pub fn local_vscode_launch_file_relative_path() -> &'static RelPath { + RelPath::new(".vscode/launch.json").unwrap() } pub fn user_ssh_config_file() -> PathBuf { diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 32e39d466f1a236da72b746fb4bf2a24b7300385..b9c40e814c4caf760123cf460e2eed7298f9e951 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -12,7 +12,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::paths::PathMatcher; +use util::paths::{PathMatcher, PathStyle}; #[derive(Debug, Clone)] pub enum Prettier { @@ -119,7 +119,7 @@ impl Prettier { None } }).any(|workspace_definition| { - workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition]).ok().is_some_and(|path_matcher| path_matcher.is_match(subproject_path)) + workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition], PathStyle::local()).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:?}"); @@ -215,11 +215,14 @@ impl Prettier { }) .any(|workspace_definition| { workspace_definition == subproject_path.to_string_lossy() - || PathMatcher::new(&[workspace_definition]) - .ok() - .is_some_and(|path_matcher| { - path_matcher.is_match(subproject_path) - }) + || PathMatcher::new( + &[workspace_definition], + PathStyle::local(), + ) + .ok() + .is_some_and( + |path_matcher| path_matcher.is_match(subproject_path), + ) }) { let workspace_ignore = path_to_check.join(".prettierignore"); diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 1af622df351f05721c850debebfe5b3e84284dad..39dc0621732bfd42b3a24735ad803915fbf2885c 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -58,7 +58,6 @@ lsp.workspace = true markdown.workspace = true node_runtime.workspace = true parking_lot.workspace = true -pathdiff.workspace = true paths.workspace = true postage.workspace = true prettier.workspace = true diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index 3d270bcb0db13e9c616687ce5d40cbc48bd4cbb9..c11a142b57634e0bc6d4cca66c893cc19d599193 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -16,10 +16,7 @@ use gpui::{ }; use node_runtime::NodeRuntime; use remote::RemoteClient; -use rpc::{ - AnyProtoClient, TypedEnvelope, - proto::{self, ToProto}, -}; +use rpc::{AnyProtoClient, TypedEnvelope, proto}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{SettingsContent, SettingsStore}; @@ -845,7 +842,7 @@ impl ExternalAgentServer for LocalGemini { // Gemini CLI doesn't seem to have a dedicated invocation for logging in--we just run it normally without any arguments. let login = task::SpawnInTerminal { - command: Some(command.path.clone().to_proto()), + command: Some(command.path.to_string_lossy().to_string()), args: command.args.clone(), env: command.env.clone().unwrap_or_default(), label: "gemini /auth".into(), @@ -854,7 +851,7 @@ impl ExternalAgentServer for LocalGemini { command.env.get_or_insert_default().extend(extra_env); command.args.push("--experimental-acp".into()); - Ok((command, root_dir.to_proto(), Some(login))) + Ok((command, root_dir.to_string_lossy().to_string(), Some(login))) }) } @@ -922,7 +919,7 @@ impl ExternalAgentServer for LocalClaudeCode { path.strip_suffix("/@zed-industries/claude-code-acp/dist/index.js") }) .map(|path_prefix| task::SpawnInTerminal { - command: Some(command.path.clone().to_proto()), + command: Some(command.path.to_string_lossy().to_string()), args: vec![ Path::new(path_prefix) .join("@anthropic-ai/claude-code/cli.js") @@ -938,7 +935,7 @@ impl ExternalAgentServer for LocalClaudeCode { }; command.env.get_or_insert_default().extend(extra_env); - Ok((command, root_dir.to_proto(), login)) + Ok((command, root_dir.to_string_lossy().to_string(), login)) }) } @@ -977,7 +974,7 @@ impl ExternalAgentServer for LocalCustomAgent { env.extend(command.env.unwrap_or_default()); env.extend(extra_env); command.env = Some(env); - Ok((command, root_dir.to_proto(), None)) + Ok((command, root_dir.to_string_lossy().to_string(), None)) }) } diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 07f8e0c95cf8551803d5f5828703dbec090fcedb..8a4d4f7918c12abd94cf7bf8fc97c939db7ce033 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -21,12 +21,12 @@ use language::{ }; use rpc::{ AnyProtoClient, ErrorCode, ErrorExt as _, TypedEnvelope, - proto::{self, ToProto}, + proto::{self}, }; use smol::channel::Receiver; -use std::{io, path::Path, pin::pin, sync::Arc, time::Instant}; +use std::{io, pin::pin, sync::Arc, time::Instant}; use text::BufferId; -use util::{ResultExt as _, TryFutureExt, debug_panic, maybe}; +use util::{ResultExt as _, TryFutureExt, debug_panic, maybe, rel_path::RelPath}; use worktree::{File, PathChange, ProjectEntryId, Worktree, WorktreeId}; /// A set of open buffers. @@ -292,7 +292,7 @@ impl RemoteBufferStore { fn open_buffer( &self, - path: Arc, + path: Arc, worktree: Entity, cx: &mut Context, ) -> Task>> { @@ -370,7 +370,7 @@ impl LocalBufferStore { &self, buffer_handle: Entity, worktree: Entity, - path: Arc, + path: Arc, mut has_changed_file: bool, cx: &mut Context, ) -> Task> { @@ -389,7 +389,7 @@ impl LocalBufferStore { } let save = worktree.update(cx, |worktree, cx| { - worktree.write_file(path.as_ref(), text, line_ending, cx) + worktree.write_file(path, text, line_ending, cx) }); cx.spawn(async move |this, cx| { @@ -443,7 +443,7 @@ impl LocalBufferStore { fn local_worktree_entries_changed( this: &mut BufferStore, worktree_handle: &Entity, - changes: &[(Arc, ProjectEntryId, PathChange)], + changes: &[(Arc, ProjectEntryId, PathChange)], cx: &mut Context, ) { let snapshot = worktree_handle.read(cx).snapshot(); @@ -462,7 +462,7 @@ impl LocalBufferStore { fn local_worktree_entry_changed( this: &mut BufferStore, entry_id: ProjectEntryId, - path: &Arc, + path: &Arc, worktree: &Entity, snapshot: &worktree::Snapshot, cx: &mut Context, @@ -615,7 +615,7 @@ impl LocalBufferStore { fn open_buffer( &self, - path: Arc, + path: Arc, worktree: Entity, cx: &mut Context, ) -> Task>> { @@ -1402,8 +1402,9 @@ impl BufferStore { .await?; 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); + if let Some(new_path) = envelope.payload.new_path + && let Some(new_path) = ProjectPath::from_proto(new_path) + { this.update(&mut cx, |this, cx| { this.save_buffer_as(buffer.clone(), new_path, cx) })? diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 364128ae4f8cf5703bf7987117b0109462fa4e3c..e4673744da6883a5aa298c2fca3a39f556cb1357 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -1,7 +1,7 @@ pub mod extension; pub mod registry; -use std::{path::Path, sync::Arc}; +use std::sync::Arc; use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet}; @@ -10,7 +10,7 @@ use futures::{FutureExt as _, future::join_all}; use gpui::{App, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity, actions}; use registry::ContextServerDescriptorRegistry; use settings::{Settings as _, SettingsStore}; -use util::ResultExt as _; +use util::{ResultExt as _, rel_path::RelPath}; use crate::{ Project, @@ -510,7 +510,7 @@ impl ContextServerStore { .next() .map(|worktree| settings::SettingsLocation { worktree_id: worktree.read(cx).id(), - path: Path::new(""), + path: RelPath::empty(), }); &ProjectSettings::get(location, cx).context_servers } diff --git a/crates/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs index c47e5d35d5948eb0c176bbc6d14281faa3f60451..42663ab9852a5dc2e9850d20dd20940c6723d03c 100644 --- a/crates/project/src/debugger/breakpoint_store.rs +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -387,7 +387,7 @@ impl BreakpointStore { pub fn abs_path_from_buffer(buffer: &Entity, cx: &App) -> Option> { worktree::File::from_dyn(buffer.read(cx).file()) - .and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok()) + .map(|file| file.worktree.read(cx).absolutize(&file.path)) .map(Arc::::from) } @@ -794,7 +794,7 @@ impl BreakpointStore { .update(cx, |this, cx| { let path = ProjectPath { worktree_id: worktree.read(cx).id(), - path: relative_path.into(), + path: relative_path, }; this.open_buffer(path, cx) })? diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index d96e5c220fcc98413a42dc46e0889004300d1e2f..923de3190cdf8d7f6bf4536a8ca8c67ebb924513 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -50,7 +50,7 @@ use std::{ sync::{Arc, Once}, }; use task::{DebugScenario, SpawnInTerminal, TaskContext, TaskTemplate}; -use util::ResultExt as _; +use util::{ResultExt as _, rel_path::RelPath}; use worktree::Worktree; #[derive(Debug)] @@ -206,7 +206,7 @@ impl DapStore { let settings_location = SettingsLocation { worktree_id: worktree.read(cx).id(), - path: Path::new(""), + path: RelPath::empty(), }; let dap_settings = ProjectSettings::get(Some(settings_location), cx) .dap @@ -943,15 +943,13 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { fn toolchain_store(&self) -> Arc { self.toolchain_store.clone() } - async fn read_text_file(&self, path: PathBuf) -> Result { + + async fn read_text_file(&self, path: &RelPath) -> Result { let entry = self .worktree - .entry_for_path(&path) + .entry_for_path(path) .with_context(|| format!("no worktree entry for path {path:?}"))?; - let abs_path = self - .worktree - .absolutize(&entry.path) - .with_context(|| format!("cannot absolutize path {path:?}"))?; + let abs_path = self.worktree.absolutize(&entry.path); self.fs.load(&abs_path).await } diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index aefdd8acb18841eecc9ec7aa5613126b44d7a429..168549d4eb7ae11b28bcf944f9dfe2bb26946748 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -20,7 +20,7 @@ use futures::{ stream::FuturesOrdered, }; use git::{ - BuildPermalinkParams, GitHostingProviderRegistry, Oid, WORK_DIRECTORY_REPO_PATH, + BuildPermalinkParams, GitHostingProviderRegistry, Oid, blame::Blame, parse_git_remote_url, repository::{ @@ -45,7 +45,7 @@ use parking_lot::Mutex; use postage::stream::Stream as _; use rpc::{ AnyProtoClient, TypedEnvelope, - proto::{self, FromProto, ToProto, git_reset, split_repository_update}, + proto::{self, git_reset, split_repository_update}, }; use serde::Deserialize; use std::{ @@ -63,7 +63,12 @@ use std::{ }; use sum_tree::{Edit, SumTree, TreeSet}; use text::{Bias, BufferId}; -use util::{ResultExt, debug_panic, paths::SanitizedPath, post_inc}; +use util::{ + ResultExt, debug_panic, + paths::{PathStyle, SanitizedPath}, + post_inc, + rel_path::RelPath, +}; use worktree::{ File, PathChange, PathKey, PathProgress, PathSummary, PathTarget, ProjectEntryId, UpdatedGitRepositoriesSet, UpdatedGitRepository, Worktree, @@ -189,7 +194,7 @@ impl StatusEntry { }; proto::StatusEntry { - repo_path: self.repo_path.as_ref().to_proto(), + repo_path: self.repo_path.to_proto(), simple_status, status: Some(status_to_proto(self.status)), } @@ -200,7 +205,7 @@ impl TryFrom for StatusEntry { type Error = anyhow::Error; fn try_from(value: proto::StatusEntry) -> Result { - let repo_path = RepoPath(Arc::::from_proto(value.repo_path)); + let repo_path = RepoPath::from_proto(&value.repo_path).context("invalid repo path")?; let status = status_from_proto(value.simple_status, value.status)?; Ok(Self { repo_path, status }) } @@ -240,6 +245,7 @@ pub struct RepositorySnapshot { pub id: RepositoryId, pub statuses_by_path: SumTree, pub work_directory_abs_path: Arc, + pub path_style: PathStyle, pub branch: Option, pub head_commit: Option, pub scan_id: u64, @@ -947,9 +953,7 @@ impl GitStore { { return Task::ready(Err(anyhow!("no permalink available"))); } - let Some(file_path) = file.worktree.read(cx).absolutize(&file.path).ok() else { - return Task::ready(Err(anyhow!("no permalink available"))); - }; + let file_path = file.worktree.read(cx).absolutize(&file.path); return cx.spawn(async move |cx| { let provider_registry = cx.update(GitHostingProviderRegistry::default_global)?; get_permalink_in_rust_registry_src(provider_registry, file_path, selection) @@ -985,9 +989,7 @@ impl GitStore { parse_git_remote_url(provider_registry, &origin_url) .context("parsing Git remote URL")?; - let path = repo_path.to_str().with_context(|| { - format!("converting repo path {repo_path:?} to string") - })?; + let path = repo_path.as_str(); Ok(provider.build_permalink( remote, @@ -1313,7 +1315,7 @@ impl GitStore { }); if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) { let recv = repo.update(cx, |repo, cx| { - log::debug!("hunks changed for {}", path.display()); + log::debug!("hunks changed for {}", path.as_str()); repo.spawn_set_index_text_job( path, new_index_text.as_ref().map(|rope| rope.to_string()), @@ -1475,6 +1477,7 @@ impl GitStore { mut cx: AsyncApp, ) -> Result<()> { this.update(&mut cx, |this, cx| { + let path_style = this.worktree_store.read(cx).path_style(); let mut update = envelope.payload; let id = RepositoryId::from_proto(update.id); @@ -1488,6 +1491,7 @@ impl GitStore { Repository::remote( id, Path::new(&update.abs_path).into(), + path_style, ProjectId(update.project_id), client, git_store, @@ -1681,9 +1685,8 @@ impl GitStore { .payload .paths .into_iter() - .map(PathBuf::from) - .map(RepoPath::new) - .collect(); + .map(|path| RepoPath::new(&path)) + .collect::>>()?; repository_handle .update(&mut cx, |repository_handle, cx| { @@ -1705,9 +1708,8 @@ impl GitStore { .payload .paths .into_iter() - .map(PathBuf::from) - .map(RepoPath::new) - .collect(); + .map(|path| RepoPath::new(&path)) + .collect::>>()?; repository_handle .update(&mut cx, |repository_handle, cx| { @@ -1730,9 +1732,8 @@ impl GitStore { .payload .paths .into_iter() - .map(PathBuf::from) - .map(RepoPath::new) - .collect(); + .map(|path| RepoPath::new(&path)) + .collect::>>()?; repository_handle .update(&mut cx, |repository_handle, cx| { @@ -1804,7 +1805,7 @@ impl GitStore { ) -> Result { let repository_id = RepositoryId::from_proto(envelope.payload.repository_id); let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?; - let repo_path = RepoPath::from_str(&envelope.payload.path); + let repo_path = RepoPath::from_proto(&envelope.payload.path)?; repository_handle .update(&mut cx, |repository_handle, cx| { @@ -2005,7 +2006,7 @@ impl GitStore { .files .into_iter() .map(|file| proto::CommitFile { - path: file.path.to_string(), + path: file.path.to_proto(), old_text: file.old_text, new_text: file.new_text, }) @@ -2045,8 +2046,8 @@ impl GitStore { .payload .paths .iter() - .map(|s| RepoPath::from_str(s)) - .collect(); + .map(|s| RepoPath::from_proto(s)) + .collect::>>()?; repository_handle .update(&mut cx, |repository_handle, cx| { @@ -2332,9 +2333,10 @@ impl GitStore { fn process_updated_entries( &self, worktree: &Entity, - updated_entries: &[(Arc, ProjectEntryId, PathChange)], + updated_entries: &[(Arc, ProjectEntryId, PathChange)], cx: &mut App, ) -> Task, Vec>> { + let path_style = worktree.read(cx).path_style(); let mut repo_paths = self .repositories .values() @@ -2349,7 +2351,7 @@ impl GitStore { let entries = entries .into_iter() - .filter_map(|path| worktree.absolutize(&path).ok()) + .map(|path| worktree.absolutize(&path)) .collect::>(); let executor = cx.background_executor().clone(); @@ -2369,8 +2371,9 @@ impl GitStore { let mut paths = Vec::new(); // 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) + && let Some(repo_path) = RepositorySnapshot::abs_path_to_repo_path_inner( + &repo_path, path, path_style, + ) { paths.push((repo_path, ix)); ix += 1; @@ -2764,7 +2767,7 @@ impl RepositoryId { } impl RepositorySnapshot { - fn empty(id: RepositoryId, work_directory_abs_path: Arc) -> Self { + fn empty(id: RepositoryId, work_directory_abs_path: Arc, path_style: PathStyle) -> Self { Self { id, statuses_by_path: Default::default(), @@ -2776,6 +2779,7 @@ impl RepositorySnapshot { remote_origin_url: None, remote_upstream_url: None, stash_entries: Default::default(), + path_style, } } @@ -2798,7 +2802,7 @@ impl RepositorySnapshot { 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(), + abs_path: self.work_directory_abs_path.to_string_lossy().to_string(), entry_ids: vec![self.id.to_proto()], scan_id: self.scan_id, is_last_update: true, @@ -2836,13 +2840,13 @@ impl RepositorySnapshot { current_new_entry = new_statuses.next(); } Ordering::Greater => { - removed_statuses.push(old_entry.repo_path.as_ref().to_proto()); + removed_statuses.push(old_entry.repo_path.to_proto()); current_old_entry = old_statuses.next(); } } } (None, Some(old_entry)) => { - removed_statuses.push(old_entry.repo_path.as_ref().to_proto()); + removed_statuses.push(old_entry.repo_path.to_proto()); current_old_entry = old_statuses.next(); } (Some(new_entry), None) => { @@ -2862,12 +2866,12 @@ impl RepositorySnapshot { .merge .conflicted_paths .iter() - .map(|path| path.as_ref().to_proto()) + .map(|path| 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(), + abs_path: self.work_directory_abs_path.to_string_lossy().to_string(), entry_ids: vec![], scan_id: self.scan_id, is_last_update: true, @@ -2895,18 +2899,19 @@ impl RepositorySnapshot { } pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option { - Self::abs_path_to_repo_path_inner(&self.work_directory_abs_path, abs_path) + Self::abs_path_to_repo_path_inner(&self.work_directory_abs_path, abs_path, self.path_style) } #[inline] fn abs_path_to_repo_path_inner( work_directory_abs_path: &Path, abs_path: &Path, + path_style: PathStyle, ) -> Option { abs_path .strip_prefix(&work_directory_abs_path) - .map(RepoPath::from) .ok() + .and_then(|path| RepoPath::from_std_path(path, path_style).ok()) } pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool { @@ -3032,7 +3037,8 @@ impl Repository { git_store: WeakEntity, cx: &mut Context, ) -> Self { - let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path.clone()); + let snapshot = + RepositorySnapshot::empty(id, work_directory_abs_path.clone(), PathStyle::local()); Repository { this: cx.weak_entity(), git_store, @@ -3058,12 +3064,13 @@ impl Repository { fn remote( id: RepositoryId, work_directory_abs_path: Arc, + path_style: PathStyle, project_id: ProjectId, client: AnyProtoClient, git_store: WeakEntity, cx: &mut Context, ) -> Self { - let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path); + let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path, path_style); Self { this: cx.weak_entity(), snapshot, @@ -3107,12 +3114,11 @@ impl Repository { let buffer_store = git_store.buffer_store.read(cx); let buffer = buffer_store.get(*buffer_id)?; let file = File::from_dyn(buffer.read(cx).file())?; - let abs_path = - file.worktree.read(cx).absolutize(&file.path).ok()?; + let abs_path = file.worktree.read(cx).absolutize(&file.path); let repo_path = this.abs_path_to_repo_path(&abs_path)?; log::debug!( "start reload diff bases for repo path {}", - repo_path.0.display() + repo_path.as_str() ); diff_state.update(cx, |diff_state, _| { let has_unstaged_diff = diff_state @@ -3335,12 +3341,15 @@ impl Repository { pub fn repo_path_to_project_path(&self, path: &RepoPath, cx: &App) -> Option { let git_store = self.git_store.upgrade()?; let worktree_store = git_store.read(cx).worktree_store.read(cx); - let abs_path = self.snapshot.work_directory_abs_path.join(&path.0); + let abs_path = self + .snapshot + .work_directory_abs_path + .join(path.as_std_path()); let abs_path = SanitizedPath::new(&abs_path); let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?; Some(ProjectPath { worktree_id: worktree.read(cx).id(), - path: relative_path.into(), + path: relative_path, }) } @@ -3464,10 +3473,7 @@ impl Repository { project_id: project_id.0, repository_id: id.to_proto(), commit, - paths: paths - .into_iter() - .map(|p| p.to_string_lossy().to_string()) - .collect(), + paths: paths.into_iter().map(|p| p.to_proto()).collect(), }) .await?; @@ -3557,12 +3563,14 @@ impl Repository { files: response .files .into_iter() - .map(|file| CommitFile { - path: Path::new(&file.path).into(), - old_text: file.old_text, - new_text: file.new_text, + .map(|file| { + Ok(CommitFile { + path: RepoPath::from_proto(&file.path)?, + old_text: file.old_text, + new_text: file.new_text, + }) }) - .collect(), + .collect::>>()?, }) } } @@ -3622,7 +3630,7 @@ impl Repository { repository_id: id.to_proto(), paths: entries .into_iter() - .map(|repo_path| repo_path.as_ref().to_proto()) + .map(|repo_path| repo_path.to_proto()) .collect(), }) .await @@ -3688,7 +3696,7 @@ impl Repository { repository_id: id.to_proto(), paths: entries .into_iter() - .map(|repo_path| repo_path.as_ref().to_proto()) + .map(|repo_path| repo_path.to_proto()) .collect(), }) .await @@ -3752,7 +3760,7 @@ impl Repository { repository_id: id.to_proto(), paths: entries .into_iter() - .map(|repo_path| repo_path.as_ref().to_proto()) + .map(|repo_path| repo_path.to_proto()) .collect(), }) .await @@ -4154,7 +4162,7 @@ impl Repository { Some(GitJobKey::WriteIndex(path.clone())), None, move |git_repo, mut cx| async move { - log::debug!("start updating index text for buffer {}", path.display()); + log::debug!("start updating index text for buffer {}", path.as_str()); match git_repo { RepositoryState::Local { backend, @@ -4170,13 +4178,13 @@ impl Repository { .request(proto::SetIndexText { project_id: project_id.0, repository_id: id.to_proto(), - path: path.as_ref().to_proto(), + path: path.to_proto(), text: content, }) .await?; } } - log::debug!("finish updating index text for buffer {}", path.display()); + log::debug!("finish updating index text for buffer {}", path.as_str()); if let Some(hunk_staging_operation_count) = hunk_staging_operation_count { let project_path = this @@ -4439,7 +4447,7 @@ impl Repository { update .current_merge_conflicts .into_iter() - .map(|path| RepoPath(Path::new(&path).into())), + .filter_map(|path| RepoPath::from_proto(&path).log_err()), ); self.snapshot.branch = update.branch_summary.as_ref().map(proto_to_branch); self.snapshot.head_commit = update @@ -4460,7 +4468,11 @@ impl Repository { let edits = update .removed_statuses .into_iter() - .map(|path| sum_tree::Edit::Remove(PathKey(FromProto::from_proto(path)))) + .filter_map(|path| { + Some(sum_tree::Edit::Remove(PathKey( + RelPath::from_proto(&path).log_err()?, + ))) + }) .chain( update .updated_statuses @@ -5060,9 +5072,7 @@ async fn compute_snapshot( let mut events = Vec::new(); let branches = backend.branches().await?; let branch = branches.into_iter().find(|branch| branch.is_head); - let statuses = backend - .status(std::slice::from_ref(&WORK_DIRECTORY_REPO_PATH)) - .await?; + let statuses = backend.status(&[RelPath::empty().into()]).await?; let stash_entries = backend.stash_entries().await?; let statuses_by_path = SumTree::from_iter( statuses @@ -5108,6 +5118,7 @@ async fn compute_snapshot( id, statuses_by_path, work_directory_abs_path, + path_style: prev_snapshot.path_style, scan_id: prev_snapshot.scan_id + 1, branch, head_commit, diff --git a/crates/project/src/git_store/conflict_set.rs b/crates/project/src/git_store/conflict_set.rs index 067af17820e58264006d0227cfb0f3c13069fcf9..13a082b35024b11870fb14fb3419c76841566193 100644 --- a/crates/project/src/git_store/conflict_set.rs +++ b/crates/project/src/git_store/conflict_set.rs @@ -255,20 +255,23 @@ impl EventEmitter for ConflictSet {} #[cfg(test)] mod tests { - use std::{path::Path, sync::mpsc}; + use std::sync::mpsc; use crate::Project; use super::*; use fs::FakeFs; - use git::status::{UnmergedStatus, UnmergedStatusCode}; + use git::{ + repository::repo_path, + status::{UnmergedStatus, UnmergedStatusCode}, + }; use gpui::{BackgroundExecutor, TestAppContext}; use language::language_settings::AllLanguageSettings; use serde_json::json; use settings::Settings as _; use text::{Buffer, BufferId, Point, ToOffset as _}; use unindent::Unindent as _; - use util::path; + use util::{path, rel_path::rel_path}; use worktree::WorktreeSettings; #[test] @@ -543,7 +546,7 @@ mod tests { fs.with_git_state(path!("/project/.git").as_ref(), true, |state| { state.unmerged_paths.insert( - "a.txt".into(), + repo_path("a.txt"), UnmergedStatus { first_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Updated, @@ -621,7 +624,7 @@ mod tests { cx.run_until_parked(); fs.with_git_state(path!("/project/.git").as_ref(), true, |state| { state.unmerged_paths.insert( - "a.txt".into(), + rel_path("a.txt").into(), UnmergedStatus { first_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Updated, @@ -647,7 +650,7 @@ mod tests { // Simulate the conflict being removed by e.g. staging the file. fs.with_git_state(path!("/project/.git").as_ref(), true, |state| { - state.unmerged_paths.remove(Path::new("a.txt")) + state.unmerged_paths.remove(&repo_path("a.txt")) }) .unwrap(); @@ -660,7 +663,7 @@ mod tests { // Simulate the conflict being re-added. fs.with_git_state(path!("/project/.git").as_ref(), true, |state| { state.unmerged_paths.insert( - "a.txt".into(), + repo_path("a.txt"), UnmergedStatus { first_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Updated, diff --git a/crates/project/src/git_store/git_traversal.rs b/crates/project/src/git_store/git_traversal.rs index 93f5d3e24c883073eca657c1b724da64648f88ae..3da56b0246a84927202179a8b440c5b2e8116d29 100644 --- a/crates/project/src/git_store/git_traversal.rs +++ b/crates/project/src/git_store/git_traversal.rs @@ -3,6 +3,7 @@ use git::{repository::RepoPath, status::GitSummary}; use std::{collections::BTreeMap, ops::Deref, path::Path}; use sum_tree::Cursor; use text::Bias; +use util::rel_path::RelPath; use worktree::{Entry, PathProgress, PathTarget, Traversal}; use super::{RepositoryId, RepositorySnapshot, StatusEntry}; @@ -70,10 +71,7 @@ impl<'a> GitTraversal<'a> { return; }; - let Ok(abs_path) = self.traversal.snapshot().absolutize(&entry.path) else { - self.repo_location = None; - return; - }; + let abs_path = self.traversal.snapshot().absolutize(&entry.path); let Some((repo, repo_path)) = self.repo_root_for_path(&abs_path) else { self.repo_location = None; @@ -97,13 +95,13 @@ impl<'a> GitTraversal<'a> { if entry.is_dir() { let mut statuses = statuses.clone(); - statuses.seek_forward(&PathTarget::Path(repo_path.as_ref()), Bias::Left); - let summary = statuses.summary(&PathTarget::Successor(repo_path.as_ref()), Bias::Left); + statuses.seek_forward(&PathTarget::Path(&repo_path), Bias::Left); + let summary = statuses.summary(&PathTarget::Successor(&repo_path), Bias::Left); self.current_entry_summary = Some(summary); } else if entry.is_file() { // For a file entry, park the cursor on the corresponding status - if statuses.seek_forward(&PathTarget::Path(repo_path.as_ref()), Bias::Left) { + if statuses.seek_forward(&PathTarget::Path(&repo_path), Bias::Left) { // TODO: Investigate statuses.item() being None here. self.current_entry_summary = statuses.item().map(|item| item.status.into()); } else { @@ -159,7 +157,7 @@ impl<'a> Iterator for GitTraversal<'a> { } pub struct ChildEntriesGitIter<'a> { - parent_path: &'a Path, + parent_path: &'a RelPath, traversal: GitTraversal<'a>, } @@ -167,7 +165,7 @@ impl<'a> ChildEntriesGitIter<'a> { pub fn new( repo_snapshots: &'a HashMap, worktree_snapshot: &'a worktree::Snapshot, - parent_path: &'a Path, + parent_path: &'a RelPath, ) -> Self { let mut traversal = GitTraversal::new( repo_snapshots, @@ -265,7 +263,7 @@ mod tests { use gpui::TestAppContext; use serde_json::json; use settings::SettingsStore; - use util::path; + use util::{path, rel_path::rel_path}; const CONFLICT: FileStatus = FileStatus::Unmerged(UnmergedStatus { first_head: UnmergedStatusCode::Updated, @@ -312,17 +310,14 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/x/.git")), &[ - (Path::new("x2.txt"), StatusCode::Modified.index()), - (Path::new("z.txt"), StatusCode::Added.index()), + ("x2.txt", StatusCode::Modified.index()), + ("z.txt", StatusCode::Added.index()), ], ); - fs.set_status_for_repo( - Path::new(path!("/root/x/y/.git")), - &[(Path::new("y1.txt"), CONFLICT)], - ); + fs.set_status_for_repo(Path::new(path!("/root/x/y/.git")), &[("y1.txt", CONFLICT)]); fs.set_status_for_repo( Path::new(path!("/root/z/.git")), - &[(Path::new("z2.txt"), StatusCode::Added.index())], + &[("z2.txt", StatusCode::Added.index())], ); let project = Project::test(fs, [path!("/root").as_ref()], cx).await; @@ -337,7 +332,7 @@ mod tests { let traversal = GitTraversal::new( &repo_snapshots, - worktree_snapshot.traverse_from_path(true, false, true, Path::new("x")), + worktree_snapshot.traverse_from_path(true, false, true, RelPath::new("x").unwrap()), ); let entries = traversal .map(|entry| (entry.path.clone(), entry.git_summary)) @@ -345,13 +340,13 @@ mod tests { pretty_assertions::assert_eq!( entries, [ - (Path::new("x/x1.txt").into(), GitSummary::UNCHANGED), - (Path::new("x/x2.txt").into(), MODIFIED), - (Path::new("x/y/y1.txt").into(), GitSummary::CONFLICT), - (Path::new("x/y/y2.txt").into(), GitSummary::UNCHANGED), - (Path::new("x/z.txt").into(), ADDED), - (Path::new("z/z1.txt").into(), GitSummary::UNCHANGED), - (Path::new("z/z2.txt").into(), ADDED), + (rel_path("x/x1.txt").into(), GitSummary::UNCHANGED), + (rel_path("x/x2.txt").into(), MODIFIED), + (rel_path("x/y/y1.txt").into(), GitSummary::CONFLICT), + (rel_path("x/y/y2.txt").into(), GitSummary::UNCHANGED), + (rel_path("x/z.txt").into(), ADDED), + (rel_path("z/z1.txt").into(), GitSummary::UNCHANGED), + (rel_path("z/z2.txt").into(), ADDED), ] ) } @@ -386,18 +381,15 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/x/.git")), &[ - (Path::new("x2.txt"), StatusCode::Modified.index()), - (Path::new("z.txt"), StatusCode::Added.index()), + ("x2.txt", StatusCode::Modified.index()), + ("z.txt", StatusCode::Added.index()), ], ); - fs.set_status_for_repo( - Path::new(path!("/root/x/y/.git")), - &[(Path::new("y1.txt"), CONFLICT)], - ); + fs.set_status_for_repo(Path::new(path!("/root/x/y/.git")), &[("y1.txt", CONFLICT)]); fs.set_status_for_repo( Path::new(path!("/root/z/.git")), - &[(Path::new("z2.txt"), StatusCode::Added.index())], + &[("z2.txt", StatusCode::Added.index())], ); let project = Project::test(fs, [path!("/root").as_ref()], cx).await; @@ -415,18 +407,18 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new("x/y"), GitSummary::CONFLICT), - (Path::new("x/y/y1.txt"), GitSummary::CONFLICT), - (Path::new("x/y/y2.txt"), GitSummary::UNCHANGED), + ("x/y", GitSummary::CONFLICT), + ("x/y/y1.txt", GitSummary::CONFLICT), + ("x/y/y2.txt", GitSummary::UNCHANGED), ], ); check_git_statuses( &repo_snapshots, &worktree_snapshot, &[ - (Path::new("z"), ADDED), - (Path::new("z/z1.txt"), GitSummary::UNCHANGED), - (Path::new("z/z2.txt"), ADDED), + ("z", ADDED), + ("z/z1.txt", GitSummary::UNCHANGED), + ("z/z2.txt", ADDED), ], ); @@ -435,9 +427,9 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new("x"), MODIFIED + ADDED), - (Path::new("x/y"), GitSummary::CONFLICT), - (Path::new("x/y/y1.txt"), GitSummary::CONFLICT), + ("x", MODIFIED + ADDED), + ("x/y", GitSummary::CONFLICT), + ("x/y/y1.txt", GitSummary::CONFLICT), ], ); @@ -446,13 +438,13 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new("x"), MODIFIED + ADDED), - (Path::new("x/x1.txt"), GitSummary::UNCHANGED), - (Path::new("x/x2.txt"), MODIFIED), - (Path::new("x/y"), GitSummary::CONFLICT), - (Path::new("x/y/y1.txt"), GitSummary::CONFLICT), - (Path::new("x/y/y2.txt"), GitSummary::UNCHANGED), - (Path::new("x/z.txt"), ADDED), + ("x", MODIFIED + ADDED), + ("x/x1.txt", GitSummary::UNCHANGED), + ("x/x2.txt", MODIFIED), + ("x/y", GitSummary::CONFLICT), + ("x/y/y1.txt", GitSummary::CONFLICT), + ("x/y/y2.txt", GitSummary::UNCHANGED), + ("x/z.txt", ADDED), ], ); @@ -461,9 +453,9 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new(""), GitSummary::UNCHANGED), - (Path::new("x"), MODIFIED + ADDED), - (Path::new("x/x1.txt"), GitSummary::UNCHANGED), + ("", GitSummary::UNCHANGED), + ("x", MODIFIED + ADDED), + ("x/x1.txt", GitSummary::UNCHANGED), ], ); @@ -472,17 +464,17 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new(""), GitSummary::UNCHANGED), - (Path::new("x"), MODIFIED + ADDED), - (Path::new("x/x1.txt"), GitSummary::UNCHANGED), - (Path::new("x/x2.txt"), MODIFIED), - (Path::new("x/y"), GitSummary::CONFLICT), - (Path::new("x/y/y1.txt"), GitSummary::CONFLICT), - (Path::new("x/y/y2.txt"), GitSummary::UNCHANGED), - (Path::new("x/z.txt"), ADDED), - (Path::new("z"), ADDED), - (Path::new("z/z1.txt"), GitSummary::UNCHANGED), - (Path::new("z/z2.txt"), ADDED), + ("", GitSummary::UNCHANGED), + ("x", MODIFIED + ADDED), + ("x/x1.txt", GitSummary::UNCHANGED), + ("x/x2.txt", MODIFIED), + ("x/y", GitSummary::CONFLICT), + ("x/y/y1.txt", GitSummary::CONFLICT), + ("x/y/y2.txt", GitSummary::UNCHANGED), + ("x/z.txt", ADDED), + ("z", ADDED), + ("z/z1.txt", GitSummary::UNCHANGED), + ("z/z2.txt", ADDED), ], ); } @@ -520,9 +512,9 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/.git")), &[ - (Path::new("a/b/c1.txt"), StatusCode::Added.index()), - (Path::new("a/d/e2.txt"), StatusCode::Modified.index()), - (Path::new("g/h2.txt"), CONFLICT), + ("a/b/c1.txt", StatusCode::Added.index()), + ("a/d/e2.txt", StatusCode::Modified.index()), + ("g/h2.txt", CONFLICT), ], ); @@ -540,9 +532,9 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new(""), GitSummary::CONFLICT + MODIFIED + ADDED), - (Path::new("g"), GitSummary::CONFLICT), - (Path::new("g/h2.txt"), GitSummary::CONFLICT), + ("", GitSummary::CONFLICT + MODIFIED + ADDED), + ("g", GitSummary::CONFLICT), + ("g/h2.txt", GitSummary::CONFLICT), ], ); @@ -550,17 +542,17 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new(""), GitSummary::CONFLICT + ADDED + MODIFIED), - (Path::new("a"), ADDED + MODIFIED), - (Path::new("a/b"), ADDED), - (Path::new("a/b/c1.txt"), ADDED), - (Path::new("a/b/c2.txt"), GitSummary::UNCHANGED), - (Path::new("a/d"), MODIFIED), - (Path::new("a/d/e2.txt"), MODIFIED), - (Path::new("f"), GitSummary::UNCHANGED), - (Path::new("f/no-status.txt"), GitSummary::UNCHANGED), - (Path::new("g"), GitSummary::CONFLICT), - (Path::new("g/h2.txt"), GitSummary::CONFLICT), + ("", GitSummary::CONFLICT + ADDED + MODIFIED), + ("a", ADDED + MODIFIED), + ("a/b", ADDED), + ("a/b/c1.txt", ADDED), + ("a/b/c2.txt", GitSummary::UNCHANGED), + ("a/d", MODIFIED), + ("a/d/e2.txt", MODIFIED), + ("f", GitSummary::UNCHANGED), + ("f/no-status.txt", GitSummary::UNCHANGED), + ("g", GitSummary::CONFLICT), + ("g/h2.txt", GitSummary::CONFLICT), ], ); @@ -568,15 +560,15 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new("a/b"), ADDED), - (Path::new("a/b/c1.txt"), ADDED), - (Path::new("a/b/c2.txt"), GitSummary::UNCHANGED), - (Path::new("a/d"), MODIFIED), - (Path::new("a/d/e1.txt"), GitSummary::UNCHANGED), - (Path::new("a/d/e2.txt"), MODIFIED), - (Path::new("f"), GitSummary::UNCHANGED), - (Path::new("f/no-status.txt"), GitSummary::UNCHANGED), - (Path::new("g"), GitSummary::CONFLICT), + ("a/b", ADDED), + ("a/b/c1.txt", ADDED), + ("a/b/c2.txt", GitSummary::UNCHANGED), + ("a/d", MODIFIED), + ("a/d/e1.txt", GitSummary::UNCHANGED), + ("a/d/e2.txt", MODIFIED), + ("f", GitSummary::UNCHANGED), + ("f/no-status.txt", GitSummary::UNCHANGED), + ("g", GitSummary::CONFLICT), ], ); @@ -584,11 +576,11 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new("a/b/c1.txt"), ADDED), - (Path::new("a/b/c2.txt"), GitSummary::UNCHANGED), - (Path::new("a/d/e1.txt"), GitSummary::UNCHANGED), - (Path::new("a/d/e2.txt"), MODIFIED), - (Path::new("f/no-status.txt"), GitSummary::UNCHANGED), + ("a/b/c1.txt", ADDED), + ("a/b/c2.txt", GitSummary::UNCHANGED), + ("a/d/e1.txt", GitSummary::UNCHANGED), + ("a/d/e2.txt", MODIFIED), + ("f/no-status.txt", GitSummary::UNCHANGED), ], ); } @@ -621,18 +613,18 @@ mod tests { fs.set_status_for_repo( Path::new(path!("/root/x/.git")), - &[(Path::new("x1.txt"), StatusCode::Added.index())], + &[("x1.txt", StatusCode::Added.index())], ); fs.set_status_for_repo( Path::new(path!("/root/y/.git")), &[ - (Path::new("y1.txt"), CONFLICT), - (Path::new("y2.txt"), StatusCode::Modified.index()), + ("y1.txt", CONFLICT), + ("y2.txt", StatusCode::Modified.index()), ], ); fs.set_status_for_repo( Path::new(path!("/root/z/.git")), - &[(Path::new("z2.txt"), StatusCode::Modified.index())], + &[("z2.txt", StatusCode::Modified.index())], ); let project = Project::test(fs, [path!("/root").as_ref()], cx).await; @@ -648,47 +640,44 @@ mod tests { check_git_statuses( &repo_snapshots, &worktree_snapshot, - &[(Path::new("x"), ADDED), (Path::new("x/x1.txt"), ADDED)], + &[("x", ADDED), ("x/x1.txt", ADDED)], ); check_git_statuses( &repo_snapshots, &worktree_snapshot, &[ - (Path::new("y"), GitSummary::CONFLICT + MODIFIED), - (Path::new("y/y1.txt"), GitSummary::CONFLICT), - (Path::new("y/y2.txt"), MODIFIED), + ("y", GitSummary::CONFLICT + MODIFIED), + ("y/y1.txt", GitSummary::CONFLICT), + ("y/y2.txt", MODIFIED), ], ); check_git_statuses( &repo_snapshots, &worktree_snapshot, - &[ - (Path::new("z"), MODIFIED), - (Path::new("z/z2.txt"), MODIFIED), - ], + &[("z", MODIFIED), ("z/z2.txt", MODIFIED)], ); check_git_statuses( &repo_snapshots, &worktree_snapshot, - &[(Path::new("x"), ADDED), (Path::new("x/x1.txt"), ADDED)], + &[("x", ADDED), ("x/x1.txt", ADDED)], ); check_git_statuses( &repo_snapshots, &worktree_snapshot, &[ - (Path::new("x"), ADDED), - (Path::new("x/x1.txt"), ADDED), - (Path::new("x/x2.txt"), GitSummary::UNCHANGED), - (Path::new("y"), GitSummary::CONFLICT + MODIFIED), - (Path::new("y/y1.txt"), GitSummary::CONFLICT), - (Path::new("y/y2.txt"), MODIFIED), - (Path::new("z"), MODIFIED), - (Path::new("z/z1.txt"), GitSummary::UNCHANGED), - (Path::new("z/z2.txt"), MODIFIED), + ("x", ADDED), + ("x/x1.txt", ADDED), + ("x/x2.txt", GitSummary::UNCHANGED), + ("y", GitSummary::CONFLICT + MODIFIED), + ("y/y1.txt", GitSummary::CONFLICT), + ("y/y2.txt", MODIFIED), + ("z", MODIFIED), + ("z/z1.txt", GitSummary::UNCHANGED), + ("z/z2.txt", MODIFIED), ], ); } @@ -722,7 +711,7 @@ mod tests { .await; fs.set_head_and_index_for_repo( path!("/root/.git").as_ref(), - &[("a.txt".into(), "".into()), ("b/c.txt".into(), "".into())], + &[("a.txt", "".into()), ("b/c.txt", "".into())], ); cx.run_until_parked(); @@ -757,10 +746,7 @@ mod tests { // detected. fs.set_head_for_repo( path!("/root/.git").as_ref(), - &[ - ("a.txt".into(), "".into()), - ("b/c.txt".into(), "something-else".into()), - ], + &[("a.txt", "".into()), ("b/c.txt", "something-else".into())], "deadbeef", ); cx.executor().run_until_parked(); @@ -777,9 +763,9 @@ mod tests { &repo_snapshots, &worktree_snapshot, &[ - (Path::new(""), MODIFIED), - (Path::new("a.txt"), GitSummary::UNCHANGED), - (Path::new("b/c.txt"), MODIFIED), + ("", MODIFIED), + ("a.txt", GitSummary::UNCHANGED), + ("b/c.txt", MODIFIED), ], ); } @@ -788,17 +774,17 @@ mod tests { fn check_git_statuses( repo_snapshots: &HashMap, worktree_snapshot: &worktree::Snapshot, - expected_statuses: &[(&Path, GitSummary)], + expected_statuses: &[(&str, GitSummary)], ) { let mut traversal = GitTraversal::new( repo_snapshots, - worktree_snapshot.traverse_from_path(true, true, false, "".as_ref()), + worktree_snapshot.traverse_from_path(true, true, false, RelPath::empty()), ); let found_statuses = expected_statuses .iter() .map(|&(path, _)| { let git_entry = traversal - .find(|git_entry| &*git_entry.path == path) + .find(|git_entry| git_entry.path.as_ref() == rel_path(path)) .unwrap_or_else(|| panic!("Traversal has no entry for {path:?}")); (path, git_entry.git_summary) }) diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index e499d4e026f724f12e023738f12afb2735f9ce2d..71394ead2eb27067706023d4870c78c557c3747b 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -13,10 +13,9 @@ use image::{ExtendedColorType, GenericImageView, ImageReader}; use language::{DiskState, File}; use rpc::{AnyProtoClient, ErrorExt as _}; use std::num::NonZeroU64; -use std::path::Path; +use std::path::PathBuf; use std::sync::Arc; -use std::{ffi::OsStr, path::PathBuf}; -use util::ResultExt; +use util::{ResultExt, rel_path::RelPath}; use worktree::{LoadedBinaryFile, PathChange, Worktree}; #[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)] @@ -207,8 +206,7 @@ pub fn is_image_file(project: &Entity, path: &ProjectPath, cx: &App) -> .abs_path(); path.path .extension() - .or_else(|| worktree_abs_path.extension()) - .and_then(OsStr::to_str) + .or_else(|| worktree_abs_path.extension()?.to_str()) .map(str::to_lowercase) }); @@ -255,7 +253,7 @@ impl ProjectItem for ImageItem { trait ImageStoreImpl { fn open_image( &self, - path: Arc, + path: Arc, worktree: Entity, cx: &mut Context, ) -> Task>>; @@ -458,7 +456,7 @@ impl ImageStore { impl ImageStoreImpl for Entity { fn open_image( &self, - path: Arc, + path: Arc, worktree: Entity, cx: &mut Context, ) -> Task>> { @@ -539,7 +537,7 @@ impl LocalImageStore { fn local_worktree_entries_changed( &mut self, worktree_handle: &Entity, - changes: &[(Arc, ProjectEntryId, PathChange)], + changes: &[(Arc, ProjectEntryId, PathChange)], cx: &mut Context, ) { let snapshot = worktree_handle.read(cx).snapshot(); @@ -551,7 +549,7 @@ impl LocalImageStore { fn local_worktree_entry_changed( &mut self, entry_id: ProjectEntryId, - path: &Arc, + path: &Arc, worktree: &Entity, snapshot: &worktree::Snapshot, cx: &mut Context, @@ -698,7 +696,7 @@ fn create_gpui_image(content: Vec) -> anyhow::Result> { impl ImageStoreImpl for Entity { fn open_image( &self, - _path: Arc, + _path: Arc, _worktree: Entity, _cx: &mut Context, ) -> Task>> { @@ -729,7 +727,7 @@ mod tests { use gpui::TestAppContext; use serde_json::json; use settings::SettingsStore; - use std::path::PathBuf; + use util::rel_path::rel_path; pub fn init_test(cx: &mut TestAppContext) { zlog::init_test(); @@ -768,7 +766,7 @@ mod tests { let project_path = ProjectPath { worktree_id, - path: PathBuf::from("image_1.png").into(), + path: rel_path("image_1.png").into(), }; let (task1, task2) = project.update(cx, |project, cx| { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 14a3f1921c04a6572fb5f4e4535ba4895c556d94..77ec10d01ea5400194d5a9288f5d1b0a4e874648 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -33,7 +33,6 @@ use crate::{ }, prettier_store::{self, PrettierStore, PrettierStoreEvent}, project_settings::{LspSettings, ProjectSettings}, - relativize_path, resolve_path, toolchain_store::{LocalToolchainStore, ToolchainStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, yarn::YarnPathStore, @@ -88,7 +87,7 @@ use postage::{mpsc, sink::Sink, stream::Stream, watch}; use rand::prelude::*; use rpc::{ AnyProtoClient, - proto::{FromProto, LspRequestId, LspRequestMessage as _, ToProto}, + proto::{LspRequestId, LspRequestMessage as _}, }; use serde::Serialize; use settings::{Settings, SettingsLocation, SettingsStore}; @@ -116,8 +115,9 @@ use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, ToPoint as _}; use util::{ ConnectionResult, ResultExt as _, debug_panic, defer, maybe, merge_json_value_into, - paths::{PathExt, SanitizedPath}, + paths::{PathStyle, SanitizedPath}, post_inc, + rel_path::RelPath, }; pub use fs::*; @@ -158,7 +158,7 @@ impl FormatTrigger { #[derive(Clone)] struct UnifiedLanguageServer { id: LanguageServerId, - project_roots: HashSet>, + project_roots: HashSet>, } #[derive(Clone, Hash, PartialEq, Eq)] @@ -209,7 +209,7 @@ pub struct LocalLspStore { diagnostics: HashMap< WorktreeId, HashMap< - Arc, + Arc, Vec<( LanguageServerId, Vec>>, @@ -1086,7 +1086,7 @@ impl LocalLspStore { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let worktree_id = file.worktree_id(cx); - let path: Arc = file + let path: Arc = file .path() .parent() .map(Arc::from) @@ -1842,17 +1842,19 @@ impl LocalLspStore { } if !project_transaction_command.0.is_empty() { - let extra_buffers = project_transaction_command - .0 - .keys() - .filter_map(|buffer_handle| { - buffer_handle - .read_with(cx, |b, cx| b.project_path(cx)) - .ok() - .flatten() - }) - .map(|p| p.path.to_sanitized_string()) - .join(", "); + let mut extra_buffers = String::new(); + for buffer in project_transaction_command.0.keys() { + buffer + .read_with(cx, |b, cx| { + if let Some(path) = b.project_path(cx) { + if !extra_buffers.is_empty() { + extra_buffers.push_str(", "); + } + extra_buffers.push_str(path.path.as_str()); + } + }) + .ok(); + } zlog::warn!( logger => "Unexpected edits to buffers other than the buffer actively being formatted due to command {}. Impacted buffers: [{}].", @@ -2347,7 +2349,7 @@ impl LocalLspStore { let Some(language) = buffer.language().cloned() else { return; }; - let path: Arc = file + let path: Arc = file .path() .parent() .map(Arc::from) @@ -2403,8 +2405,7 @@ impl LocalLspStore { let path = &disposition.path; { - let uri = - Uri::from_file_path(worktree.read(cx).abs_path().join(&path.path)); + let uri = Uri::from_file_path(worktree.read(cx).absolutize(&path.path)); let server_id = self.get_or_insert_language_server( &worktree, @@ -3172,7 +3173,7 @@ impl LocalLspStore { if let Some((tree, glob)) = worktree.as_local_mut().zip(Glob::new(&pattern).log_err()) { - tree.add_path_prefix_to_scan(literal_prefix.into()); + tree.add_path_prefix_to_scan(literal_prefix); worktree_globs .entry(tree.id()) .or_insert_with(GlobSetBuilder::new) @@ -3268,10 +3269,11 @@ impl LocalLspStore { worktrees: &[Entity], watcher: &FileSystemWatcher, cx: &App, - ) -> Option<(Entity, PathBuf, String)> { + ) -> Option<(Entity, Arc, String)> { worktrees.iter().find_map(|worktree| { let tree = worktree.read(cx); let worktree_root_path = tree.abs_path(); + let path_style = tree.path_style(); match &watcher.glob_pattern { lsp::GlobPattern::String(s) => { let watcher_path = SanitizedPath::new(s); @@ -3282,7 +3284,7 @@ impl LocalLspStore { let literal_prefix = glob_literal_prefix(relative); Some(( worktree.clone(), - literal_prefix, + RelPath::from_std_path(&literal_prefix, path_style).ok()?, relative.to_string_lossy().to_string(), )) } @@ -3296,7 +3298,11 @@ impl LocalLspStore { let relative = base_uri.strip_prefix(&worktree_root_path).ok()?; let mut literal_prefix = relative.to_owned(); literal_prefix.push(glob_literal_prefix(Path::new(&rp.pattern))); - Some((worktree.clone(), literal_prefix, rp.pattern.clone())) + Some(( + worktree.clone(), + RelPath::from_std_path(&literal_prefix, path_style).ok()?, + rp.pattern.clone(), + )) } } }) @@ -3483,7 +3489,7 @@ pub struct LspStore { _maintain_workspace_config: (Task>, watch::Sender<()>), _maintain_buffer_languages: Task<()>, diagnostic_summaries: - HashMap, HashMap>>, + HashMap, HashMap>>, pub lsp_server_capabilities: HashMap, lsp_document_colors: HashMap, lsp_code_lens: HashMap, @@ -3569,11 +3575,28 @@ struct CoreSymbol { pub language_server_name: LanguageServerName, pub source_worktree_id: WorktreeId, pub source_language_server_id: LanguageServerId, - pub path: ProjectPath, + pub path: SymbolLocation, pub name: String, pub kind: lsp::SymbolKind, pub range: Range>, - pub signature: [u8; 32], +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SymbolLocation { + InProject(ProjectPath), + OutsideProject { + abs_path: Arc, + signature: [u8; 32], + }, +} + +impl SymbolLocation { + fn file_name(&self) -> Option<&str> { + match self { + Self::InProject(path) => path.path.file_name(), + Self::OutsideProject { abs_path, .. } => abs_path.file_name()?.to_str(), + } + } } impl LspStore { @@ -4353,7 +4376,7 @@ impl LspStore { let mut summaries = diangostic_summaries.iter().flat_map(|(path, summaries)| { summaries .iter() - .map(|(server_id, summary)| summary.to_proto(*server_id, path)) + .map(|(server_id, summary)| summary.to_proto(*server_id, path.as_ref())) }); if let Some(summary) = summaries.next() { client @@ -4655,7 +4678,6 @@ impl LspStore { .unwrap_or_else(|| file.path().clone()); let worktree_path = ProjectPath { worktree_id, path }; let abs_path = file.abs_path(cx); - let worktree_root = worktree.read(cx).abs_path(); let nodes = rebase .walk( worktree_path, @@ -4668,7 +4690,7 @@ impl LspStore { for node in nodes { let server_id = node.server_id_or_init(|disposition| { let path = &disposition.path; - let uri = Uri::from_file_path(worktree_root.join(&path.path)); + let uri = Uri::from_file_path(worktree.read(cx).absolutize(&path.path)); let key = LanguageServerSeed { worktree_id, name: disposition.server_name.clone(), @@ -6965,7 +6987,6 @@ impl LspStore { server_id: LanguageServerId, lsp_adapter: Arc, worktree: WeakEntity, - worktree_abs_path: Arc, lsp_symbols: Vec<(String, SymbolKind, lsp::Location)>, } @@ -7004,7 +7025,6 @@ impl LspStore { if !supports_workspace_symbol_request { continue; } - let worktree_abs_path = worktree.abs_path().clone(); let worktree_handle = worktree_handle.clone(); let server_id = server.server_id(); requests.push( @@ -7044,7 +7064,6 @@ impl LspStore { server_id, lsp_adapter, worktree: worktree_handle.downgrade(), - worktree_abs_path, lsp_symbols, } }), @@ -7069,33 +7088,29 @@ impl LspStore { let source_worktree = result.worktree.upgrade()?; let source_worktree_id = source_worktree.read(cx).id(); - let path; - let worktree; - if let Some((tree, rel_path)) = + let path = if let Some((tree, rel_path)) = this.worktree_store.read(cx).find_worktree(&abs_path, cx) { - worktree = tree; - path = rel_path; + let worktree_id = tree.read(cx).id(); + SymbolLocation::InProject(ProjectPath { + worktree_id, + path: rel_path, + }) } else { - worktree = source_worktree; - path = relativize_path(&result.worktree_abs_path, &abs_path); - } - - let worktree_id = worktree.read(cx).id(); - let project_path = ProjectPath { - worktree_id, - path: path.into(), + SymbolLocation::OutsideProject { + signature: this.symbol_signature(&abs_path), + abs_path: abs_path.into(), + } }; - let signature = this.symbol_signature(&project_path); + Some(CoreSymbol { source_language_server_id: result.server_id, language_server_name: result.lsp_adapter.name.clone(), source_worktree_id, - path: project_path, + path, kind: symbol_kind, name: symbol_name, range: range_from_lsp(symbol_location.range), - signature, }) }) .collect() @@ -7638,7 +7653,7 @@ impl LspStore { let worktree_id = worktree.read(cx).id(); let project_path = ProjectPath { worktree_id, - path: relative_path.into(), + path: relative_path, }; if let Some(buffer_handle) = self.buffer_store.read(cx).get_by_path(&project_path) { @@ -7735,7 +7750,7 @@ impl LspStore { &mut self, worktree_id: WorktreeId, server_id: LanguageServerId, - path_in_worktree: Arc, + path_in_worktree: Arc, diagnostics: Vec>>, _: &mut Context, ) -> Result>> { @@ -7827,18 +7842,21 @@ impl LspStore { ))); }; - let worktree_abs_path = if let Some(worktree_abs_path) = self - .worktree_store - .read(cx) - .worktree_for_id(symbol.path.worktree_id, cx) - .map(|worktree| worktree.read(cx).abs_path()) - { - worktree_abs_path - } else { - return Task::ready(Err(anyhow!("worktree not found for symbol"))); + let symbol_abs_path = match &symbol.path { + SymbolLocation::InProject(project_path) => self + .worktree_store + .read(cx) + .absolutize(&project_path, cx) + .context("no such worktree"), + SymbolLocation::OutsideProject { + abs_path, + signature: _, + } => Ok(abs_path.to_path_buf()), + }; + let symbol_abs_path = match symbol_abs_path { + Ok(abs_path) => abs_path, + Err(err) => return Task::ready(Err(err)), }; - - let symbol_abs_path = resolve_path(&worktree_abs_path, &symbol.path.path); let symbol_uri = if let Ok(uri) = lsp::Uri::from_file_path(symbol_abs_path) { uri } else { @@ -7891,8 +7909,7 @@ impl LspStore { worktree_store.find_worktree(&worktree_root_target, cx) }) })? { - let relative_path = - known_relative_path.unwrap_or_else(|| Arc::::from(result.1)); + let relative_path = known_relative_path.unwrap_or_else(|| result.1.clone()); (result.0, relative_path) } else { let worktree = lsp_store @@ -7919,7 +7936,11 @@ impl LspStore { let relative_path = if let Some(known_path) = known_relative_path { known_path } else { - abs_path.strip_prefix(worktree_root)?.into() + RelPath::from_std_path( + abs_path.strip_prefix(worktree_root)?, + PathStyle::local(), + ) + .context("failed to create relative path")? }; (worktree, relative_path) }; @@ -8326,39 +8347,56 @@ impl LspStore { mut cx: AsyncApp, ) -> Result { let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id); - let (worktree_id, worktree, old_path, is_dir) = this + let new_worktree_id = WorktreeId::from_proto(envelope.payload.new_worktree_id); + let new_path = + RelPath::from_proto(&envelope.payload.new_path).context("invalid relative path")?; + + let (worktree_store, old_worktree, new_worktree, old_entry) = this .update(&mut cx, |this, cx| { - this.worktree_store + let (worktree, entry) = this + .worktree_store .read(cx) - .worktree_and_entry_for_id(entry_id, cx) - .map(|(worktree, entry)| { - ( - worktree.read(cx).id(), - worktree, - entry.path.clone(), - entry.is_dir(), - ) - }) + .worktree_and_entry_for_id(entry_id, cx)?; + let new_worktree = this + .worktree_store + .read(cx) + .worktree_for_id(new_worktree_id, cx)?; + Some(( + this.worktree_store.clone(), + worktree, + new_worktree, + entry.clone(), + )) })? .context("worktree not found")?; - let (old_abs_path, new_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)) - }; + let (old_abs_path, old_worktree_id) = old_worktree.read_with(&cx, |worktree, _| { + (worktree.absolutize(&old_entry.path), worktree.id()) + })?; + let new_abs_path = + new_worktree.read_with(&cx, |worktree, _| worktree.absolutize(&new_path))?; let _transaction = Self::will_rename_entry( this.downgrade(), - worktree_id, + old_worktree_id, &old_abs_path, &new_abs_path, - is_dir, + old_entry.is_dir(), + cx.clone(), + ) + .await; + let response = WorktreeStore::handle_rename_project_entry( + worktree_store, + envelope.payload, cx.clone(), ) .await; - let response = Worktree::handle_rename_entry(worktree, envelope.payload, cx.clone()).await; this.read_with(&cx, |this, _| { - this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir); + this.did_rename_entry( + old_worktree_id, + &old_abs_path, + &new_abs_path, + old_entry.is_dir(), + ); }) .ok(); response @@ -8381,7 +8419,7 @@ impl LspStore { { let project_path = ProjectPath { worktree_id, - path: Arc::::from_proto(message_summary.path), + path: RelPath::from_proto(&message_summary.path).context("invalid path")?, }; let path = project_path.path.clone(); let server_id = LanguageServerId(message_summary.language_server_id as usize); @@ -9436,10 +9474,16 @@ 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(&cx, |this, _| { - let signature = this.symbol_signature(&symbol.path); - anyhow::ensure!(signature == symbol.signature, "invalid symbol signature"); - Ok(symbol) + this.read_with(&cx, |this, _| { + if let SymbolLocation::OutsideProject { + abs_path, + signature, + } = &symbol.path + { + let new_signature = this.symbol_signature(&abs_path); + anyhow::ensure!(&new_signature == signature, "invalid symbol signature"); + } + Ok(()) })??; let buffer = this .update(&mut cx, |this, cx| { @@ -9452,7 +9496,6 @@ impl LspStore { name: symbol.name, kind: symbol.kind, range: symbol.range, - signature: symbol.signature, label: CodeLabel { text: Default::default(), runs: Default::default(), @@ -9484,10 +9527,9 @@ impl LspStore { })? } - fn symbol_signature(&self, project_path: &ProjectPath) -> [u8; 32] { + fn symbol_signature(&self, abs_path: &Path) -> [u8; 32] { let mut hasher = Sha256::new(); - hasher.update(project_path.worktree_id.to_proto().to_be_bytes()); - hasher.update(project_path.path.to_string_lossy().as_bytes()); + hasher.update(abs_path.to_string_lossy().as_bytes()); hasher.update(self.nonce.to_be_bytes()); hasher.finalize().as_slice().try_into().unwrap() } @@ -10233,7 +10275,7 @@ impl LspStore { let project_path = ProjectPath { worktree_id: worktree.read(cx).id(), - path: relative_path.into(), + path: relative_path, }; Some( @@ -10799,7 +10841,7 @@ impl LspStore { pub(super) fn update_local_worktree_language_servers( &mut self, worktree_handle: &Entity, - changes: &[(Arc, ProjectEntryId, PathChange)], + changes: &[(Arc, ProjectEntryId, PathChange)], cx: &mut Context, ) { if changes.is_empty() { @@ -10821,7 +10863,7 @@ impl LspStore { language_server_ids.sort(); language_server_ids.dedup(); - let abs_path = worktree_handle.read(cx).abs_path(); + // let abs_path = worktree_handle.read(cx).abs_path(); for server_id in &language_server_ids { if let Some(LanguageServerState::Running { server, .. }) = local.language_servers.get(server_id) @@ -10834,7 +10876,7 @@ impl LspStore { changes: changes .iter() .filter_map(|(path, _, change)| { - if !watched_paths.is_match(path) { + if !watched_paths.is_match(path.as_std_path()) { return None; } let typ = match change { @@ -10844,10 +10886,11 @@ impl LspStore { PathChange::Updated => lsp::FileChangeType::CHANGED, PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, }; - Some(lsp::FileEvent { - uri: lsp::Uri::from_file_path(abs_path.join(path)).unwrap(), - typ, - }) + let uri = lsp::Uri::from_file_path( + worktree_handle.read(cx).absolutize(&path), + ) + .ok()?; + Some(lsp::FileEvent { uri, typ }) }) .collect(), }; @@ -10859,7 +10902,7 @@ impl LspStore { } } for (path, _, _) in changes { - if let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) + if let Some(file_name) = path.file_name() && local.watched_manifest_filenames.contains(file_name) { self.request_workspace_config_refresh(); @@ -10879,12 +10922,10 @@ impl LspStore { } fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { - proto::Symbol { + let mut result = proto::Symbol { language_server_name: symbol.language_server_name.0.to_string(), source_worktree_id: symbol.source_worktree_id.to_proto(), language_server_id: symbol.source_language_server_id.to_proto(), - worktree_id: symbol.path.worktree_id.to_proto(), - path: symbol.path.path.as_ref().to_proto(), name: symbol.name.clone(), kind: unsafe { mem::transmute::(symbol.kind) }, start: Some(proto::PointUtf16 { @@ -10895,17 +10936,45 @@ impl LspStore { row: symbol.range.end.0.row, column: symbol.range.end.0.column, }), - signature: symbol.signature.to_vec(), + worktree_id: Default::default(), + path: Default::default(), + signature: Default::default(), + }; + match &symbol.path { + SymbolLocation::InProject(path) => { + result.worktree_id = path.worktree_id.to_proto(); + result.path = path.path.to_proto(); + } + SymbolLocation::OutsideProject { + abs_path, + signature, + } => { + result.path = abs_path.to_string_lossy().to_string(); + result.signature = signature.to_vec(); + } } + result } fn deserialize_symbol(serialized_symbol: proto::Symbol) -> Result { let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id); let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id); let kind = unsafe { mem::transmute::(serialized_symbol.kind) }; - let path = ProjectPath { - worktree_id, - path: Arc::::from_proto(serialized_symbol.path), + + let path = if serialized_symbol.signature.is_empty() { + SymbolLocation::InProject(ProjectPath { + worktree_id, + path: RelPath::from_proto(&serialized_symbol.path) + .context("invalid symbol path")?, + }) + } else { + SymbolLocation::OutsideProject { + abs_path: Path::new(&serialized_symbol.path).into(), + signature: serialized_symbol + .signature + .try_into() + .map_err(|_| anyhow!("invalid signature"))?, + } }; let start = serialized_symbol.start.context("invalid start")?; @@ -10921,10 +10990,6 @@ impl LspStore { range: Unclipped(PointUtf16::new(start.row, start.column)) ..Unclipped(PointUtf16::new(end.row, end.column)), kind, - signature: serialized_symbol - .signature - .try_into() - .map_err(|_| anyhow!("invalid signature"))?, }) } @@ -12484,7 +12549,7 @@ impl DiagnosticSummary { pub fn to_proto( self, language_server_id: LanguageServerId, - path: &Path, + path: &RelPath, ) -> proto::DiagnosticSummary { proto::DiagnosticSummary { path: path.to_proto(), @@ -12657,7 +12722,7 @@ pub fn language_server_settings<'a>( language_server_settings_for( SettingsLocation { worktree_id: delegate.worktree_id(), - path: delegate.worktree_root_path(), + path: RelPath::empty(), }, language, cx, @@ -12847,16 +12912,12 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { Some(dir) } - async fn read_text_file(&self, path: PathBuf) -> Result { + async fn read_text_file(&self, path: &RelPath) -> Result { let entry = self .worktree - .entry_for_path(&path) + .entry_for_path(path) .with_context(|| format!("no worktree entry for path {path:?}"))?; - let abs_path = self - .worktree - .absolutize(&entry.path) - .with_context(|| format!("cannot absolutize path {path:?}"))?; - + let abs_path = self.worktree.absolutize(&entry.path); self.fs.load(&abs_path).await } } @@ -12870,14 +12931,17 @@ async fn populate_labels_for_symbols( #[allow(clippy::mutable_key_type)] let mut symbols_by_language = HashMap::>, Vec>::default(); - let mut unknown_paths = BTreeSet::new(); + let mut unknown_paths = BTreeSet::>::new(); for symbol in symbols { + let Some(file_name) = symbol.path.file_name() else { + continue; + }; let language = language_registry - .language_for_file_path(&symbol.path.path) + .language_for_file_path(Path::new(file_name)) .await .ok() .or_else(|| { - unknown_paths.insert(symbol.path.path.clone()); + unknown_paths.insert(file_name.into()); None }); symbols_by_language @@ -12887,10 +12951,7 @@ async fn populate_labels_for_symbols( } for unknown_path in unknown_paths { - log::info!( - "no language found for symbol path {}", - unknown_path.display() - ); + log::info!("no language found for symbol in file {unknown_path:?}"); } let mut label_params = Vec::new(); @@ -12933,7 +12994,6 @@ async fn populate_labels_for_symbols( name, kind: symbol.kind, range: symbol.range, - signature: symbol.signature, }); } } diff --git a/crates/project/src/manifest_tree.rs b/crates/project/src/manifest_tree.rs index 5a3c7bd40fb11ee5bebe340ddc57ec71a112270b..ffa4872ca78e2295e18c515a03e81e4d7b63c07b 100644 --- a/crates/project/src/manifest_tree.rs +++ b/crates/project/src/manifest_tree.rs @@ -7,7 +7,7 @@ mod manifest_store; mod path_trie; mod server_tree; -use std::{borrow::Borrow, collections::hash_map::Entry, ops::ControlFlow, path::Path, sync::Arc}; +use std::{borrow::Borrow, collections::hash_map::Entry, ops::ControlFlow, sync::Arc}; use collections::HashMap; use gpui::{App, AppContext as _, Context, Entity, Subscription}; @@ -15,6 +15,7 @@ use language::{ManifestDelegate, ManifestName, ManifestQuery}; pub use manifest_store::ManifestProvidersStore; use path_trie::{LabelPresence, RootPathTrie, TriePath}; use settings::{SettingsStore, WorktreeId}; +use util::rel_path::RelPath; use worktree::{Event as WorktreeEvent, Snapshot, Worktree}; use crate::{ @@ -184,7 +185,7 @@ impl ManifestTree { .and_then(|manifest_name| self.root_for_path(project_path, manifest_name, delegate, cx)) .unwrap_or_else(|| ProjectPath { worktree_id, - path: Arc::from(Path::new("")), + path: RelPath::empty().into(), }) } @@ -211,7 +212,7 @@ impl ManifestQueryDelegate { } impl ManifestDelegate for ManifestQueryDelegate { - fn exists(&self, path: &Path, is_dir: Option) -> bool { + fn exists(&self, path: &RelPath, is_dir: Option) -> bool { 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 9cebfda25c69fa35b06cefe9ec744b5e6152a820..2dd301fb13a89a61086d61a55c0f53568d1f3dd0 100644 --- a/crates/project/src/manifest_tree/path_trie.rs +++ b/crates/project/src/manifest_tree/path_trie.rs @@ -1,11 +1,11 @@ use std::{ collections::{BTreeMap, btree_map::Entry}, - ffi::OsStr, ops::ControlFlow, - path::{Path, PathBuf}, sync::Arc, }; +use util::rel_path::RelPath; + /// [RootPathTrie] is a workhorse of [super::ManifestTree]. It is responsible for determining the closest known entry for a given path. /// It also determines how much of a given path is unexplored, thus letting callers fill in that gap if needed. /// Conceptually, it allows one to annotate Worktree entries with arbitrary extra metadata and run closest-ancestor searches. @@ -14,9 +14,9 @@ use std::{ /// For example, if there's a project root at path `python/project` and we query for a path `python/project/subdir/another_subdir/file.py`, there is /// a known root at `python/project` and the unexplored part is `subdir/another_subdir` - we need to run a scan on these 2 directories. pub(super) struct RootPathTrie