From cfc050e3fe2cd795941e50a246006834af372fc3 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 13 Dec 2023 12:16:39 -0500 Subject: [PATCH 01/61] Fire focus handlers on draw to avoid timing with newly created item Co-Authored-By: Antonio Scandurra --- crates/gpui2/src/app.rs | 68 ++------------- crates/gpui2/src/elements/div.rs | 13 +-- crates/gpui2/src/interactive.rs | 8 +- crates/gpui2/src/key_dispatch.rs | 20 ++++- crates/gpui2/src/window.rs | 137 ++++++++++++------------------- 5 files changed, 84 insertions(+), 162 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 62ce6305ea7303362a5b6fb8889e63e18ca3fd68..9b9a5921e17dca7fa3c22571ca6f6f0fdc9e5c55 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,11 +17,10 @@ use time::UtcOffset; use crate::{ current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, - DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, - ForegroundExecutor, KeyBinding, Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, - Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, - SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, - WindowContext, WindowHandle, WindowId, + DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap, + Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, + SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, + TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{FxHashMap, FxHashSet, VecDeque}; @@ -577,23 +576,21 @@ impl AppContext { Effect::Notify { emitter } => { self.apply_notify_effect(emitter); } + Effect::Emit { emitter, event_type, event, } => self.apply_emit_effect(emitter, event_type, event), - Effect::FocusChanged { - window_handle, - focused, - } => { - self.apply_focus_changed_effect(window_handle, focused); - } + Effect::Refresh => { self.apply_refresh_effect(); } + Effect::NotifyGlobalObservers { global_type } => { self.apply_notify_global_observers_effect(global_type); } + Effect::Defer { callback } => { self.apply_defer_effect(callback); } @@ -693,51 +690,6 @@ impl AppContext { }); } - fn apply_focus_changed_effect( - &mut self, - window_handle: AnyWindowHandle, - focused: Option, - ) { - window_handle - .update(self, |_, cx| { - // The window might change focus multiple times in an effect cycle. - // We only honor effects for the most recently focused handle. - if cx.window.focus == focused { - // if someone calls focus multiple times in one frame with the same handle - // the first apply_focus_changed_effect will have taken the last blur already - // and run the rest of this, so we can return. - let Some(last_blur) = cx.window.last_blur.take() else { - return; - }; - - let focused = focused - .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); - - let blurred = - last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - - let focus_changed = focused.is_some() || blurred.is_some(); - let event = FocusEvent { focused, blurred }; - - let mut listeners = mem::take(&mut cx.window.rendered_frame.focus_listeners); - if focus_changed { - for listener in &mut listeners { - listener(&event, cx); - } - } - cx.window.rendered_frame.focus_listeners = listeners; - - if focus_changed { - cx.window - .focus_listeners - .clone() - .retain(&(), |listener| listener(&event, cx)); - } - } - }) - .ok(); - } - fn apply_refresh_effect(&mut self) { for window in self.windows.values_mut() { if let Some(window) = window.as_mut() { @@ -1246,10 +1198,6 @@ pub(crate) enum Effect { event_type: TypeId, event: Box, }, - FocusChanged { - window_handle: AnyWindowHandle, - focused: Option, - }, Refresh, NotifyGlobalObservers { global_type: TypeId, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index a102c71a6fbc1d8d0dbc0b2d984efcbdbc677276..3ec0a46fcbfbe9caf0bd371741026a18a1345017 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,9 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, - IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, - WindowContext, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement, + KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, + StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -462,10 +461,6 @@ pub trait FocusableElement: InteractiveElement { } } -pub type FocusListeners = Vec; - -pub type FocusListener = Box; - pub type MouseDownListener = Box; pub type MouseUpListener = diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 1c6955111cd2f31870ff92fb2dfeff11db4b02ef..58ee849aad4a6d665c6ec14176f72eb457c9a82e 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,6 +1,5 @@ use crate::{ - div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point, - Render, ViewContext, + div, point, Div, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; @@ -290,11 +289,6 @@ impl InputEvent { } } -pub struct FocusEvent { - pub blurred: Option, - pub focused: Option, -} - #[cfg(test)] mod test { use crate::{ diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index a44987aa39e65ecbabacbca40868fc27d61a3b49..ddb1f1e6ca8b4fd4db5d9c317144ca302519aa87 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -29,6 +29,7 @@ pub(crate) struct DispatchNode { pub key_listeners: Vec, pub action_listeners: Vec, pub context: Option, + focus_id: Option, parent: Option, } @@ -127,8 +128,9 @@ impl DispatchTree { } pub fn make_focusable(&mut self, focus_id: FocusId) { - self.focusable_node_ids - .insert(focus_id, self.active_node_id()); + let node_id = self.active_node_id(); + self.active_node().focus_id = Some(focus_id); + self.focusable_node_ids.insert(focus_id, node_id); } pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { @@ -247,6 +249,20 @@ impl DispatchTree { dispatch_path } + pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> { + let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new(); + let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied(); + while let Some(node_id) = current_node_id { + let node = self.node(node_id); + if let Some(focus_id) = node.focus_id { + focus_path.push(focus_id); + } + current_node_id = node.parent; + } + focus_path.reverse(); // Reverse the path so it goes from the root to the focused node. + focus_path + } + pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 77eb4e27be20bd734c27258a00fd05eaeff35caf..d2f48917112f5eda16c8a9a8a684bf47b036da09 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2,10 +2,10 @@ use crate::{ key_dispatch::DispatchActionListener, px, size, Action, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, - EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, - ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, - Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, - Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, ImageData, + InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, Model, + ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, @@ -74,9 +74,13 @@ impl DispatchPhase { type AnyObserver = Box bool + 'static>; type AnyMouseListener = Box; -type AnyFocusListener = Box; type AnyWindowFocusListener = Box bool + 'static>; +struct FocusEvent { + previous_focus_path: SmallVec<[FocusId; 8]>, + current_focus_path: SmallVec<[FocusId; 8]>, +} + slotmap::new_key_type! { pub struct FocusId; } impl FocusId { @@ -227,8 +231,8 @@ pub struct Window { pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, pub(crate) focus_handles: Arc>>, - pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, - pub(crate) blur_listeners: SubscriberSet<(), AnyObserver>, + focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, + blur_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, @@ -238,7 +242,6 @@ pub struct Window { active: bool, pub(crate) dirty: bool, activation_observers: SubscriberSet<(), AnyObserver>, - pub(crate) last_blur: Option>, pub(crate) focus: Option, } @@ -250,10 +253,10 @@ pub(crate) struct ElementStateBox { // #[derive(Default)] pub(crate) struct Frame { + focus: Option, pub(crate) element_states: HashMap, mouse_listeners: HashMap>, pub(crate) dispatch_tree: DispatchTree, - pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, pub(crate) z_index_stack: StackingOrder, @@ -264,10 +267,10 @@ pub(crate) struct Frame { impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { + focus: None, element_states: HashMap::default(), mouse_listeners: HashMap::default(), dispatch_tree, - focus_listeners: Vec::new(), scene_builder: SceneBuilder::default(), z_index_stack: StackingOrder::default(), depth_map: Default::default(), @@ -279,10 +282,15 @@ impl Frame { fn clear(&mut self) { self.element_states.clear(); self.mouse_listeners.values_mut().for_each(Vec::clear); - self.focus_listeners.clear(); self.dispatch_tree.clear(); self.depth_map.clear(); } + + fn focus_path(&self) -> SmallVec<[FocusId; 8]> { + self.focus + .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) + .unwrap_or_default() + } } impl Window { @@ -372,7 +380,6 @@ impl Window { active: false, dirty: false, activation_observers: SubscriberSet::new(), - last_blur: None, focus: None, } } @@ -449,35 +456,17 @@ impl<'a> WindowContext<'a> { return; } - let focus_id = handle.id; - - if self.window.last_blur.is_none() { - self.window.last_blur = Some(self.window.focus); - } - - self.window.focus = Some(focus_id); + self.window.focus = Some(handle.id); self.window .rendered_frame .dispatch_tree .clear_pending_keystrokes(); - self.app.push_effect(Effect::FocusChanged { - window_handle: self.window.handle, - focused: Some(focus_id), - }); self.notify(); } /// Remove focus from all elements within this context's window. pub fn blur(&mut self) { - if self.window.last_blur.is_none() { - self.window.last_blur = Some(self.window.focus); - } - self.window.focus = None; - self.app.push_effect(Effect::FocusChanged { - window_handle: self.window.handle, - focused: None, - }); self.notify(); } @@ -1236,16 +1225,6 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { - let window_was_focused = self - .window - .focus - .and_then(|focus_id| { - self.window - .rendered_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) - .is_some(); self.text_system().start_frame(); self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); @@ -1284,23 +1263,6 @@ impl<'a> WindowContext<'a> { }); } - let window_is_focused = self - .window - .focus - .and_then(|focus_id| { - self.window - .next_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) - .is_some(); - if window_was_focused && !window_is_focused { - self.window - .blur_listeners - .clone() - .retain(&(), |listener| listener(self)); - } - self.window .next_frame .dispatch_tree @@ -1308,11 +1270,34 @@ impl<'a> WindowContext<'a> { &mut self.window.rendered_frame.dispatch_tree, self.window.focus, ); + self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + let previous_focus_path = self.window.rendered_frame.focus_path(); + let window = &mut self.window; mem::swap(&mut window.rendered_frame, &mut window.next_frame); + let current_focus_path = self.window.rendered_frame.focus_path(); + + if previous_focus_path != current_focus_path { + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { + self.window + .blur_listeners + .clone() + .retain(&(), |listener| listener(self)); + } + + let event = FocusEvent { + previous_focus_path, + current_focus_path, + }; + self.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, self)); + } + let scene = self.window.rendered_frame.scene_builder.build(); // Set the cursor only if we're the active window. @@ -1699,22 +1684,6 @@ impl<'a> WindowContext<'a> { result } - /// Register a focus listener for the next frame only. It will be cleared - /// on the next frame render. You should use this method only from within elements, - /// and we may want to enforce that better via a different context type. - // todo!() Move this to `FrameContext` to emphasize its individuality? - pub fn on_focus_changed( - &mut self, - listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static, - ) { - self.window - .next_frame - .focus_listeners - .push(Box::new(move |event, cx| { - listener(event, cx); - })); - } - /// Set an input handler, such as [ElementInputHandler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. @@ -2389,7 +2358,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event.focused.as_ref().map(|focused| focused.id) == Some(focus_id) { + if event.previous_focus_path.last() != Some(&focus_id) + && event.current_focus_path.last() == Some(&focus_id) + { listener(view, cx) } }) @@ -2414,10 +2385,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event - .focused - .as_ref() - .map_or(false, |focused| focus_id.contains(focused.id, cx)) + if !event.previous_focus_path.contains(&focus_id) + && event.current_focus_path.contains(&focus_id) { listener(view, cx) } @@ -2443,7 +2412,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event.blurred.as_ref().map(|blurred| blurred.id) == Some(focus_id) { + if event.previous_focus_path.last() == Some(&focus_id) + && event.current_focus_path.last() != Some(&focus_id) + { listener(view, cx) } }) @@ -2484,10 +2455,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event - .blurred - .as_ref() - .map_or(false, |blurred| focus_id.contains(blurred.id, cx)) + if event.previous_focus_path.contains(&focus_id) + && !event.current_focus_path.contains(&focus_id) { listener(view, cx) } From 0e19da3107657fcdda07aa7145611a1f762dd126 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:53:53 +0100 Subject: [PATCH 02/61] Bring back semantic search UI --- crates/search2/src/project_search.rs | 340 ++++++++++----------------- 1 file changed, 128 insertions(+), 212 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 34cd2b98149d74d6064b0abc50c7e938ed1c82a4..875d2fe095a73495144182e14e5e4e4dd9959582 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -1,7 +1,8 @@ use crate::{ - history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode, - NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, - SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord, + history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode, + ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, + SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, + ToggleWholeWord, }; use anyhow::{Context as _, Result}; use collections::HashMap; @@ -29,7 +30,7 @@ use std::{ mem, ops::{Not, Range}, path::PathBuf, - time::Duration, + time::{Duration, Instant}, }; use ui::{ @@ -283,196 +284,86 @@ impl Render for ProjectSearchView { let model = self.model.read(cx); let has_no_results = model.no_results.unwrap_or(false); let is_search_underway = model.pending_search.is_some(); - let major_text = if is_search_underway { + let mut major_text = if is_search_underway { Label::new("Searching...") } else if has_no_results { - Label::new("No results for a given query") + Label::new("No results") } else { Label::new(format!("{} search all files", self.current_mode.label())) }; + + let mut show_minor_text = true; + let semantic_status = self.semantic_state.as_ref().and_then(|semantic| { + let status = semantic.index_status; + match status { + SemanticIndexStatus::NotAuthenticated => { + major_text = Label::new("Not Authenticated"); + show_minor_text = false; + Some( + "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables. If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()) + } + SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), + SemanticIndexStatus::Indexing { + remaining_files, + rate_limit_expiry, + } => { + if remaining_files == 0 { + Some("Indexing...".to_string()) + } else { + if let Some(rate_limit_expiry) = rate_limit_expiry { + let remaining_seconds = + rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) { + Some(format!( + "Remaining files to index (rate limit resets in {}s): {}", + remaining_seconds.as_secs(), + remaining_files + )) + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } + } + } + SemanticIndexStatus::NotIndexed => None, + } + }); let major_text = div().justify_center().max_w_96().child(major_text); - let middle_text = div() - .items_center() - .max_w_96() - .child(Label::new(self.landing_text_minor()).size(LabelSize::Small)); + + let minor_text: Option = if let Some(no_results) = model.no_results { + if model.pending_search.is_none() && no_results { + Some("No results found in this project for the provided query".into()) + } else { + None + } + } else { + if let Some(mut semantic_status) = semantic_status { + semantic_status.extend(self.landing_text_minor().chars()); + Some(semantic_status.into()) + } else { + Some(self.landing_text_minor()) + } + }; + let minor_text = minor_text.map(|text| { + div() + .items_center() + .max_w_96() + .child(Label::new(text).size(LabelSize::Small)) + }); v_stack().flex_1().size_full().justify_center().child( h_stack() .size_full() .justify_center() .child(h_stack().flex_1()) - .child(v_stack().child(major_text).child(middle_text)) + .child(v_stack().child(major_text).children(minor_text)) .child(h_stack().flex_1()), ) } } } -// impl Entity for ProjectSearchView { -// type Event = ViewEvent; -// } - -// impl View for ProjectSearchView { -// fn ui_name() -> &'static str { -// "ProjectSearchView" -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let model = &self.model.read(cx); -// if model.match_ranges.is_empty() { -// enum Status {} - -// let theme = theme::current(cx).clone(); - -// // If Search is Active -> Major: Searching..., Minor: None -// // If Semantic -> Major: "Search using Natural Language", Minor: {Status}/n{ex...}/n{ex...} -// // If Regex -> Major: "Search using Regex", Minor: {ex...} -// // If Text -> Major: "Text search all files and folders", Minor: {...} - -// let current_mode = self.current_mode; -// let mut major_text = if model.pending_search.is_some() { -// Cow::Borrowed("Searching...") -// } else if model.no_results.is_some_and(|v| v) { -// Cow::Borrowed("No Results") -// } else { -// match current_mode { -// SearchMode::Text => Cow::Borrowed("Text search all files and folders"), -// SearchMode::Semantic => { -// Cow::Borrowed("Search all code objects using Natural Language") -// } -// SearchMode::Regex => Cow::Borrowed("Regex search all files and folders"), -// } -// }; - -// let mut show_minor_text = true; -// let semantic_status = self.semantic_state.as_ref().and_then(|semantic| { -// let status = semantic.index_status; -// match status { -// SemanticIndexStatus::NotAuthenticated => { -// major_text = Cow::Borrowed("Not Authenticated"); -// show_minor_text = false; -// Some(vec![ -// "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables." -// .to_string(), "If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()]) -// } -// SemanticIndexStatus::Indexed => Some(vec!["Indexing complete".to_string()]), -// SemanticIndexStatus::Indexing { -// remaining_files, -// rate_limit_expiry, -// } => { -// if remaining_files == 0 { -// Some(vec![format!("Indexing...")]) -// } else { -// if let Some(rate_limit_expiry) = rate_limit_expiry { -// let remaining_seconds = -// rate_limit_expiry.duration_since(Instant::now()); -// if remaining_seconds > Duration::from_secs(0) { -// Some(vec![format!( -// "Remaining files to index (rate limit resets in {}s): {}", -// remaining_seconds.as_secs(), -// remaining_files -// )]) -// } else { -// Some(vec![format!("Remaining files to index: {}", remaining_files)]) -// } -// } else { -// Some(vec![format!("Remaining files to index: {}", remaining_files)]) -// } -// } -// } -// SemanticIndexStatus::NotIndexed => None, -// } -// }); - -// let minor_text = if let Some(no_results) = model.no_results { -// if model.pending_search.is_none() && no_results { -// vec!["No results found in this project for the provided query".to_owned()] -// } else { -// vec![] -// } -// } else { -// match current_mode { -// SearchMode::Semantic => { -// let mut minor_text: Vec = Vec::new(); -// minor_text.push("".into()); -// if let Some(semantic_status) = semantic_status { -// minor_text.extend(semantic_status); -// } -// if show_minor_text { -// minor_text -// .push("Simply explain the code you are looking to find.".into()); -// minor_text.push( -// "ex. 'prompt user for permissions to index their project'".into(), -// ); -// } -// minor_text -// } -// _ => vec![ -// "".to_owned(), -// "Include/exclude specific paths with the filter option.".to_owned(), -// "Matching exact word and/or casing is available too.".to_owned(), -// ], -// } -// }; - -// MouseEventHandler::new::(0, cx, |_, _| { -// Flex::column() -// .with_child(Flex::column().contained().flex(1., true)) -// .with_child( -// Flex::column() -// .align_children_center() -// .with_child(Label::new( -// major_text, -// theme.search.major_results_status.clone(), -// )) -// .with_children( -// minor_text.into_iter().map(|x| { -// Label::new(x, theme.search.minor_results_status.clone()) -// }), -// ) -// .aligned() -// .top() -// .contained() -// .flex(7., true), -// ) -// .contained() -// .with_background_color(theme.editor.background) -// }) -// .on_down(MouseButton::Left, |_, _, cx| { -// cx.focus_parent(); -// }) -// .into_any_named("project search view") -// } else { -// ChildView::new(&self.results_editor, cx) -// .flex(1., true) -// .into_any_named("project search view") -// } -// } - -// fn focus_in(&mut self, _: AnyView, cx: &mut ViewContext) { -// let handle = cx.weak_handle(); -// cx.update_global(|state: &mut ActiveSearches, cx| { -// state -// .0 -// .insert(self.model.read(cx).project.downgrade(), handle) -// }); - -// cx.update_global(|state: &mut ActiveSettings, cx| { -// state.0.insert( -// self.model.read(cx).project.downgrade(), -// self.current_settings(), -// ); -// }); - -// if cx.is_self_focused() { -// if self.query_editor_was_focused { -// cx.focus(&self.query_editor); -// } else { -// cx.focus(&self.results_editor); -// } -// } -// } -// } - impl FocusableView for ProjectSearchView { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { self.results_editor.focus_handle(cx) @@ -1256,7 +1147,7 @@ impl ProjectSearchView { fn landing_text_minor(&self) -> SharedString { match self.current_mode { SearchMode::Text | SearchMode::Regex => "Include/exclude specific paths with the filter option. Matching exact word and/or casing is available too.".into(), - SearchMode::Semantic => ".Simply explain the code you are looking to find. ex. 'prompt user for permissions to index their project'".into() + SearchMode::Semantic => "\nSimply explain the code you are looking to find. ex. 'prompt user for permissions to index their project'".into() } } } @@ -1277,8 +1168,8 @@ impl ProjectSearchBar { fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext) { if let Some(view) = self.active_project_search.as_ref() { view.update(cx, |this, cx| { - // todo: po: 2nd argument of `next_mode` should be `SemanticIndex::enabled(cx))`, but we need to flesh out port of semantic_index first. - let new_mode = crate::mode::next_mode(&this.current_mode, false); + let new_mode = + crate::mode::next_mode(&this.current_mode, SemanticIndex::enabled(cx)); this.activate_search_mode(new_mode, cx); let editor_handle = this.query_editor.focus_handle(cx); cx.focus(&editor_handle); @@ -1548,7 +1439,7 @@ impl Render for ProjectSearchBar { }); } let search = search.read(cx); - + let semantic_is_available = SemanticIndex::enabled(cx); let query_column = v_stack() //.flex_1() .child( @@ -1578,42 +1469,51 @@ impl Render for ProjectSearchBar { .unwrap_or_default(), ), ) - .child( - IconButton::new( - "project-search-case-sensitive", - Icon::CaseSensitive, - ) - .tooltip(|cx| { - Tooltip::for_action( - "Toggle case sensitive", - &ToggleCaseSensitive, - cx, + .when(search.current_mode != SearchMode::Semantic, |this| { + this.child( + IconButton::new( + "project-search-case-sensitive", + Icon::CaseSensitive, ) - }) - .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) - .on_click(cx.listener( - |this, _, cx| { - this.toggle_search_option( - SearchOptions::CASE_SENSITIVE, - cx, - ); - }, - )), - ) - .child( - IconButton::new("project-search-whole-word", Icon::WholeWord) .tooltip(|cx| { Tooltip::for_action( - "Toggle whole word", - &ToggleWholeWord, + "Toggle case sensitive", + &ToggleCaseSensitive, cx, ) }) - .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) - .on_click(cx.listener(|this, _, cx| { - this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); - })), - ), + .selected( + self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx), + ) + .on_click(cx.listener( + |this, _, cx| { + this.toggle_search_option( + SearchOptions::CASE_SENSITIVE, + cx, + ); + }, + )), + ) + .child( + IconButton::new("project-search-whole-word", Icon::WholeWord) + .tooltip(|cx| { + Tooltip::for_action( + "Toggle whole word", + &ToggleWholeWord, + cx, + ) + }) + .selected( + self.is_option_enabled(SearchOptions::WHOLE_WORD, cx), + ) + .on_click(cx.listener(|this, _, cx| { + this.toggle_search_option( + SearchOptions::WHOLE_WORD, + cx, + ); + })), + ) + }), ) .border_2() .bg(white()) @@ -1668,7 +1568,23 @@ impl Render for ProjectSearchBar { cx, ) }), - ), + ) + .when(semantic_is_available, |this| { + this.child( + Button::new("project-search-semantic-button", "Semantic") + .selected(search.current_mode == SearchMode::Semantic) + .on_click(cx.listener(|this, _, cx| { + this.activate_search_mode(SearchMode::Semantic, cx) + })) + .tooltip(|cx| { + Tooltip::for_action( + "Toggle semantic search", + &ActivateSemanticMode, + cx, + ) + }), + ) + }), ) .child( IconButton::new("project-search-toggle-replace", Icon::Replace) From ce1489f5dcb6471216d7a66924f880333afdddf4 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:22:29 +0100 Subject: [PATCH 03/61] Add inclusion of ignored files --- crates/search2/src/project_search.rs | 75 ++++++++++++++++++---------- crates/search2/src/search.rs | 5 ++ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 875d2fe095a73495144182e14e5e4e4dd9959582..f1b0c16d5708241cd4421e9c7b88d762383ff532 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -1,8 +1,8 @@ use crate::{ history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode, ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, - SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, - ToggleWholeWord, + SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored, + ToggleReplace, ToggleWholeWord, }; use anyhow::{Context as _, Result}; use collections::HashMap; @@ -1530,7 +1530,22 @@ impl Render for ProjectSearchBar { .flex_1() .border_1() .mr_2() - .child(search.included_files_editor.clone()), + .child(search.included_files_editor.clone()) + .when(search.current_mode != SearchMode::Semantic, |this| { + this.child( + SearchOptions::INCLUDE_IGNORED.as_button( + search + .search_options + .contains(SearchOptions::INCLUDE_IGNORED), + cx.listener(|this, _, cx| { + this.toggle_search_option( + SearchOptions::INCLUDE_IGNORED, + cx, + ); + }), + ), + ) + }), ) .child( h_stack() @@ -1671,34 +1686,14 @@ impl Render for ProjectSearchBar { .on_action(cx.listener(|this, _: &ToggleFilters, cx| { this.toggle_filters(cx); })) - .on_action(cx.listener(|this, _: &ToggleWholeWord, cx| { - this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); - })) - .on_action(cx.listener(|this, _: &ToggleCaseSensitive, cx| { - this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); - })) - .on_action(cx.listener(|this, action, cx| { - this.toggle_replace(action, cx); - })) .on_action(cx.listener(|this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx) })) .on_action(cx.listener(|this, _: &ActivateRegexMode, cx| { this.activate_search_mode(SearchMode::Regex, cx) })) - .on_action(cx.listener(|this, action, cx| { - if let Some(search) = this.active_project_search.as_ref() { - search.update(cx, |this, cx| { - this.replace_next(action, cx); - }) - } - })) - .on_action(cx.listener(|this, action, cx| { - if let Some(search) = this.active_project_search.as_ref() { - search.update(cx, |this, cx| { - this.replace_all(action, cx); - }) - } + .on_action(cx.listener(|this, _: &ActivateSemanticMode, cx| { + this.activate_search_mode(SearchMode::Semantic, cx) })) .on_action(cx.listener(|this, action, cx| { this.tab(action, cx); @@ -1709,6 +1704,36 @@ impl Render for ProjectSearchBar { .on_action(cx.listener(|this, action, cx| { this.cycle_mode(action, cx); })) + .when(search.current_mode != SearchMode::Semantic, |this| { + this.on_action(cx.listener(|this, action, cx| { + this.toggle_replace(action, cx); + })) + .on_action(cx.listener(|this, _: &ToggleWholeWord, cx| { + this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + })) + .on_action(cx.listener(|this, _: &ToggleCaseSensitive, cx| { + this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); + })) + .on_action(cx.listener(|this, action, cx| { + if let Some(search) = this.active_project_search.as_ref() { + search.update(cx, |this, cx| { + this.replace_next(action, cx); + }) + } + })) + .on_action(cx.listener(|this, action, cx| { + if let Some(search) = this.active_project_search.as_ref() { + search.update(cx, |this, cx| { + this.replace_all(action, cx); + }) + } + })) + .when(search.filters_enabled, |this| { + this.on_action(cx.listener(|this, _: &ToggleIncludeIgnored, cx| { + this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx); + })) + }) + }) .child(query_column) .child(mode_column) .child(replace_column) diff --git a/crates/search2/src/search.rs b/crates/search2/src/search.rs index 015c126aa1ef5abd37e67f6c169a3c7787ccf4b6..18fcc258f44497c8863f9000201238cebc1520ab 100644 --- a/crates/search2/src/search.rs +++ b/crates/search2/src/search.rs @@ -28,6 +28,7 @@ actions!( CycleMode, ToggleWholeWord, ToggleCaseSensitive, + ToggleIncludeIgnored, ToggleReplace, SelectNextMatch, SelectPrevMatch, @@ -57,6 +58,7 @@ impl SearchOptions { match *self { SearchOptions::WHOLE_WORD => "Match Whole Word", SearchOptions::CASE_SENSITIVE => "Match Case", + SearchOptions::INCLUDE_IGNORED => "Include ignored", _ => panic!("{:?} is not a named SearchOption", self), } } @@ -65,6 +67,7 @@ impl SearchOptions { match *self { SearchOptions::WHOLE_WORD => ui::Icon::WholeWord, SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive, + SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit, _ => panic!("{:?} is not a named SearchOption", self), } } @@ -73,6 +76,7 @@ impl SearchOptions { match *self { SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord), SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive), + SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored), _ => panic!("{:?} is not a named SearchOption", self), } } @@ -85,6 +89,7 @@ impl SearchOptions { let mut options = SearchOptions::NONE; options.set(SearchOptions::WHOLE_WORD, query.whole_word()); options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive()); + options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored()); options } From 85a1a8f777f4a8586d7adcd88863f6afeb086b2c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 11 Dec 2023 19:17:13 -0500 Subject: [PATCH 04/61] WIP --- docs/.gitignore | 2 + docs/book.toml | 6 + docs/src/CODE_OF_CONDUCT.md | 128 ++ docs/src/CONTRIBUTING.md | 3 + docs/src/SUMMARY.md | 18 + docs/src/configuring_zed.md | 1035 +++++++++++++++++ docs/src/configuring_zed__configuring_vim.md | 170 +++ docs/src/developing_zed__adding_languages.md | 83 ++ docs/src/developing_zed__building_zed.md | 107 ++ .../developing_zed__local_collaboration.md} | 0 docs/src/feedback.md | 29 + docs/src/getting_started.md | 15 + docs/src/system_requirements.md | 13 + docs/src/telemetry.md | 147 +++ {docs => docs_old}/backend-development.md | 0 {docs => docs_old}/building-zed.md | 0 {docs => docs_old}/company-and-vision.md | 0 {docs => docs_old}/design-tools.md | 0 {docs => docs_old}/index.md | 0 docs_old/local-collaboration.md | 22 + {docs => docs_old}/release-process.md | 0 .../theme/generating-theme-types.md | 0 {docs => docs_old}/tools.md | 0 {docs => docs_old}/zed/syntax-highlighting.md | 0 24 files changed, 1778 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/book.toml create mode 100644 docs/src/CODE_OF_CONDUCT.md create mode 100644 docs/src/CONTRIBUTING.md create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/configuring_zed.md create mode 100644 docs/src/configuring_zed__configuring_vim.md create mode 100644 docs/src/developing_zed__adding_languages.md create mode 100644 docs/src/developing_zed__building_zed.md rename docs/{local-collaboration.md => src/developing_zed__local_collaboration.md} (100%) create mode 100644 docs/src/feedback.md create mode 100644 docs/src/getting_started.md create mode 100644 docs/src/system_requirements.md create mode 100644 docs/src/telemetry.md rename {docs => docs_old}/backend-development.md (100%) rename {docs => docs_old}/building-zed.md (100%) rename {docs => docs_old}/company-and-vision.md (100%) rename {docs => docs_old}/design-tools.md (100%) rename {docs => docs_old}/index.md (100%) create mode 100644 docs_old/local-collaboration.md rename {docs => docs_old}/release-process.md (100%) rename {docs => docs_old}/theme/generating-theme-types.md (100%) rename {docs => docs_old}/tools.md (100%) rename {docs => docs_old}/zed/syntax-highlighting.md (100%) diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..eed3a3e0462ad6c1a85505fbedae8c76112571ba --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +book +.vercel diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000000000000000000000000000000000000..8062a76a4216ca248c786283bef9acf725fdee9d --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Nate Butler"] +language = "en" +multilingual = false +src = "src" +title = "Zed App Docs" diff --git a/docs/src/CODE_OF_CONDUCT.md b/docs/src/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..bc1d5522a0af5502dd8a428f8f3d28a9965b52be --- /dev/null +++ b/docs/src/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +hi@zed.dev. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/docs/src/CONTRIBUTING.md b/docs/src/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..d48c26844f2ee079fedcbdda70ac83704abb9747 --- /dev/null +++ b/docs/src/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +TBD diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000000000000000000000000000000000000..4c6f5f9421dcb7e6b8517085e0c30374a216632b --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,18 @@ +# Summary + +[Getting Started](./getting_started.md) +[Feedback](./feedback.md) + +# Configuring Zed +- [Settings](./configuring_zed.md) +- [Vim Mode](./configuring_zed__configuring_vim.md) + +# Developing Zed +- [Building from Source](./developing_zed__building_zed.md) +- [Local Collaboration](./developing_zed__local_collaboration.md) +- [Adding Languages](./developing_zed__adding_languages.md) + +# Other +- [Code of Conduct](./CODE_OF_CONDUCT.md) +- [Telemetry](./telemetry.md) +- [Contributing](./CONTRIBUTING.md) diff --git a/docs/src/configuring_zed.md b/docs/src/configuring_zed.md new file mode 100644 index 0000000000000000000000000000000000000000..9b9205f70ca7cac1cb587a2800de6af3f7bed956 --- /dev/null +++ b/docs/src/configuring_zed.md @@ -0,0 +1,1035 @@ +# Configuring Zed + +## Folder-specific settings + +Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it. + +The following global settings can be overriden with a folder-specific configuration: + +- `copilot` +- `enable_language_server` +- `ensure_final_newline_on_save` +- `format_on_save` +- `formatter` +- `hard_tabs` +- `language_overrides` +- `preferred_line_length` +- `remove_trailing_whitespace_on_save` +- `soft_wrap` +- `tab_size` +- `show_copilot_suggestions` +- `show_whitespaces` + +*See the Global settings section for details about these settings* + +## Global settings + +To get started with editing Zed's global settings, open `~/.config/zed/settings.json` via `cmd-,`, the command palette (`zed: open settings`), or the `Zed > Settings > Open Settings` application menu item. + +Here are all the currently available settings. + +## Active Pane Magnification + +- Description: Scale by which to zoom the active pane. When set to `1.0`, the active pane has the same size as others, but when set to a larger value, the active pane takes up more space. +- Setting: `active_pane_magnification` +- Default: `1.0` + +**Options** + +`float` values + +## Autosave + +- Description: When to automatically save edited buffers. +- Setting: `autosave` +- Default: `off` + +**Options** + +1. To disable autosave, set it to `off` + +```json +{ + "autosave": "off" +} +``` + +2. To autosave when focus changes, use `on_focus_change`: + +```json +{ + "autosave": "on_focus_change" +} +``` + +3. To autosave when the active window changes, use `on_window_change`: + +```json +{ + "autosave": "on_window_change" +} +``` + +4. To autosave after an inactivity period, use `after_delay`: + +```json +{ + "autosave": { + "after_delay": { + "milliseconds": 1000 + } + } +} +``` + +## Auto Update + +- Description: Whether or not to automatically check for updates. +- Setting: `auto_update` +- Default: `true` + +**Options** + +`boolean` values + +## Buffer Font Family + +- Description: The name of a font to use for rendering text in the editor. +- Setting: `buffer_font_family` +- Default: `Zed Mono` + +**Options** + +The name of any font family installed on the user's system + +## Buffer Font Features + +- Description: The OpenType features to enable for text in the editor. +- Setting: `buffer_font_features` +- Default: `null` + +**Options** + +Zed supports a subset of OpenType features that can be enabled or disabled for a given buffer or terminal font. The following [OpenType features](https://en.wikipedia.org/wiki/List_of_typographic_features) can be enabled or disabled too: `calt`, `case`, `cpsp`, `frac`, `liga`, `onum`, `ordn`, `pnum`, `ss01`, `ss02`, `ss03`, `ss04`, `ss05`, `ss06`, `ss07`, `ss08`, `ss09`, `ss10`, `ss11`, `ss12`, `ss13`, `ss14`, `ss15`, `ss16`, `ss17`, `ss18`, `ss19`, `ss20`, `subs`, `sups`, `swsh`, `titl`, `tnum`, `zero`. + +For example, to disable ligatures for a given font you can add the following to your settings: + +```json +{ + "buffer_font_features": { + "calt": false + } +} +``` + +## Buffer Font Size + +- Description: The default font size for text in the editor. +- Setting: `buffer_font_size` +- Default: `15` + +**Options** + +`integer` values + +## Confirm Quit + +- Description: Whether or not to prompt the user to confirm before closing the application. +- Setting: `confirm_quit` +- Default: `false` + +**Options** + +`boolean` values + +## Copilot + +- Description: Copilot-specific settings. +- Setting: `copilot` +- Default: + +```json +"copilot": { + "disabled_globs": [ + ".env" + ] +} +``` + +**Options** + +### Disabled Globs + +- Description: The set of glob patterns for which Copilot should be disabled in any matching file. +- Setting: `disabled_globs` +- Default: [".env"] + +**Options** + +List of `string` values + +## Cursor Blink + +- Description: Whether or not the cursor blinks. +- Setting: `cursor_blink` +- Default: `true` + +**Options** + +`boolean` values + +## Default Dock Anchor + +- Description: The default anchor for new docks. +- Setting: `default_dock_anchor` +- Default: `bottom` + +**Options** + +1. Position the dock attached to the bottom of the workspace: `bottom` +2. Position the dock to the right of the workspace like a side panel: `right` +3. Position the dock full screen over the entire workspace: `expanded` + +## Enable Language Server + +- Description: Whether or not to use language servers to provide code intelligence. +- Setting: `enable_language_server` +- Default: `true` + +**Options** + +`boolean` values + +## Ensure Final Newline On Save + +- Description: Whether or not to ensure there's a single newline at the end of a buffer when saving it. +- Setting: `ensure_final_newline_on_save` +- Default: `true` + +**Options** + +`boolean` values + +## LSP + +- Description: Configuration for language servers. +- Setting: `lsp` +- Default: `null` + +**Options** + +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. Example: + +```json +"lsp": { + "rust-analyzer": { + "initialization_options": { + "checkOnSave": { + "command": "clippy" // rust-analyzer.checkOnSave.command + } + } + } +} +``` + +## Format On Save + +- Description: Whether or not to perform a buffer format before saving. +- Setting: `format_on_save` +- Default: `on` + +**Options** + +1. `on`, enables format on save obeying `formatter` setting: + +```json +{ + "format_on_save": "on" +} +``` + +2. `off`, disables format on save: + +```json +{ + "format_on_save": "off" +} +``` + +## Formatter + +- Description: How to perform a buffer format. +- Setting: `formatter` +- Default: `language_server` + +**Options** + +1. To use the current language server, use `"language_server"`: + +```json +{ + "formatter": "language_server" +} +``` + +2. Or to use an external command, use `"external"`. Specify the name of the formatting program to run, and an array of arguments to pass to the program. The buffer's text will be passed to the program on stdin, and the formatted output should be written to stdout. For example, the following command would strip trailing spaces using [`sed(1)`](https://linux.die.net/man/1/sed): + +```json +{ + "formatter": { + "external": { + "command": "sed", + "arguments": ["-e", "s/ *$//"] + } + } +} +``` + +## Git + +- Description: Configuration for git-related features. +- Setting: `git` +- Default: + +```json +"git": { + "git_gutter": "tracked_files" +}, +``` + +### Git Gutter + +- Description: Whether or not to show the git gutter. +- Setting: `git_gutter` +- Default: `tracked_files` + +**Options** + +1. Show git gutter in tracked files + +```json +{ + "git_gutter": "tracked_files" +} +``` + +2. Hide git gutter + +```json +{ + "git_gutter": "hide" +} +``` + +## Hard Tabs + +- Description: Whether to indent lines using tab characters or multiple spaces. +- Setting: `hard_tabs` +- Default: `false` + +**Options** + +`boolean` values + +## Hover Popover Enabled + +- Description: Whether or not to show the informational hover box when moving the mouse over symbols in the editor. +- Setting: `hover_popover_enabled` +- Default: `true` + +**Options** + +`boolean` values + +## Inlay hints + +- Description: Configuration for displaying extra text with hints in the editor. +- Setting: `inlay_hints` +- Default: + +```json +"inlay_hints": { + "enabled": false, + "show_type_hints": true, + "show_parameter_hints": true, + "show_other_hints": true +} +``` + +**Options** + +Inlay hints querying consists of two parts: editor (client) and LSP server. +With the inlay settings above are changed to enable the hints, editor will start to query certain types of hints and react on LSP hint refresh request from the server. +At this point, the server may or may not return hints depending on its implementation, further configuration might be needed, refer to the corresponding LSP server documentation. + +Use `lsp` section for the server configuration, below are some examples for well known servers: + +### Rust + +```json +"lsp": { + "rust-analyzer": { + "initialization_options": { + "inlayHints": { + "maxLength": null, + "lifetimeElisionHints": { + "useParameterNames": true, + "enable": "skip_trivial" + }, + "closureReturnTypeHints": { + "enable": "always" + } + } + } + } +} +``` + +### Typescript + +```json +"lsp": { + "typescript-language-server": { + "initialization_options": { + "preferences": { + "includeInlayParameterNameHints": "all", + "includeInlayParameterNameHintsWhenArgumentMatchesName": true, + "includeInlayFunctionParameterTypeHints": true, + "includeInlayVariableTypeHints": true, + "includeInlayVariableTypeHintsWhenTypeMatchesName": false, + "includeInlayPropertyDeclarationTypeHints": true, + "includeInlayFunctionLikeReturnTypeHints": true, + "includeInlayEnumMemberValueHints": true + } + } + } +} +``` + +### Go + +```json +"lsp": { + "gopls": { + "initialization_options": { + "hints": { + "assignVariableTypes": true, + "compositeLiteralFields": true, + "compositeLiteralTypes": true, + "constantValues": true, + "functionTypeParameters": true, + "parameterNames": true, + "rangeVariableTypes": true + } + } + } +} +``` + +### Svelte + +```json +{ + "lsp": { + "typescript-language-server": { + "initialization_options": { + "preferences": { + "includeInlayParameterNameHints": "all", + "includeInlayParameterNameHintsWhenArgumentMatchesName": true, + "includeInlayFunctionParameterTypeHints": true, + "includeInlayVariableTypeHints": true, + "includeInlayVariableTypeHintsWhenTypeMatchesName": false, + "includeInlayPropertyDeclarationTypeHints": true, + "includeInlayFunctionLikeReturnTypeHints": true, + "includeInlayEnumMemberValueHints": true, + "includeInlayEnumMemberDeclarationTypes": true + } + } + } + } +} +``` + +## Journal + +- Description: Configuration for the journal. +- Setting: `journal` +- Default: + +```json +"journal": { + "path": "~", + "hour_format": "hour12" +} +``` + +### Path + +- Description: The path of the directory where journal entries are stored. +- Setting: `path` +- Default: `~` + +**Options** + +`string` values + +### Hour Format + +- Description: The format to use for displaying hours in the journal. +- Setting: `hour_format` +- Default: `hour12` + +**Options** + +1. 12-hour format: + +```json +{ + "hour_format": "hour12" +} +``` + +2. 24-hour format: + +```json +{ + "hour_format": "hour24" +} +``` + +## Language Overrides + +- Description: Configuration overrides for specific languages. +- Setting: `language_overrides` +- Default: `null` + +**Options** + +To override settings for a language, add an entry for that languages name to the `language_overrides` value. Example: + +```json +"language_overrides": { + "C": { + "format_on_save": "off", + "preferred_line_length": 64, + "soft_wrap": "preferred_line_length" + }, + "JSON": { + "tab_size": 4 + } +} +``` + +The following settings can be overridden for each specific language: + +- `enable_language_server` +- `ensure_final_newline_on_save` +- `format_on_save` +- `formatter` +- `hard_tabs` +- `preferred_line_length` +- `remove_trailing_whitespace_on_save` +- `show_copilot_suggestions` +- `show_whitespaces` +- `soft_wrap` +- `tab_size` + +These values take in the same options as the root-level settings with the same name. + +## Preferred Line Length + +- Description: The column at which to soft-wrap lines, for buffers where soft-wrap is enabled. +- Setting: `preferred_line_length` +- Default: `80` + +**Options** + +`integer` values + +## Projects Online By Default + +- Description: Whether or not to show the online projects view by default. +- Setting: `projects_online_by_default` +- Default: `true` + +**Options** + +`boolean` values + +## Remove Trailing Whitespace On Save + +- Description: Whether or not to remove any trailing whitespace from lines of a buffer before saving it. +- Setting: `remove_trailing_whitespace_on_save` +- Default: `true` + +**Options** + +`boolean` values + +## Semantic Index + +- Description: Settings related to semantic index. +- Setting: `semantic_index` +- Default: + +```json +"semantic_index": { + "enabled": false +}, +``` + +### Enabled + +- Description: Whether or not to display the `Semantic` mode in project search. +- Setting: `enabled` +- Default: `true` + +**Options** + +`boolean` values + +## Show Call Status Icon + +- Description: Whether or not to show the call status icon in the status bar. +- Setting: `show_call_status_icon` +- Default: `true` + +**Options** + +`boolean` values + +## Show Completions On Input + +- Description: Whether or not to show completions as you type. +- Setting: `show_completions_on_input` +- Default: `true` + +**Options** + +`boolean` values + +## Show Completion Documentation + +- Description: Whether to display inline and alongside documentation for items in the completions menu. +- Setting: `show_completion_documentation` +- Default: `true` + +**Options** + +`boolean` values + +## Show Copilot Suggestions + +- Description: Whether or not to show Copilot suggestions as you type or wait for a `copilot::Toggle`. +- Setting: `show_copilot_suggestions` +- Default: `true` + +**Options** + +`boolean` values + +## Show Whitespaces + +- Description: Whether or not to show render whitespace characters in the editor. +- Setting: `show_whitespaces` +- Default: `selection` + +**Options** + +1. `all` +2. `selection` +3. `none` + +## Soft Wrap + +- Description: Whether or not to automatically wrap lines of text to fit editor / preferred width. +- Setting: `soft_wrap` +- Default: `none` + +**Options** + +1. `editor_width` +2. `preferred_line_length` +3. `none` + +## Tab Size + +- Description: The number of spaces to use for each tab character. +- Setting: `tab_size` +- Default: `4` + +**Options** + +`integer` values + +## Telemetry + +- Description: Control what info is collected by Zed. +- Setting: `telemetry` +- Default: + +```json +"telemetry": { + "diagnostics": true, + "metrics": true +}, +``` + +**Options** + +### Diagnostics + +- Description: Setting for sending debug-related data, such as crash reports. +- Setting: `diagnostics` +- Default: `true` + +**Options** + +`boolean` values + +### Metrics + +- Description: Setting for sending anonymized usage data, such what languages you're using Zed with. +- Setting: `metrics` +- Default: `true` + +**Options** + +`boolean` values + +## Terminal + +- Description: Configuration for the terminal. +- Setting: `terminal` +- Default: + +```json +"terminal": { + "alternate_scroll": "off", + "blinking": "terminal_controlled", + "copy_on_select": false, + "env": {}, + "font_family": null, + "font_features": null, + "font_size": null, + "option_as_meta": false, + "shell": {}, + "working_directory": "current_project_directory" +} +``` + +### Alternate Scroll + +- Description: Set whether Alternate Scroll mode (DECSET 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 with ANSI escape codes. +- Setting: `alternate_scroll` +- Default: `off` + +**Options** + +1. Default alternate scroll mode to on + +```json +{ + "alternate_scroll": "on" +} +``` + +2. Default alternate scroll mode to off + +```json +{ + "alternate_scroll": "off" +} +``` + +### Blinking + +- Description: Set the cursor blinking behavior in the terminal +- Setting: `blinking` +- Default: `terminal_controlled` + +**Options** + +1. Never blink the cursor, ignore the terminal mode + +```json +{ + "blinking": "off" +} +``` + +2. Default the cursor blink to off, but allow the terminal to turn blinking on + +```json +{ + "blinking": "terminal_controlled" +} +``` + +3. Always blink the cursor, ignore the terminal mode + +```json +"blinking": "on", +``` + +### Copy On Select + +- Description: Whether or not selecting text in the terminal will automatically copy to the system clipboard. +- Setting: `copy_on_select` +- Default: `false` + +**Options** + +`boolean` values + +### Env + +- Description: Any key-value pairs added to this object will be added to the terminal's environment. Keys must be unique, use `:` to separate multiple values in a single variable +- Setting: `env` +- Default: `{}` + +**Example** + +```json +"env": { + "ZED": "1", + "KEY": "value1:value2" +} +``` + +### Font Size + +- Description: What font size to use for the terminal. When not set defaults to matching the editor's font size +- Setting: `font_size` +- Default: `null` + +**Options** + +`integer` values + +### Font Family + +- Description: What font to use for the terminal. When not set, defaults to matching the editor's font. +- Setting: `font_family` +- Default: `null` + +**Options** + +The name of any font family installed on the user's system + +### Font Features + +- Description: What font features to use for the terminal. When not set, defaults to matching the editor's font features. +- Setting: `font_features` +- Default: `null` + +**Options** + +See Buffer Font Features + +### Option As Meta + +- Description: Re-interprets the option keys to act like a 'meta' key, like in Emacs. +- Setting: `option_as_meta` +- Default: `true` + +**Options** + +`boolean` values + +### Shell + +- Description: What shell to use when launching the terminal. +- Setting: `shell` +- Default: `system` + +**Options** + +1. Use the system's default terminal configuration (usually the `/etc/passwd` file). + +```json +{ + "shell": "system" +} +``` + +2. A program to launch: + +```json +"shell": { + "program": "sh" +} +``` + +3. A program with arguments: + +```json +"shell": { + "with_arguments": { + "program": "/bin/bash", + "args": ["--login"] + } +} +``` + +### Working Directory + +- Description: What working directory to use when launching the terminal. +- Setting: `working_directory` +- Default: `"current_project_directory"` + +**Options** + +1. Use the current file's project directory. Will Fallback to the first project directory strategy if unsuccessful + +```json +{ + "working_directory": "current_project_directory" +} +``` + +2. Use the first project in this workspace's directory. Will fallback to using this platform's home directory. + +```json +{ + "working_directory": "first_project_directory" +} +``` + +3. Always use this platform's home directory (if we can find it) + +```json +{ + "working_directory": "always_home" +} +``` + +4. 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. + +```json +"working_directory": { + "always": { + "directory": "~/zed/projects/" + } +} +``` + +## Theme + +- Description: The name of the Zed theme to use for the UI. +- Setting: `theme` +- Default: `One Dark` + +**Options** + +Run the `theme selector: toggle` action in the command palette to see a current list of valid themes names. + +## Vim + +- Description: Whether or not to enable vim mode (work in progress). +- Setting: `vim_mode` +- Default: `false` + +## Project Panel + +- Description: Customise project panel +- Setting: `project_panel` +- Default: + +```json +"project_panel": { + "dock": "left", + "git_status": true, + "default_width": "N/A - width in pixels" +}, +``` + +### Dock + +- Description: Control the position of the dock +- Setting: `dock` +- Default: `left` + +**Options** + +1. Default dock position to left + +```json +{ + "dock": "left" +} +``` + +2. Default dock position to right + +```json +{ + "dock": "right" +} +``` + +### Git Status + +- Description: Indicates newly created and updated files +- Setting: `git_status` +- Default: `true` + +1. Default enable git status + +```json +{ + "git_status": true +} +``` + +2. Default disable git status + +```json +{ + "git_status": false +} +``` + +### Default Width +- Description: Customise default width taken by project panel +- Setting: `default_width` +- Default: N/A width in pixels (eg: 420) + +**Options** + +`boolean` values + +## An example configuration: + +```json +// ~/.config/zed/settings.json +{ + "theme": "cave-light", + "tab_size": 2, + "preferred_line_length": 80, + "soft_wrap": "none", + + "buffer_font_size": 18, + "buffer_font_family": "Zed Mono", + + "autosave": "on_focus_change", + "format_on_save": "off", + "vim_mode": false, + "projects_online_by_default": true, + "terminal": { + "font_family": "FiraCode Nerd Font Mono", + "blinking": "off" + }, + "language_overrides": { + "C": { + "format_on_save": "language_server", + "preferred_line_length": 64, + "soft_wrap": "preferred_line_length" + } + } +} +``` diff --git a/docs/src/configuring_zed__configuring_vim.md b/docs/src/configuring_zed__configuring_vim.md new file mode 100644 index 0000000000000000000000000000000000000000..ddef07e3c28f8e2d2e3452d1e586f57656fbff91 --- /dev/null +++ b/docs/src/configuring_zed__configuring_vim.md @@ -0,0 +1,170 @@ +# Vim Mode + +Zed includes a vim emulation layer known as “vim mode”. This document aims to describe how it works, and how to make the most out of it. + +### Philosophy +Vim mode in Zed is supposed to primarily "do what you expect": it mostly tries to copy vim exactly, but will use Zed-specific functionality when available to make things smoother. + +This means Zed will never be 100% vim compatible, but should be 100% vim familiar! We expect that our vim mode already copes with 90% of your workflow, and we'd like to keep improving it. If you find things that you can’t yet do in vim mode, but which you rely on in your current workflow, please leave feedback in the editor itself (`:feedback`), or [file an issue](https://github.com/zed-industries/community). + +### Zed-specific features +Zed is built on a modern foundation that (among other things) uses tree-sitter to understand the content of the file you're editing, and supports multiple cursors out of the box. + +Vim mode has several "core Zed" key bindings, that will help you make the most of Zed's specific feature set. +``` +# Normal mode +g d Go to definition +g D Go to type definition +c d Rename (change definition) +g A Go to All references to the current word + +g Open the current search excerpt in its own tab + +g s Find symbol in current file +g S Find symbol in entire project + +g n Add a visual selection for the next copy of the current word +g N The same, but backwards +g > Skip latest word selection, and add next. +g < The same, but backwards +g a Add a visual selection for every copy of the current word + +g h Show inline error (hover) + +# Insert mode +ctrl-x ctrl-o Open the completion menu +ctrl-x ctrl-c Request Github Copilot suggestion (if configured) +ctrl-x ctrl-a Open the inline AI assistant (if configured) +ctrl-x ctrl-l Open the LSP code actions +ctrl-x ctrl-z Hides all suggestions +``` + +Vim mode uses Zed to define concepts like "brackets" (for the `%` key) and "words" (for motions like `w` and `e`). This does lead to some differences, but they are mostly positive. For example `%` considers `|` to be a bracket in languages like Rust; and `w` considers `$` to be a word-character in languages like Javascript. + +Vim mode emulates visual block mode using Zed's multiple cursor support. This again leads to some differences, but is much more powerful. + +Finally, Vim mode's search and replace functionality is backed by Zed's. This means that the pattern syntax is slightly different, see the section on [Regex differences](#regex-differences) for details. + +### Custom key bindings +Zed does not yet have an equivalent to vim’s `map` command to convert one set of keystrokes into another, however you can bind any sequence of keys to fire any Action documented in the [Key bindings documentation](https://docs.zed.dev/configuration/key-bindings). + +You can edit your personal key bindings with `:keymap`. +For vim-specific shortcuts, you may find the following template a good place to start: + +```json +[ + { + "context": "Editor && VimControl && !VimWaiting && !menu", + "bindings": { + // put key-bindings here if you want them to work in normal & visual mode + } + }, + { + "context": "Editor && vim_mode == normal && !VimWaiting && !menu", + "bindings": { + // put key-bindings here if you want them to work only in normal mode + } + }, + { + "context": "Editor && vim_mode == visual && !VimWaiting && !menu", + "bindings": { + // visual, visual line & visual block modes + } + }, + { + "context": "Editor && vim_mode == insert && !menu", + "bindings": { + // put key-bindings here if you want them to work in insert mode + } + } +] +``` + +You can see the bindings that are enabled by default in vim mode [here](https://zed.dev/ref/vim.json). + +The details of the context are a little out of scope for this doc, but suffice to say that `menu` is true when a menu is open (e.g. the completions menu), `VimWaiting` is true after you type `f` or `t` when we’re waiting for a new key (and you probably don’t want bindings to happen). Please reach out on [Github](https://github.com/zed-industries/community) if you want help making a key bindings work. + +### Command palette + +Vim mode allows you to enable Zed’s command palette with `:`. This means that you can use vim's command palette to run any action that Zed supports. + +Additionally vim mode contains a number of aliases for popular vim commands to ensure that muscle memory works. For example `:w` will save the file. + +We do not (yet) emulate the full power of vim’s command line, in particular we special case specific patterns instead of using vim's range selection syntax, and we do not support arguments to commands yet. Please reach out on [Github](https://github.com/zed-industries/community) as you find things that are missing from the command palette. + +As mentioned above, one thing to be aware of is that the regex engine is slightly different from vim's in `:%s/a/b`. + +Currently supported vim-specific commands (as of Zed 0.106): +``` +# window management +:w[rite][!], :wq[!], :q[uit][!], :wa[ll][!], :wqa[ll][!], :qa[ll][!], :[e]x[it][!], :up[date] + to save/close tab(s) and pane(s) (no filename is supported yet) +:cq + to quit completely. +:vs[plit], :sp[lit] + to split vertically/horizontally (no filename is supported yet) +:new, :vne[w] + to create a new file in a new pane above or to the left +:tabedit, :tabnew + to create a new file in a new tab. +:tabn[ext], :tabp[rev] + to go to previous/next tabs +:tabc[lose] + to close the current tab + +# navigating diagnostics +:cn[ext], :cp[rev], :ln[ext], :lp[rev] + to go to the next/prev diagnostics +:cc, :ll + to open the errors page + +# jump to position +: + to jump to a line number +:$ + to jump to the end of the file +:/foo and :?foo + to jump to next/prev line matching foo + +# replacement +:%s/foo/bar/ + to replace instances of foo with bar (/g is always assumed, the range must always be %, and Zed uses different regex syntax to vim) + +# editing +:j[oin] + to join the current line (no range is yet supported) +:d[elete][l][p] + to delete the current line (no range is yet supported) +:s[ort] [i] + to sort the current selection (with i, case-insensitively) +``` + + +### Related settings +There are a few Zed settings that you may also enjoy if you use vim mode: +``` +{ + // disable cursor blink + "cursor_blink": false + // use relative line numbers + "relative_line_numbers": true, + // hide the scroll bar + "scrollbar": {"show": "never"}, +} +``` + +### Regex differences + +Zed uses a different regular expression engine from Vim. This means that you will have to use a different syntax for some things. + +Notably: +* Vim uses `\(` and `\)` to represent capture groups, in Zed these are `(` and `)`. +* On the flip side, `(` and `)` represent literal parentheses, but in Zed these must be escaped to `\(` and `\)`. +* When replacing, Vim uses `\0` to represent the entire match, in Zed this is `$0`, same for numbered capture groups `\1` -> `$1`. +* Vim uses `\<` and `\>` to represent word boundaries, in Zed these are both handled by `\b` +* Vim uses `/g` to indicate "all matches on one line", in Zed this is implied +* Vim uses `/i` to indicate "case-insensitive", in Zed you can either use `(?i)` at the start of the pattern or toggle case-sensitivity with `cmd-option-c`. + +To help with the transition, the command palette will fix parentheses and replace groups for you when you run `:%s//`. So `%s:/\(a\)(b)/\1/` will be converted into a search for "(a)\(b\)" and a replacement of "$1". + +For the full syntax supported by Zed's regex engine see the [regex crate documentation](https://docs.rs/regex/latest/regex/#syntax). diff --git a/docs/src/developing_zed__adding_languages.md b/docs/src/developing_zed__adding_languages.md new file mode 100644 index 0000000000000000000000000000000000000000..2917b08422c1e2153a38f5c857a9318a9228c031 --- /dev/null +++ b/docs/src/developing_zed__adding_languages.md @@ -0,0 +1,83 @@ +# Adding New Languages to Zed + +## LSP + +Zed uses the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) to provide language support. This means, in theory, we can support any language that has an LSP server. + +## Syntax Highlighting + +### Defining syntax highlighting rules + +We use tree-sitter queries to match certian properties to highlight. + +#### Simple Example: + +```scheme +(property_identifier) @property +``` + +```ts +const font: FontFamily = { + weight: "normal", + underline: false, + italic: false, +} +``` + +Match a property identifier and highlight it using the identifier `@property`. In the above example, `weight`, `underline`, and `italic` would be highlighted. + +#### Complex example: + +```scheme +(_ + return_type: (type_annotation + [ + (type_identifier) @type.return + (generic_type + name: (type_identifier) @type.return) + ])) +``` + +```ts +function buildDefaultSyntax(colorScheme: Theme): Partial { + // ... +} +``` + +Match a function return type, and highlight the type using the identifier `@type.return`. In the above example, `Partial` would be highlighted. + +#### Example - Typescript + +Here is an example portion of our `highlights.scm` for TypeScript: + +```scheme +; crates/zed/src/languages/typescript/highlights.scm + +; Variables + +(identifier) @variable + +; Properties + +(property_identifier) @property + +; Function and method calls + +(call_expression + function: (identifier) @function) + +(call_expression + function: (member_expression + property: (property_identifier) @function.method)) + +; Function and method definitions + +(function + name: (identifier) @function) +(function_declaration + name: (identifier) @function) +(method_definition + name: (property_identifier) @function.method) + +; ... +``` diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md new file mode 100644 index 0000000000000000000000000000000000000000..cb30051ffa19f551b46d6ddefdbf981077cb9fed --- /dev/null +++ b/docs/src/developing_zed__building_zed.md @@ -0,0 +1,107 @@ +# Building Zed + +🚧 TODO: +- [ ] Tidy up & update instructions +- [ ] Remove ZI-specific things +- [ ] Rework any steps that currently require a ZI-specific account + +How to build Zed from source for the first time. + +## Prerequisites + +- Be added to the GitHub organization +- Be added to the Vercel team + +## Process + +Expect this to take 30min to an hour! Some of these steps will take quite a while based on your connection speed, and how long your first build will be. + +1. Install the [GitHub CLI](https://cli.github.com/): + - `brew install gh` +1. Clone the `zed` repo + - `gh repo clone zed-industries/zed` +1. Install Xcode from the macOS App Store +1. Install Xcode command line tools + - `xcode-select --install` + - If xcode-select --print-path prints /Library/Developer/CommandLineTools… run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` +1. Install [Postgres](https://postgresapp.com) +1. Install rust/rustup + - `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` +1. Install the wasm toolchain + - `rustup target add wasm32-wasi` +1. Install Livekit & Foreman + - `brew install livekit` + - `brew install foreman` +1. Generate an GitHub API Key + - Go to https://github.com/settings/tokens and Generate new token + - GitHub currently provides two kinds of tokens: + - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected + Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories + - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos + - Keep the token in the browser tab/editor for the next two steps +1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` +1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: + ``` + cd .. + git clone https://github.com/zed-industries/zed.dev + cd zed.dev && npm install + npm install -g vercel + ``` +1. Link your zed.dev project to Vercel + - `vercel link` + - Select the `zed-industries` team. If you don't have this get someone on the team to add you to it. + - Select the `zed.dev` project +1. Run `vercel pull` to pull down the environment variables and project info from Vercel +1. Open Postgres.app +1. From `./path/to/zed/`: + - Run: + - `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap` + - Replace `{yourGithubAPIToken}` with the API token you generated above. + - You don't need to include the GITHUB_TOKEN if you exported it above. + - Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault). + - If you get: + - ```bash + Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! + Please create a new installation in /opt/homebrew using one of the + "Alternative Installs" from: + https://docs.brew.sh/Installation + ``` + - In that case try: + - `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` + - If Homebrew is not in your PATH: + - Replace `{username}` with your home folder name (usually your login name) + - `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/{username}/.zprofile` + - `eval "$(/opt/homebrew/bin/brew shellenv)"` +1. To run the Zed app: + - If you are working on zed: + - `cargo run` + - If you are just using the latest version, but not working on zed: + - `cargo run --release` + - If you need to run the collaboration server locally: + - `script/zed-local` + +## Troubleshooting + +### `error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)` + +- Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` + +### `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` + +### Seeding errors during `script/bootstrap` runs + +``` +seeding database... +thread 'main' panicked at 'failed to deserialize github user from 'https://api.github.com/orgs/zed-industries/teams/staff/members': reqwest::Error { kind: Decode, source: Error("invalid type: map, expected a sequence", line: 1, column: 0) }', crates/collab/src/bin/seed.rs:111:10 +``` + +Wrong permissions for `GITHUB_TOKEN` token used, the token needs to be able to read from private repos. +For Classic GitHub Tokens, that required OAuth scope `repo` (seacrh the scope name above for more details) + +Same command + +`sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer` + +### If you experience errors that mention some dependency is using unstable features + +Try `cargo clean` and `cargo build` diff --git a/docs/local-collaboration.md b/docs/src/developing_zed__local_collaboration.md similarity index 100% rename from docs/local-collaboration.md rename to docs/src/developing_zed__local_collaboration.md diff --git a/docs/src/feedback.md b/docs/src/feedback.md new file mode 100644 index 0000000000000000000000000000000000000000..11ae444079c05d9b2573e4c691ef88848ddd2fd7 --- /dev/null +++ b/docs/src/feedback.md @@ -0,0 +1,29 @@ +# Giving feedback + +### Community repository + +We track our issues at [`zed-industries/community`](https://github.com/zed-industries/community/issues). + +#### Feature requests + +Try to focus on the things that are most critical to you rather than exhaustively listing all features another editor you have used has. + +Command palette: `request feature` + +#### Bug reports + +Try to add as much detail as possible, if it is not obvious to reproduce. Let us know how severe the problem is for you; is the issue more of a minor inconvenience or something that would prevent you from using Zed? + +Command palette: `file bug report` + +### In-app feedback + +Anonymous feedback can be submitted from within Zed via the feedback editor (command palette: `give feedback`). + +### Zed forum + +Use the [community forum](https://github.com/zed-industries/community/discussions) to ask questions and learn from one another. We will be present in the forum and answering questions as well. + +### Email + +If you prefer to write up your thoughts as an email, you can send them to [hi@zed.dev](mailto:hi@zed.dev). diff --git a/docs/src/getting_started.md b/docs/src/getting_started.md new file mode 100644 index 0000000000000000000000000000000000000000..236249d00abf339e0cd291e1b0ec82b5447f8419 --- /dev/null +++ b/docs/src/getting_started.md @@ -0,0 +1,15 @@ +# Getting Started + +Welcome to Zed! We are excited to have you. Here is a jumping-off point to getting started. + +### Download Zed + +You can obtain the release build via the [download page](https://zed.dev/download). After the first manual installation, Zed will periodically check for and install updates automatically for you. + +### Configure Zed + +Use `CMD + ,` to open your custom settings to set things like fonts, formatting settings, per-language settings and more. You can access the default configuration using the `Zed > Settings > Open Default Settings` menu item. See Configuring Zed for all available settings. + +### Set up your key bindings + +You can access the default key binding set using the `Zed > Settings > Open Default Key Bindings` menu item. Use `CMD + K`,`CMD + S` to open your custom keymap to add your own key bindings. See Key Bindings for more info., diff --git a/docs/src/system_requirements.md b/docs/src/system_requirements.md new file mode 100644 index 0000000000000000000000000000000000000000..debf7fa2998b262e9ad07746e3732e433b448fe6 --- /dev/null +++ b/docs/src/system_requirements.md @@ -0,0 +1,13 @@ +# System Requirements + +## macOS + +Supported versions: Catalina (10.15) - Ventura (13.x). + +{% hint style="info" %} +The implementation of our screen sharing feature makes use of [LiveKit](https://livekit.io). The LiveKit SDK requires macOS Catalina (10.15); consequently, in v0.62.4, we dropped support for earlier macOS versions that we were initially supporting. +{% endhint %} + +## Linux, Windows, and Web + +_Not supported at this time. See our_ [_Platform Support_](https://github.com/zed-industries/community/issues/174) _issue._ diff --git a/docs/src/telemetry.md b/docs/src/telemetry.md new file mode 100644 index 0000000000000000000000000000000000000000..834e02770aee030dcbf33c419ba6a8848de65024 --- /dev/null +++ b/docs/src/telemetry.md @@ -0,0 +1,147 @@ +# Telemetry in Zed + +**Up to date with v0.112.0** + +Zed collects anonymous telemetry data to help the team understand how people are using the application and to see what sort of issues they are experiencing. + +## Dataflow + +Telemetry is sent from the application to zed.dev. Data is proxied through our servers to enable us to easily switch analytics services; we never store this data. The data is then sent off to various services: + +- [Datadog](https://www.datadoghq.com): Cloud-monitoring service - stores diagnostic events +- [Clickhouse](https://clickhouse.com): Business Intelligence platform - stores both diagnostic and metric events +- [Metabase](https://www.metabase.com): Dashboards - dashboards built around data pulled from Clickhouse + +## Types of Telemetry + +### Diagnostics + +Diagnostic events include debug information (stack traces) from crash reports. Reports are sent on the first application launch after the crash occurred. We've built dashboards that allow us to visualize the frequency and severity of issues experienced by users. Having these reports sent automatically allows us to begin implementing fixes without the user needing to file a report in our issue tracker. The plots in the dashboards also give us an informal measurement of the stability of Zed. + +When a panic occurs, the following data is sent: + +#### PanicRequest + +- `panic`: The panic data +- `token`: An identifier that is used to authenticate the request on zed.dev + +#### Panic + +- `thread`: The name of the thread that panicked +- `payload`: The panic message +- `location_data`: The location of the panic + - `file` + - `line` +- `backtrace`: The backtrace of the panic +- `app_version`: Zed's app version +- `release_channel`: Zed's release channel + - `stable` + - `preview` + - `dev` +- `os_name`: The name of your operating system +- `os_version`: The version of your operating system +- `architecture`: The architecture of your CPU +- `panicked_on`: The time that the panic occurred +- `installation_id`: An identifier that is unique to each installation of Zed (this differs for stable, preview, and dev builds) +- `session_id`: An identifier that is unique to each Zed session (this differs for each time you open Zed) + +### Metrics + +Zed also collects metric information based on user actions. Metric events are reported over HTTPS, and requests are rate-limited to avoid using significant network bandwidth. All data remains anonymous, and can't be related to specific Zed users. + +The following data is sent: + +#### ClickhouseEventRequestBody + +- `token`: An identifier that is used to authenticate the request on zed.dev +- `installation_id`: An identifier that is unique to each installation of Zed (this differs for stable, preview, and dev builds) +- `session_id`: An identifier that is unique to each Zed session (this differs for each time you open Zed) +- `is_staff`: A boolean that indicates whether the user is a member of the Zed team or not +- `app_version`: Zed's app version +- `os_name`: The name of your operating system +- `os_version`: The version of your operating system +- `architecture`: The architecture of your CPU +- `release_channel`: Zed's release channel + - `stable` + - `preview` + - `dev` +- `events`: A vector of `ClickhouseEventWrapper`s + +#### ClickhouseEventWrapper + +- `signed_in`: A boolean that indicates whether the user is signed in or not +- `event`: An enum, where each variant can be one of the following `ClickhouseEvent` variants: + +#### ClickhouseEvent + +- `editor` + - `operation`: The editor operation that was performed + - `open` + - `save` + - `file_extension`: The extension of the file that was opened or saved + - `vim_mode`: A boolean that indicates whether the user is in vim mode or not + - `copilot_enabled`: A boolean that indicates whether the user has copilot enabled or not + - `copilot_enabled_for_language`: A boolean that indicates whether the user has copilot enabled for the language of the file that was opened or saved + - `milliseconds_since_first_event`: Duration of time between this event's timestamp and the timestamp of the first event in the current batch +- `copilot` + - `suggestion_id`: The ID of the suggestion + - `suggestion_accepted`: A boolean that indicates whether the suggestion was accepted or not + - `file_extension`: The file extension of the file that was opened or saved + - `milliseconds_since_first_event`: Same as above +- `call` + - `operation`: The call operation that was performed + - `accept incoming` + - `decline incoming` + - `disable microphone` + - `disable screen share` + - `enable microphone` + - `enable screen share` + - `hang up` + - `invite` + - `join channel` + - `open channel notes` + - `share project` + - `unshare project` + - `room_id`: The ID of the room + - `channel_id`: The ID of the channel + - `milliseconds_since_first_event`: Same as above +- `assistant` + - `conversation_id`: The ID of the conversation (for panel events only) + - `kind`: An enum with the following variants: + - `panel` + - `inline` + - `model`: The model that was used + - `milliseconds_since_first_event`: Same as above +- `cpu` + - `usage_as_percentage`: The CPU usage + - `core_count`: The number of cores on the CPU + - `milliseconds_since_first_event`: Same as above +- `memory` + - `memory_in_bytes`: The amount of memory used in bytes + - `virtual_memory_in_bytes`: The amount of virtual memory used in bytes + - `milliseconds_since_first_event`: Same as above +- `app` + - `operation`: The app operation that was performed + - `first open` + - `open` + - `close (only in GPUI2-powered Zed)` + - `milliseconds_since_first_event`: Same as above + +You can audit the metrics data that Zed has reported by running the command `zed: open telemetry log` from the command palette, or clicking `Help > View Telemetry Log` in the application menu. + +### Configuring Telemetry Settings + +You have full control over what data is sent out by Zed. To enable or disable some or all telemetry types, open your `settings.json` file via `zed: open settings` from the command palette. Insert and tweak the following: + +```json +"telemetry": { + "diagnostics": false, + "metrics": false +}, +``` + +The telemetry settings can also be configured via the `welcome` screen, which can be invoked via the `workspace: welcome` action in the command palette. + +### Concerns and Questions + +If you have concerns about telemetry, please feel free to open issues in our [community repository](https://github.com/zed-industries/community/issues/new/choose). diff --git a/docs/backend-development.md b/docs_old/backend-development.md similarity index 100% rename from docs/backend-development.md rename to docs_old/backend-development.md diff --git a/docs/building-zed.md b/docs_old/building-zed.md similarity index 100% rename from docs/building-zed.md rename to docs_old/building-zed.md diff --git a/docs/company-and-vision.md b/docs_old/company-and-vision.md similarity index 100% rename from docs/company-and-vision.md rename to docs_old/company-and-vision.md diff --git a/docs/design-tools.md b/docs_old/design-tools.md similarity index 100% rename from docs/design-tools.md rename to docs_old/design-tools.md diff --git a/docs/index.md b/docs_old/index.md similarity index 100% rename from docs/index.md rename to docs_old/index.md diff --git a/docs_old/local-collaboration.md b/docs_old/local-collaboration.md new file mode 100644 index 0000000000000000000000000000000000000000..4c059c0878b4df38a3450a5e4d44787ee10aaf0f --- /dev/null +++ b/docs_old/local-collaboration.md @@ -0,0 +1,22 @@ +# Local Collaboration + +## Setting up the local collaboration server + +### Setting up for the first time? + +1. Make sure you have livekit installed (`brew install livekit`) +1. Install [Postgres](https://postgresapp.com) and run it. +1. Then, from the root of the repo, run `script/bootstrap`. + +### Have a db that is out of date? / Need to migrate? + +1. Make sure you have livekit installed (`brew install livekit`) +1. Try `cd crates/collab && cargo run -- migrate` from the root of the repo. +1. Run `script/seed-db` + +## Testing collab locally + +1. Run `foreman start` from the root of the repo. +1. In another terminal run `script/zed-local -2`. +1. Two copies of Zed will open. Add yourself as a contact in the one that is not you. +1. Start a collaboration session as normal with any open project. diff --git a/docs/release-process.md b/docs_old/release-process.md similarity index 100% rename from docs/release-process.md rename to docs_old/release-process.md diff --git a/docs/theme/generating-theme-types.md b/docs_old/theme/generating-theme-types.md similarity index 100% rename from docs/theme/generating-theme-types.md rename to docs_old/theme/generating-theme-types.md diff --git a/docs/tools.md b/docs_old/tools.md similarity index 100% rename from docs/tools.md rename to docs_old/tools.md diff --git a/docs/zed/syntax-highlighting.md b/docs_old/zed/syntax-highlighting.md similarity index 100% rename from docs/zed/syntax-highlighting.md rename to docs_old/zed/syntax-highlighting.md From 5b3b15e95cb97553377e7cc0661fec145f9dd5a3 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 12 Dec 2023 09:10:12 -0500 Subject: [PATCH 05/61] Futher outline --- docs/src/CONTRIBUTING.md | 3 --- docs/src/SUMMARY.md | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) delete mode 100644 docs/src/CONTRIBUTING.md diff --git a/docs/src/CONTRIBUTING.md b/docs/src/CONTRIBUTING.md deleted file mode 100644 index d48c26844f2ee079fedcbdda70ac83704abb9747..0000000000000000000000000000000000000000 --- a/docs/src/CONTRIBUTING.md +++ /dev/null @@ -1,3 +0,0 @@ -# Contributing - -TBD diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 4c6f5f9421dcb7e6b8517085e0c30374a216632b..ad1cd6332c4a5200a66ff5767db3673abc88f921 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,12 +7,19 @@ - [Settings](./configuring_zed.md) - [Vim Mode](./configuring_zed__configuring_vim.md) -# Developing Zed +# Using Zed +- [Workflows]() +- [Collaboration]() +- [Using AI]() + +# Contributing to Zed +- [How to Contribute]() - [Building from Source](./developing_zed__building_zed.md) - [Local Collaboration](./developing_zed__local_collaboration.md) - [Adding Languages](./developing_zed__adding_languages.md) +- [Adding UI]() + +--- -# Other -- [Code of Conduct](./CODE_OF_CONDUCT.md) -- [Telemetry](./telemetry.md) -- [Contributing](./CONTRIBUTING.md) +[Telemetry](./telemetry.md) +[Code of Conduct](./CODE_OF_CONDUCT.md) From f9e7c796720834ec7c7da0622b5b8a4da34d8afe Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 13 Dec 2023 10:35:03 -0500 Subject: [PATCH 06/61] Add deploy note --- docs/how-to-deploy.md | 8 ++++++++ {docs_old => docs/old}/backend-development.md | 0 {docs_old => docs/old}/building-zed.md | 0 {docs_old => docs/old}/company-and-vision.md | 0 {docs_old => docs/old}/design-tools.md | 0 {docs_old => docs/old}/index.md | 0 {docs_old => docs/old}/local-collaboration.md | 0 {docs_old => docs/old}/release-process.md | 0 {docs_old => docs/old}/theme/generating-theme-types.md | 0 {docs_old => docs/old}/tools.md | 0 {docs_old => docs/old}/zed/syntax-highlighting.md | 0 11 files changed, 8 insertions(+) create mode 100644 docs/how-to-deploy.md rename {docs_old => docs/old}/backend-development.md (100%) rename {docs_old => docs/old}/building-zed.md (100%) rename {docs_old => docs/old}/company-and-vision.md (100%) rename {docs_old => docs/old}/design-tools.md (100%) rename {docs_old => docs/old}/index.md (100%) rename {docs_old => docs/old}/local-collaboration.md (100%) rename {docs_old => docs/old}/release-process.md (100%) rename {docs_old => docs/old}/theme/generating-theme-types.md (100%) rename {docs_old => docs/old}/tools.md (100%) rename {docs_old => docs/old}/zed/syntax-highlighting.md (100%) diff --git a/docs/how-to-deploy.md b/docs/how-to-deploy.md new file mode 100644 index 0000000000000000000000000000000000000000..b1222aac5cddc196add7ea8020f59641df3b39eb --- /dev/null +++ b/docs/how-to-deploy.md @@ -0,0 +1,8 @@ +1. `cd docs` from repo root +1. Install the vercel cli if you haven't already + - `pnpm i -g vercel` +1. `vercel` to deploy if you already have the project linked +1. Otherwise, `vercel login` and `vercel` to link + - Choose Zed Industries as the team, then `zed-app-docs` as the project + +Someone can write a script for this when they have time. diff --git a/docs_old/backend-development.md b/docs/old/backend-development.md similarity index 100% rename from docs_old/backend-development.md rename to docs/old/backend-development.md diff --git a/docs_old/building-zed.md b/docs/old/building-zed.md similarity index 100% rename from docs_old/building-zed.md rename to docs/old/building-zed.md diff --git a/docs_old/company-and-vision.md b/docs/old/company-and-vision.md similarity index 100% rename from docs_old/company-and-vision.md rename to docs/old/company-and-vision.md diff --git a/docs_old/design-tools.md b/docs/old/design-tools.md similarity index 100% rename from docs_old/design-tools.md rename to docs/old/design-tools.md diff --git a/docs_old/index.md b/docs/old/index.md similarity index 100% rename from docs_old/index.md rename to docs/old/index.md diff --git a/docs_old/local-collaboration.md b/docs/old/local-collaboration.md similarity index 100% rename from docs_old/local-collaboration.md rename to docs/old/local-collaboration.md diff --git a/docs_old/release-process.md b/docs/old/release-process.md similarity index 100% rename from docs_old/release-process.md rename to docs/old/release-process.md diff --git a/docs_old/theme/generating-theme-types.md b/docs/old/theme/generating-theme-types.md similarity index 100% rename from docs_old/theme/generating-theme-types.md rename to docs/old/theme/generating-theme-types.md diff --git a/docs_old/tools.md b/docs/old/tools.md similarity index 100% rename from docs_old/tools.md rename to docs/old/tools.md diff --git a/docs_old/zed/syntax-highlighting.md b/docs/old/zed/syntax-highlighting.md similarity index 100% rename from docs_old/zed/syntax-highlighting.md rename to docs/old/zed/syntax-highlighting.md From 70c6660ae4f7d0c2a70218022509bc4c1d77e4f1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 13 Dec 2023 10:35:54 -0500 Subject: [PATCH 07/61] Add note --- docs/how-to-deploy.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to-deploy.md b/docs/how-to-deploy.md index b1222aac5cddc196add7ea8020f59641df3b39eb..c32d3619a6d12657843b43ee9083291faed1c4e1 100644 --- a/docs/how-to-deploy.md +++ b/docs/how-to-deploy.md @@ -1,3 +1,5 @@ +These docs are intendended to replace both docs.zed.dev and introduce people to how to build Zed from source. + 1. `cd docs` from repo root 1. Install the vercel cli if you haven't already - `pnpm i -g vercel` From a874a96e76b8cced2bc0fe5e108f35b813814c7c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 13 Dec 2023 11:44:51 -0500 Subject: [PATCH 08/61] Fix tab bar drop target sizing (#3627) This PR fixes an issue where the tab bar drop target was not receiving any size. The styling isn't 100% correct yet, as the updated background color has a gap around it. Release Notes: - N/A --- crates/workspace2/src/pane.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 20682451596f986e09665882014db6972ed68b19..afd7e665c4503e8492921206964d6a37b795b6f2 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1664,6 +1664,10 @@ impl Pane { ) .child( div() + .min_w_6() + // HACK: This empty child is currently necessary to force the drop traget to appear + // despite us setting a min width above. + .child("") .h_full() .flex_grow() .drag_over::(|bar| { From ab8d0abbc142052779a9968c4d64c2c26d335cab Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 13 Dec 2023 12:11:33 -0500 Subject: [PATCH 09/61] Wire up tooltips on tab bar actions (#3629) This PR wires up the tooltips on the actions in the tab bar. Release Notes: - N/A --- crates/workspace2/src/pane.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index afd7e665c4503e8492921206964d6a37b795b6f2..bcbadc4e532bcde153d4d70fc7044d51a490d44d 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1597,7 +1597,8 @@ impl Pane { let view = cx.view().clone(); move |_, cx| view.update(cx, Self::navigate_backward) }) - .disabled(!self.can_navigate_backward()), + .disabled(!self.can_navigate_backward()) + .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)), ) .start_child( IconButton::new("navigate_forward", Icon::ArrowRight) @@ -1606,7 +1607,8 @@ impl Pane { let view = cx.view().clone(); move |_, cx| view.update(cx, Self::navigate_backward) }) - .disabled(!self.can_navigate_forward()), + .disabled(!self.can_navigate_forward()) + .tooltip(|cx| Tooltip::for_action("Go Forward", &GoForward, cx)), ) .end_child( div() @@ -1625,7 +1627,8 @@ impl Pane { }) .detach(); this.new_item_menu = Some(menu); - })), + })) + .tooltip(|cx| Tooltip::text("New...", cx)), ) .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| { el.child(Self::render_menu_overlay(new_item_menu)) @@ -1649,7 +1652,8 @@ impl Pane { }) .detach(); this.split_item_menu = Some(menu); - })), + })) + .tooltip(|cx| Tooltip::text("Split Pane", cx)), ) .when_some(self.split_item_menu.as_ref(), |el, split_item_menu| { el.child(Self::render_menu_overlay(split_item_menu)) From 48faa171b53e252a07e1f95c10603c9d6dd196e6 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 13 Dec 2023 12:24:10 -0500 Subject: [PATCH 10/61] v0.118.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 033ff8b69c542ea96e5d3cac6d7d8320c4383946..cc4393bffa9e541330e6cf34c19b50b2e64e2a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11882,7 +11882,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.117.0" +version = "0.118.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f665cc36dbdc8f4b0fa8576768efb32388d07374..0c115fb2850a07afc1d26b348c80d9816418c232 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.117.0" +version = "0.118.0" publish = false [lib] From a91a42763f8abd5426059dfb1206e0239deba099 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:56:49 +0100 Subject: [PATCH 11/61] collab_ui: Wire up project picker Co-authored-by: Conrad --- crates/collab_ui2/src/collab_titlebar_item.rs | 111 ++++++++++-------- crates/picker2/src/picker2.rs | 23 ++-- .../recent_projects2/src/recent_projects.rs | 21 +++- 3 files changed, 95 insertions(+), 60 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 2b931f7085b55d868a428453040e6a905b1628dd..f48a78fb1d87dd1d69db7011c70e3c726f48d733 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -2,11 +2,13 @@ use crate::face_pile::FacePile; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use gpui::{ - actions, canvas, div, point, px, rems, AppContext, Div, Element, Hsla, InteractiveElement, - IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled, - Subscription, ViewContext, VisualContext, WeakView, WindowBounds, + actions, canvas, div, overlay, point, px, rems, AppContext, DismissEvent, Div, Element, + FocusableView, Hsla, InteractiveElement, IntoElement, Model, Overlay, ParentElement, Path, + Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, + WeakView, WindowBounds, }; use project::{Project, RepositoryEntry}; +use recent_projects::RecentProjects; use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ @@ -14,7 +16,7 @@ use ui::{ IconButton, IconElement, KeyBinding, Tooltip, }; use util::ResultExt; -use workspace::{notifications::NotifyResultExt, Workspace}; +use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB}; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; @@ -49,7 +51,7 @@ pub struct CollabTitlebarItem { client: Arc, workspace: WeakView, //branch_popover: Option>, - //project_popover: Option>, + project_popover: Option, //user_menu: ViewHandle, _subscriptions: Vec, } @@ -328,7 +330,7 @@ impl CollabTitlebarItem { // menu // }), // branch_popover: None, - // project_popover: None, + project_popover: None, _subscriptions: subscriptions, } } @@ -366,11 +368,27 @@ impl CollabTitlebarItem { let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH); - div().border().border_color(gpui::red()).child( - Button::new("project_name_trigger", name) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| Tooltip::text("Recent Projects", cx)), - ) + div() + .border() + .border_color(gpui::red()) + .child( + Button::new("project_name_trigger", name) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Recent Projects", cx)) + .on_click(cx.listener(|this, _, cx| { + this.toggle_project_menu(&ToggleProjectMenu, cx); + })), + ) + .children(self.project_popover.as_ref().map(|popover| { + overlay().child( + div() + .min_w_56() + .on_mouse_down_out(cx.listener_for(&popover.picker, |picker, _, cx| { + picker.cancel(&Default::default(), cx) + })) + .child(popover.picker.clone()), + ) + })) } pub fn render_project_branch(&self, cx: &mut ViewContext) -> Option { @@ -611,43 +629,40 @@ impl CollabTitlebarItem { // cx.notify(); // } - // pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext) { - // let workspace = self.workspace.clone(); - // if self.project_popover.take().is_none() { - // cx.spawn(|this, mut cx| async move { - // let workspaces = WORKSPACE_DB - // .recent_workspaces_on_disk() - // .await - // .unwrap_or_default() - // .into_iter() - // .map(|(_, location)| location) - // .collect(); - - // let workspace = workspace.clone(); - // this.update(&mut cx, move |this, cx| { - // let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx)); - - // cx.subscribe(&view, |this, _, event, cx| { - // match event { - // PickerEvent::Dismiss => { - // this.project_popover = None; - // } - // } - - // cx.notify(); - // }) - // .detach(); - // cx.focus(&view); - // this.branch_popover.take(); - // this.project_popover = Some(view); - // cx.notify(); - // }) - // .log_err(); - // }) - // .detach(); - // } - // cx.notify(); - // } + pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext) { + let workspace = self.workspace.clone(); + if self.project_popover.take().is_none() { + cx.spawn(|this, mut cx| async move { + let workspaces = WORKSPACE_DB + .recent_workspaces_on_disk() + .await + .unwrap_or_default() + .into_iter() + .map(|(_, location)| location) + .collect(); + + let workspace = workspace.clone(); + this.update(&mut cx, move |this, cx| { + let view = RecentProjects::open_popover(workspace, workspaces, cx); + + cx.subscribe(&view.picker, |this, _, _: &DismissEvent, cx| { + this.project_popover = None; + cx.notify(); + }) + .detach(); + let focus_handle = view.focus_handle(cx); + cx.focus(&focus_handle); + // todo!() + //this.branch_popover.take(); + this.project_popover = Some(view); + cx.notify(); + }) + .log_err(); + }) + .detach(); + } + cx.notify(); + } // fn render_user_menu_button( // &self, diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 98b6ce5ff099326943feb2009f9e7f27a9d43d62..db5eebff5389a1f3bd9f0561a017fcb109ec904e 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,8 +1,8 @@ use editor::Editor; use gpui::{ - div, prelude::*, rems, uniform_list, AnyElement, AppContext, Div, FocusHandle, FocusableView, - MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, - WindowContext, + div, prelude::*, rems, uniform_list, AnyElement, AppContext, DismissEvent, Div, EventEmitter, + FocusHandle, FocusableView, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, + View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; use ui::{prelude::*, v_stack, Color, Divider, Label}; @@ -113,8 +113,9 @@ impl Picker { cx.notify(); } - fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { self.delegate.dismissed(cx); + cx.emit(DismissEvent); } fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { @@ -146,9 +147,15 @@ impl Picker { event: &editor::EditorEvent, cx: &mut ViewContext, ) { - if let editor::EditorEvent::BufferEdited = event { - let query = self.editor.read(cx).text(cx); - self.update_matches(query, cx); + match event { + editor::EditorEvent::BufferEdited => { + let query = self.editor.read(cx).text(cx); + self.update_matches(query, cx); + } + editor::EditorEvent::Blurred => { + self.cancel(&menu::Cancel, cx); + } + _ => {} } } @@ -189,6 +196,8 @@ impl Picker { } } +impl EventEmitter for Picker {} + impl Render for Picker { type Element = Div; diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index e0147836876bf47a431fae5344911abd65d96292..dff6aa12ccc30f43766451d244619159c2a7c8bb 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -23,14 +23,15 @@ pub fn init(cx: &mut AppContext) { cx.observe_new_views(RecentProjects::register).detach(); } +#[derive(Clone)] pub struct RecentProjects { - picker: View>, + pub picker: View>, } impl ModalView for RecentProjects {} impl RecentProjects { - fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext) -> Self { + fn new(delegate: RecentProjectsDelegate, cx: &mut WindowContext<'_>) -> Self { Self { picker: cx.build_view(|cx| Picker::new(delegate, cx)), } @@ -86,6 +87,16 @@ impl RecentProjects { Ok(()) })) } + pub fn open_popover( + workspace: WeakView, + workspaces: Vec, + cx: &mut WindowContext<'_>, + ) -> Self { + Self::new( + RecentProjectsDelegate::new(workspace, workspaces, false), + cx, + ) + } } impl EventEmitter for RecentProjects {} @@ -127,7 +138,7 @@ impl RecentProjectsDelegate { } } } - +impl EventEmitter for RecentProjectsDelegate {} impl PickerDelegate for RecentProjectsDelegate { type ListItem = ListItem; @@ -202,11 +213,11 @@ impl PickerDelegate for RecentProjectsDelegate { .open_workspace_for_paths(workspace_location.paths().as_ref().clone(), cx) }) .detach_and_log_err(cx); - self.dismissed(cx); + cx.emit(DismissEvent); } } - fn dismissed(&mut self, _cx: &mut ViewContext>) {} + fn dismissed(&mut self, _: &mut ViewContext>) {} fn render_match( &self, From 72eef116c965f49ead482b1cc765a580c50cd191 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:58:17 +0100 Subject: [PATCH 12/61] fixup! collab_ui: Wire up project picker --- crates/collab_ui2/src/collab_titlebar_item.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index f48a78fb1d87dd1d69db7011c70e3c726f48d733..3d8fedd06bb363c4efacd4e6458b6e796a366f94 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -3,8 +3,8 @@ use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use gpui::{ actions, canvas, div, overlay, point, px, rems, AppContext, DismissEvent, Div, Element, - FocusableView, Hsla, InteractiveElement, IntoElement, Model, Overlay, ParentElement, Path, - Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, + FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, + Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView, WindowBounds, }; use project::{Project, RepositoryEntry}; From 3094cb749ed1691a230b4bc037bd5e9676164c18 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 13 Dec 2023 14:12:59 -0700 Subject: [PATCH 13/61] Implement user menu --- Cargo.lock | 2 + crates/collab_ui2/Cargo.toml | 4 +- crates/collab_ui2/src/collab_titlebar_item.rs | 60 ++++++++++++++----- crates/zed2/src/zed2.rs | 3 +- crates/zed_actions2/src/lib.rs | 2 +- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba868ebbae5a95b5faf4b90918a6d53f33e978d8..a6cc38ef6a8f22cd705aa0a8fd9d0283fb796fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1957,6 +1957,7 @@ dependencies = [ "db2", "editor2", "feature_flags2", + "feedback2", "futures 0.3.28", "fuzzy2", "gpui2", @@ -1978,6 +1979,7 @@ dependencies = [ "settings2", "smallvec", "theme2", + "theme_selector2", "time", "tree-sitter-markdown", "ui2", diff --git a/crates/collab_ui2/Cargo.toml b/crates/collab_ui2/Cargo.toml index 65aced8e7edf1e522a97d44cd4dcbdf04536f348..88c27bc1657df28cb6ecbef07fbf856353a7d516 100644 --- a/crates/collab_ui2/Cargo.toml +++ b/crates/collab_ui2/Cargo.toml @@ -32,7 +32,7 @@ collections = { path = "../collections" } # context_menu = { path = "../context_menu" } # drag_and_drop = { path = "../drag_and_drop" } editor = { package="editor2", path = "../editor2" } -#feedback = { path = "../feedback" } +feedback = { package = "feedback2", path = "../feedback2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } @@ -46,7 +46,7 @@ rpc = { package ="rpc2", path = "../rpc2" } settings = { package = "settings2", path = "../settings2" } feature_flags = { package = "feature_flags2", path = "../feature_flags2"} theme = { package = "theme2", path = "../theme2" } -# theme_selector = { path = "../theme_selector" } +theme_selector = { package = "theme_selector2", path = "../theme_selector2" } # vcs_menu = { path = "../vcs_menu" } ui = { package = "ui2", path = "../ui2" } util = { path = "../util" } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 3d8fedd06bb363c4efacd4e6458b6e796a366f94..d6de5a1565d82493c4bdec08544f63ad3370a7e2 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -1,8 +1,8 @@ use crate::face_pile::FacePile; use call::{ActiveCall, ParticipantLocation, Room}; -use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; +use client::{proto::PeerId, Client, ParticipantIndex, SignOut, User, UserStore}; use gpui::{ - actions, canvas, div, overlay, point, px, rems, AppContext, DismissEvent, Div, Element, + actions, canvas, div, overlay, point, px, rems, Action, AppContext, DismissEvent, Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView, WindowBounds, @@ -16,7 +16,7 @@ use ui::{ IconButton, IconElement, KeyBinding, Tooltip, }; use util::ResultExt; -use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB}; +use workspace::{notifications::NotifyResultExt, Feedback, Workspace, WORKSPACE_DB}; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; @@ -239,7 +239,19 @@ impl Render for CollabTitlebarItem { this.child( popover_menu("user-menu") .menu(|cx| { - ContextMenu::build(cx, |menu, _| menu.header("ADADA")) + ContextMenu::build(cx, |menu, _| { + menu.action( + "Settings", + zed_actions::OpenSettings.boxed_clone(), + ) + .action("Theme", theme_selector::Toggle.boxed_clone()) + .separator() + .action( + "Share Feedback", + feedback::GiveFeedback.boxed_clone(), + ) + .action("Sign Out", client::SignOut.boxed_clone()) + }) }) .trigger( ButtonLike::new("user-menu") @@ -259,16 +271,6 @@ impl Render for CollabTitlebarItem { ) .anchor(gpui::AnchorCorner::TopRight), ) - // this.child( - // ButtonLike::new("user-menu") - // .child( - // h_stack().gap_0p5().child(Avatar::data(avatar)).child( - // IconElement::new(Icon::ChevronDown).color(Color::Muted), - // ), - // ) - // .style(ButtonStyle::Subtle) - // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), - // ) } else { this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| { let client = client.clone(); @@ -280,6 +282,36 @@ impl Render for CollabTitlebarItem { }) .detach(); })) + .child( + popover_menu("user-menu") + .menu(|cx| { + ContextMenu::build(cx, |menu, _| { + menu.action( + "Settings", + zed_actions::OpenSettings.boxed_clone(), + ) + .action("Theme", theme_selector::Toggle.boxed_clone()) + .separator() + .action( + "Share Feedback", + feedback::GiveFeedback.boxed_clone(), + ) + }) + }) + .trigger( + ButtonLike::new("user-menu") + .child( + h_stack().gap_0p5().child( + IconElement::new(Icon::ChevronDown) + .color(Color::Muted), + ), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| { + Tooltip::text("Toggle User Menu", cx) + }), + ), + ) } })), ) diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 611cf645528039f7cff2a85412a79fce984f7146..45ef08fdcc1b9fafb03d51a850174f5db4fb24e4 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -41,7 +41,7 @@ use workspace::{ notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile, NewWindow, Workspace, WorkspaceSettings, }; -use zed_actions::{OpenBrowser, OpenZedURL, Quit}; +use zed_actions::{OpenBrowser, OpenSettings, OpenZedURL, Quit}; actions!( zed, @@ -59,7 +59,6 @@ actions!( OpenLicenses, OpenLocalSettings, OpenLog, - OpenSettings, OpenTelemetryLog, ResetBufferFontSize, ResetDatabase, diff --git a/crates/zed_actions2/src/lib.rs b/crates/zed_actions2/src/lib.rs index fa1a4a5ea961ce77ecd5d199a182c88c17166bbe..badf76a6e7b4bc84a81184b81fca33c413b61bfa 100644 --- a/crates/zed_actions2/src/lib.rs +++ b/crates/zed_actions2/src/lib.rs @@ -22,4 +22,4 @@ pub struct OpenZedURL { impl_actions!(zed, [OpenBrowser, OpenZedURL]); -actions!(zed, [Quit]); +actions!(zed, [OpenSettings, Quit]); From 26a31b41b96d56815c581b587d381d632e9cf85b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 13 Dec 2023 13:02:19 -0800 Subject: [PATCH 14/61] frame time --- crates/gpui2/src/window.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 77eb4e27be20bd734c27258a00fd05eaeff35caf..a0db7d6d9e8d26f6c7cc848860e3779d66ed0669 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1236,6 +1236,8 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { + let t0 = std::time::Instant::now(); + let window_was_focused = self .window .focus @@ -1326,6 +1328,7 @@ impl<'a> WindowContext<'a> { } self.window.dirty = false; + eprintln!("frame: {:?}", t0.elapsed()); scene } From 7899833367ea3971dd87b014aec4e962d3ffce23 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 13 Dec 2023 15:44:27 -0700 Subject: [PATCH 15/61] Don't hang the app when signing in offline --- Cargo.lock | 1 + crates/auto_update2/src/auto_update.rs | 4 +- crates/client2/src/client2.rs | 177 ++++---- crates/collab_ui2/Cargo.toml | 2 +- crates/collab_ui2/src/collab_titlebar_item.rs | 390 ++++++------------ crates/ui2/src/components/icon.rs | 2 + 6 files changed, 229 insertions(+), 347 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6cc38ef6a8f22cd705aa0a8fd9d0283fb796fbc..3b0a8e57fdb63690eed560f4bedd97d9cd1b10a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,6 +1949,7 @@ name = "collab_ui2" version = "0.1.0" dependencies = [ "anyhow", + "auto_update2", "call2", "channel2", "client2", diff --git a/crates/auto_update2/src/auto_update.rs b/crates/auto_update2/src/auto_update.rs index 31e474242ac7fd9cf918a785b35620ae7764df0e..e1e1de0da4f8a343d3b9b50aa5790e0e970f9d22 100644 --- a/crates/auto_update2/src/auto_update.rs +++ b/crates/auto_update2/src/auto_update.rs @@ -6,7 +6,7 @@ use db::kvp::KEY_VALUE_STORE; use db::RELEASE_CHANNEL; use gpui::{ actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task, - ViewContext, VisualContext, + ViewContext, VisualContext, WindowContext, }; use isahc::AsyncBody; use serde::Deserialize; @@ -125,7 +125,7 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo } } -pub fn check(_: &Check, cx: &mut ViewContext) { +pub fn check(_: &Check, cx: &mut WindowContext) { if let Some(updater) = AutoUpdater::get(cx) { updater.update(cx, |updater, cx| updater.poll(cx)); } else { diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index bf8d5dda771c3d9ddea8a3b8b3978318f051fdc3..2c9787298944cba3e49134ccb215768707f4c803 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -11,8 +11,8 @@ use async_tungstenite::tungstenite::{ http::{Request, StatusCode}, }; use futures::{ - future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, - TryStreamExt, + channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, + TryFutureExt as _, TryStreamExt, }; use gpui::{ actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, @@ -1020,91 +1020,116 @@ impl Client { ) -> Task> { let http = self.http.clone(); cx.spawn(|cx| async move { - // Generate a pair of asymmetric encryption keys. The public key will be used by the - // zed server to encrypt the user's access token, so that it can'be intercepted by - // any other app running on the user's device. - let (public_key, private_key) = - rpc::auth::keypair().expect("failed to generate keypair for auth"); - let public_key_string = - String::try_from(public_key).expect("failed to serialize public key for auth"); - - if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) { - return Self::authenticate_as_admin(http, login.clone(), token.clone()).await; - } - - // Start an HTTP server to receive the redirect from Zed's sign-in page. - let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port"); - let port = server.server_addr().port(); + let background = cx.background_executor().clone(); - // Open the Zed sign-in page in the user's browser, with query parameters that indicate - // that the user is signing in from a Zed app running on the same device. - let mut url = format!( - "{}/native_app_signin?native_app_port={}&native_app_public_key={}", - *ZED_SERVER_URL, port, public_key_string - ); + let (open_url_tx, open_url_rx) = oneshot::channel::(); + cx.update(|cx| { + cx.spawn(move |cx| async move { + let url = open_url_rx.await?; + cx.update(|cx| cx.open_url(&url)) + }) + .detach_and_log_err(cx); + }) + .log_err(); + + let credentials = background + .clone() + .spawn(async move { + // Generate a pair of asymmetric encryption keys. The public key will be used by the + // zed server to encrypt the user's access token, so that it can'be intercepted by + // any other app running on the user's device. + let (public_key, private_key) = + rpc::auth::keypair().expect("failed to generate keypair for auth"); + let public_key_string = String::try_from(public_key) + .expect("failed to serialize public key for auth"); + + if let Some((login, token)) = + IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) + { + return Self::authenticate_as_admin(http, login.clone(), token.clone()) + .await; + } - if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() { - log::info!("impersonating user @{}", impersonate_login); - write!(&mut url, "&impersonate={}", impersonate_login).unwrap(); - } + // Start an HTTP server to receive the redirect from Zed's sign-in page. + let server = + tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port"); + let port = server.server_addr().port(); + + // Open the Zed sign-in page in the user's browser, with query parameters that indicate + // that the user is signing in from a Zed app running on the same device. + let mut url = format!( + "{}/native_app_signin?native_app_port={}&native_app_public_key={}", + *ZED_SERVER_URL, port, public_key_string + ); + + if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() { + log::info!("impersonating user @{}", impersonate_login); + write!(&mut url, "&impersonate={}", impersonate_login).unwrap(); + } - cx.update(|cx| cx.open_url(&url))?; - - // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted - // access token from the query params. - // - // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a - // custom URL scheme instead of this local HTTP server. - let (user_id, access_token) = cx - .spawn(|_| async move { - for _ in 0..100 { - if let Some(req) = server.recv_timeout(Duration::from_secs(1))? { - let path = req.url(); - let mut user_id = None; - let mut access_token = None; - let url = Url::parse(&format!("http://example.com{}", path)) - .context("failed to parse login notification url")?; - for (key, value) in url.query_pairs() { - if key == "access_token" { - access_token = Some(value.to_string()); - } else if key == "user_id" { - user_id = Some(value.to_string()); + open_url_tx.send(url).log_err(); + + // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted + // access token from the query params. + // + // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a + // custom URL scheme instead of this local HTTP server. + let (user_id, access_token) = background + .spawn(async move { + for _ in 0..100 { + if let Some(req) = server.recv_timeout(Duration::from_secs(1))? { + let path = req.url(); + let mut user_id = None; + let mut access_token = None; + let url = Url::parse(&format!("http://example.com{}", path)) + .context("failed to parse login notification url")?; + for (key, value) in url.query_pairs() { + if key == "access_token" { + access_token = Some(value.to_string()); + } else if key == "user_id" { + user_id = Some(value.to_string()); + } + } + + let post_auth_url = + format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL); + req.respond( + tiny_http::Response::empty(302).with_header( + tiny_http::Header::from_bytes( + &b"Location"[..], + post_auth_url.as_bytes(), + ) + .unwrap(), + ), + ) + .context("failed to respond to login http request")?; + return Ok(( + user_id + .ok_or_else(|| anyhow!("missing user_id parameter"))?, + access_token.ok_or_else(|| { + anyhow!("missing access_token parameter") + })?, + )); } } - let post_auth_url = - format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL); - req.respond( - tiny_http::Response::empty(302).with_header( - tiny_http::Header::from_bytes( - &b"Location"[..], - post_auth_url.as_bytes(), - ) - .unwrap(), - ), - ) - .context("failed to respond to login http request")?; - return Ok(( - user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?, - access_token - .ok_or_else(|| anyhow!("missing access_token parameter"))?, - )); - } - } + Err(anyhow!("didn't receive login redirect")) + }) + .await?; - Err(anyhow!("didn't receive login redirect")) + let access_token = private_key + .decrypt_string(&access_token) + .context("failed to decrypt access token")?; + + Ok(Credentials { + user_id: user_id.parse()?, + access_token, + }) }) .await?; - let access_token = private_key - .decrypt_string(&access_token) - .context("failed to decrypt access token")?; cx.update(|cx| cx.activate(true))?; - - Ok(Credentials { - user_id: user_id.parse()?, - access_token, - }) + Ok(credentials) }) } diff --git a/crates/collab_ui2/Cargo.toml b/crates/collab_ui2/Cargo.toml index 88c27bc1657df28cb6ecbef07fbf856353a7d516..6249e9fdaff495c1d36e6dc56a35c718a53b3bd0 100644 --- a/crates/collab_ui2/Cargo.toml +++ b/crates/collab_ui2/Cargo.toml @@ -22,7 +22,7 @@ test-support = [ ] [dependencies] -# auto_update = { path = "../auto_update" } +auto_update = { package = "auto_update2", path = "../auto_update2" } db = { package = "db2", path = "../db2" } call = { package = "call2", path = "../call2" } client = { package = "client2", path = "../client2" } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index d6de5a1565d82493c4bdec08544f63ad3370a7e2..276455151e0a203edfae2a825b884247c8138015 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -1,10 +1,11 @@ use crate::face_pile::FacePile; +use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; -use client::{proto::PeerId, Client, ParticipantIndex, SignOut, User, UserStore}; +use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use gpui::{ - actions, canvas, div, overlay, point, px, rems, Action, AppContext, DismissEvent, Div, Element, - FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, - Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, + actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent, + Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, + Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView, WindowBounds, }; use project::{Project, RepositoryEntry}; @@ -16,7 +17,7 @@ use ui::{ IconButton, IconElement, KeyBinding, Tooltip, }; use util::ResultExt; -use workspace::{notifications::NotifyResultExt, Feedback, Workspace, WORKSPACE_DB}; +use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB}; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; @@ -52,7 +53,6 @@ pub struct CollabTitlebarItem { workspace: WeakView, //branch_popover: Option>, project_popover: Option, - //user_menu: ViewHandle, _subscriptions: Vec, } @@ -232,88 +232,17 @@ impl Render for CollabTitlebarItem { }), ) }) - .child(h_stack().px_1p5().map(|this| { - if let Some(user) = current_user { - // TODO: Finish implementing user menu popover - // - this.child( - popover_menu("user-menu") - .menu(|cx| { - ContextMenu::build(cx, |menu, _| { - menu.action( - "Settings", - zed_actions::OpenSettings.boxed_clone(), - ) - .action("Theme", theme_selector::Toggle.boxed_clone()) - .separator() - .action( - "Share Feedback", - feedback::GiveFeedback.boxed_clone(), - ) - .action("Sign Out", client::SignOut.boxed_clone()) - }) - }) - .trigger( - ButtonLike::new("user-menu") - .child( - h_stack() - .gap_0p5() - .child(Avatar::new(user.avatar_uri.clone())) - .child( - IconElement::new(Icon::ChevronDown) - .color(Color::Muted), - ), - ) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| { - Tooltip::text("Toggle User Menu", cx) - }), - ) - .anchor(gpui::AnchorCorner::TopRight), - ) + .map(|el| { + let status = self.client.status(); + let status = &*status.borrow(); + if matches!(status, client::Status::Connected { .. }) { + el.child(self.render_user_menu_button(cx)) } else { - this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| { - let client = client.clone(); - cx.spawn(move |mut cx| async move { - client - .authenticate_and_connect(true, &cx) - .await - .notify_async_err(&mut cx); - }) - .detach(); - })) - .child( - popover_menu("user-menu") - .menu(|cx| { - ContextMenu::build(cx, |menu, _| { - menu.action( - "Settings", - zed_actions::OpenSettings.boxed_clone(), - ) - .action("Theme", theme_selector::Toggle.boxed_clone()) - .separator() - .action( - "Share Feedback", - feedback::GiveFeedback.boxed_clone(), - ) - }) - }) - .trigger( - ButtonLike::new("user-menu") - .child( - h_stack().gap_0p5().child( - IconElement::new(Icon::ChevronDown) - .color(Color::Muted), - ), - ) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| { - Tooltip::text("Toggle User Menu", cx) - }), - ), - ) + el.children(self.render_connection_status(status, cx)) + .child(self.render_sign_in_button(cx)) + .child(self.render_user_menu_button(cx)) } - })), + }), ) } } @@ -355,12 +284,6 @@ impl CollabTitlebarItem { project, user_store, client, - // user_menu: cx.add_view(|cx| { - // let view_id = cx.view_id(); - // let mut menu = ContextMenu::new(view_id, cx); - // menu.set_position_mode(OverlayPositionMode::Local); - // menu - // }), // branch_popover: None, project_popover: None, _subscriptions: subscriptions, @@ -535,34 +458,6 @@ impl CollabTitlebarItem { .log_err(); } - // pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext) { - // self.user_menu.update(cx, |user_menu, cx| { - // let items = if let Some(_) = self.user_store.read(cx).current_user() { - // vec![ - // ContextMenuItem::action("Settings", zed_actions::OpenSettings), - // ContextMenuItem::action("Theme", theme_selector::Toggle), - // ContextMenuItem::separator(), - // ContextMenuItem::action( - // "Share Feedback", - // feedback::feedback_editor::GiveFeedback, - // ), - // ContextMenuItem::action("Sign Out", SignOut), - // ] - // } else { - // vec![ - // ContextMenuItem::action("Settings", zed_actions::OpenSettings), - // ContextMenuItem::action("Theme", theme_selector::Toggle), - // ContextMenuItem::separator(), - // ContextMenuItem::action( - // "Share Feedback", - // feedback::feedback_editor::GiveFeedback, - // ), - // ] - // }; - // user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx); - // }); - // } - // fn render_branches_popover_host<'a>( // &'a self, // _theme: &'a theme::Titlebar, @@ -696,154 +591,113 @@ impl CollabTitlebarItem { cx.notify(); } - // fn render_user_menu_button( - // &self, - // theme: &Theme, - // avatar: Option>, - // cx: &mut ViewContext, - // ) -> AnyElement { - // let tooltip = theme.tooltip.clone(); - // let user_menu_button_style = if avatar.is_some() { - // &theme.titlebar.user_menu.user_menu_button_online - // } else { - // &theme.titlebar.user_menu.user_menu_button_offline - // }; - - // let avatar_style = &user_menu_button_style.avatar; - // Stack::new() - // .with_child( - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = user_menu_button_style - // .user_menu - // .inactive_state() - // .style_for(state); - - // let mut dropdown = Flex::row().align_children_center(); - - // if let Some(avatar_img) = avatar { - // dropdown = dropdown.with_child(Self::render_face( - // avatar_img, - // *avatar_style, - // Color::transparent_black(), - // None, - // )); - // }; - - // dropdown - // .with_child( - // Svg::new("icons/caret_down.svg") - // .with_color(user_menu_button_style.icon.color) - // .constrained() - // .with_width(user_menu_button_style.icon.width) - // .contained() - // .into_any(), - // ) - // .aligned() - // .constrained() - // .with_height(style.width) - // .contained() - // .with_style(style.container) - // .into_any() - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_down(MouseButton::Left, move |_, this, cx| { - // this.user_menu.update(cx, |menu, _| menu.delay_cancel()); - // }) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.toggle_user_menu(&Default::default(), cx) - // }) - // .with_tooltip::( - // 0, - // "Toggle User Menu".to_owned(), - // Some(Box::new(ToggleUserMenu)), - // tooltip, - // cx, - // ) - // .contained(), - // ) - // .with_child( - // ChildView::new(&self.user_menu, cx) - // .aligned() - // .bottom() - // .right(), - // ) - // .into_any() - // } - - // fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { - // let titlebar = &theme.titlebar; - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = titlebar.sign_in_button.inactive_state().style_for(state); - // Label::new("Sign In", style.text.clone()) - // .contained() - // .with_style(style.container) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, move |_, this, cx| { - // let client = this.client.clone(); - // cx.app_context() - // .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await }) - // .detach_and_log_err(cx); - // }) - // .into_any() - // } + fn render_connection_status( + &self, + status: &client::Status, + cx: &mut ViewContext, + ) -> Option { + match status { + client::Status::ConnectionError + | client::Status::ConnectionLost + | client::Status::Reauthenticating { .. } + | client::Status::Reconnecting { .. } + | client::Status::ReconnectionError { .. } => Some( + div() + .id("disconnected") + .bg(gpui::red()) // todo!() @nate + .child(IconElement::new(Icon::Disconnected)) + .tooltip(|cx| Tooltip::text("Disconnected", cx)) + .into_any_element(), + ), + client::Status::UpgradeRequired => { + let auto_updater = auto_update::AutoUpdater::get(cx); + let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) { + Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate", + Some(AutoUpdateStatus::Installing) + | Some(AutoUpdateStatus::Downloading) + | Some(AutoUpdateStatus::Checking) => "Updating...", + Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { + "Please update Zed to Collaborate" + } + }; + + Some( + div() + .bg(gpui::red()) // todo!() @nate + .child(Button::new("connection-status", label).on_click(|_, cx| { + if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { + if auto_updater.read(cx).status() == AutoUpdateStatus::Updated { + workspace::restart(&Default::default(), cx); + return; + } + } + auto_update::check(&Default::default(), cx); + })) + .into_any_element(), + ) + } + _ => None, + } + } - // fn render_connection_status( - // &self, - // status: &client::Status, - // cx: &mut ViewContext, - // ) -> Option> { - // enum ConnectionStatusButton {} - - // let theme = &theme::current(cx).clone(); - // match status { - // client::Status::ConnectionError - // | client::Status::ConnectionLost - // | client::Status::Reauthenticating { .. } - // | client::Status::Reconnecting { .. } - // | client::Status::ReconnectionError { .. } => Some( - // Svg::new("icons/disconnected.svg") - // .with_color(theme.titlebar.offline_icon.color) - // .constrained() - // .with_width(theme.titlebar.offline_icon.width) - // .aligned() - // .contained() - // .with_style(theme.titlebar.offline_icon.container) - // .into_any(), - // ), - // client::Status::UpgradeRequired => { - // let auto_updater = auto_update::AutoUpdater::get(cx); - // let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) { - // Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate", - // Some(AutoUpdateStatus::Installing) - // | Some(AutoUpdateStatus::Downloading) - // | Some(AutoUpdateStatus::Checking) => "Updating...", - // Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { - // "Please update Zed to Collaborate" - // } - // }; + pub fn render_sign_in_button(&mut self, _: &mut ViewContext) -> Button { + let client = self.client.clone(); + Button::new("sign_in", "Sign in").on_click(move |_, cx| { + let client = client.clone(); + cx.spawn(move |mut cx| async move { + client + .authenticate_and_connect(true, &cx) + .await + .notify_async_err(&mut cx); + }) + .detach(); + }) + } - // Some( - // MouseEventHandler::new::(0, cx, |_, _| { - // Label::new(label, theme.titlebar.outdated_warning.text.clone()) - // .contained() - // .with_style(theme.titlebar.outdated_warning.container) - // .aligned() - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, |_, _, cx| { - // if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { - // if auto_updater.read(cx).status() == AutoUpdateStatus::Updated { - // workspace::restart(&Default::default(), cx); - // return; - // } - // } - // auto_update::check(&Default::default(), cx); - // }) - // .into_any(), - // ) - // } - // _ => None, - // } - // } + pub fn render_user_menu_button(&mut self, cx: &mut ViewContext) -> impl Element { + if let Some(user) = self.user_store.read(cx).current_user() { + popover_menu("user-menu") + .menu(|cx| { + ContextMenu::build(cx, |menu, _| { + menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) + .action("Theme", theme_selector::Toggle.boxed_clone()) + .separator() + .action("Share Feedback", feedback::GiveFeedback.boxed_clone()) + .action("Sign Out", client::SignOut.boxed_clone()) + }) + }) + .trigger( + ButtonLike::new("user-menu") + .child( + h_stack() + .gap_0p5() + .child(Avatar::new(user.avatar_uri.clone())) + .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + ) + .anchor(gpui::AnchorCorner::TopRight) + } else { + popover_menu("user-menu") + .menu(|cx| { + ContextMenu::build(cx, |menu, _| { + menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) + .action("Theme", theme_selector::Toggle.boxed_clone()) + .separator() + .action("Share Feedback", feedback::GiveFeedback.boxed_clone()) + }) + }) + .trigger( + ButtonLike::new("user-menu") + .child( + h_stack() + .gap_0p5() + .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + ) + } + } } diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index b68aa579c026d518f33e9eabc94824270c8104e7..6876630100e95d3ec23252e80787642263783d56 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -50,6 +50,7 @@ pub enum Icon { CopilotError, CopilotDisabled, Dash, + Disconnected, Envelope, ExternalLink, ExclamationTriangle, @@ -129,6 +130,7 @@ impl Icon { Icon::CopilotError => "icons/copilot_error.svg", Icon::CopilotDisabled => "icons/copilot_disabled.svg", Icon::Dash => "icons/dash.svg", + Icon::Disconnected => "icons/disconnected.svg", Icon::Envelope => "icons/feedback.svg", Icon::ExclamationTriangle => "icons/warning.svg", Icon::ExternalLink => "icons/external_link.svg", From 1e4a7e6ef18e765fc30303e2ec7176a61a1d8b72 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 13 Dec 2023 16:01:49 -0700 Subject: [PATCH 16/61] Don't notify when drawing --- crates/gpui2/src/window.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a0db7d6d9e8d26f6c7cc848860e3779d66ed0669..d0056617edea93099a40e2f1df30921eae867497 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -237,6 +237,7 @@ pub struct Window { bounds_observers: SubscriberSet<(), AnyObserver>, active: bool, pub(crate) dirty: bool, + pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, pub(crate) last_blur: Option>, pub(crate) focus: Option, @@ -371,6 +372,7 @@ impl Window { bounds_observers: SubscriberSet::new(), active: false, dirty: false, + drawing: false, activation_observers: SubscriberSet::new(), last_blur: None, focus: None, @@ -422,7 +424,9 @@ impl<'a> WindowContext<'a> { /// Mark the window as dirty, scheduling it to be redrawn on the next frame. pub fn notify(&mut self) { - self.window.dirty = true; + if !self.window.drawing { + self.window.dirty = true; + } } /// Close this window. @@ -1237,6 +1241,8 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { let t0 = std::time::Instant::now(); + self.window.dirty = false; + self.window.drawing = true; let window_was_focused = self .window @@ -1327,7 +1333,7 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } - self.window.dirty = false; + self.window.drawing = false; eprintln!("frame: {:?}", t0.elapsed()); scene @@ -2346,10 +2352,12 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn notify(&mut self) { - self.window_cx.notify(); - self.window_cx.app.push_effect(Effect::Notify { - emitter: self.view.model.entity_id, - }); + if !self.window.drawing { + self.window_cx.notify(); + self.window_cx.app.push_effect(Effect::Notify { + emitter: self.view.model.entity_id, + }); + } } pub fn observe_window_bounds( From c863227dc2f84a7bd6285963d8377b3772c70322 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 13 Dec 2023 18:44:21 -0700 Subject: [PATCH 17/61] Log frame timings --- crates/gpui2/src/platform/mac/metal_renderer.rs | 6 ++++++ crates/gpui2/src/view.rs | 6 ++++++ crates/gpui2/src/window.rs | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index c477440df5d888658e96c8e9d4d3f7e70d214679..3210a53c634e81e5410e732c2971c86a553c808f 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -187,6 +187,8 @@ impl MetalRenderer { } pub fn draw(&mut self, scene: &Scene) { + let start = std::time::Instant::now(); + let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size = size( @@ -303,6 +305,10 @@ impl MetalRenderer { command_buffer.commit(); self.sprite_atlas.clear_textures(AtlasTextureKind::Path); + + let duration_since_start = start.elapsed(); + println!("renderer draw: {:?}", duration_since_start); + command_buffer.wait_until_completed(); drawable.present(); } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 280c52df2afad19af029a75e336222eae82aa74e..46a90864783b0e8933731a275704cd1ce9dd4d32 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -209,8 +209,14 @@ impl AnyView { ) { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, rendered_element) = (self.layout)(self, cx); + let start_time = std::time::Instant::now(); cx.compute_layout(layout_id, available_space); + let duration = start_time.elapsed(); + println!("compute layout: {:?}", duration); + let start_time = std::time::Instant::now(); (self.paint)(self, rendered_element, cx); + let duration = start_time.elapsed(); + println!("paint: {:?}", duration); }) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index d0056617edea93099a40e2f1df30921eae867497..4624bcf20bbc1829c0f6d0cde24c1948d88d1ce0 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1334,7 +1334,7 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; - eprintln!("frame: {:?}", t0.elapsed()); + eprintln!("window draw: {:?}", t0.elapsed()); scene } From cd08d349a5410970b919bb0d3f0f57182ee649c2 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 13 Dec 2023 21:48:05 -0500 Subject: [PATCH 18/61] Quick and dirty attempt to immediately apply focus change in tests Doesn't quite work yet --- crates/gpui2/src/app.rs | 4 +++- crates/gpui2/src/window.rs | 22 ++++++++++++++++++---- crates/project_panel2/src/project_panel.rs | 1 + 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 9b9a5921e17dca7fa3c22571ca6f6f0fdc9e5c55..deb03498bc7d1783aac36acdd8bb88428d7c5c64 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -571,6 +571,7 @@ impl AppContext { loop { self.release_dropped_entities(); self.release_dropped_focus_handles(); + if let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify { emitter } => { @@ -610,7 +611,8 @@ impl AppContext { .values() .filter_map(|window| { let window = window.as_ref()?; - window.dirty.then_some(window.handle) + dbg!(window.focus_invalidated); + (window.dirty || window.focus_invalidated).then_some(window.handle) }) .collect::>() { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index d2f48917112f5eda16c8a9a8a684bf47b036da09..895ae788627aa7d608762cef2ca0c4b6b4bbd15e 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -243,6 +243,9 @@ pub struct Window { pub(crate) dirty: bool, activation_observers: SubscriberSet<(), AnyObserver>, pub(crate) focus: Option, + + #[cfg(any(test, feature = "test-support"))] + pub(crate) focus_invalidated: bool, } pub(crate) struct ElementStateBox { @@ -381,6 +384,9 @@ impl Window { dirty: false, activation_observers: SubscriberSet::new(), focus: None, + + #[cfg(any(test, feature = "test-support"))] + focus_invalidated: false, } } } @@ -461,6 +467,12 @@ impl<'a> WindowContext<'a> { .rendered_frame .dispatch_tree .clear_pending_keystrokes(); + + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = true; + } + self.notify(); } @@ -1274,13 +1286,15 @@ impl<'a> WindowContext<'a> { self.window.root_view = Some(root_view); let previous_focus_path = self.window.rendered_frame.focus_path(); - - let window = &mut self.window; - mem::swap(&mut window.rendered_frame, &mut window.next_frame); - + mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); let current_focus_path = self.window.rendered_frame.focus_path(); if previous_focus_path != current_focus_path { + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = false; + } + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { self.window .blur_listeners diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index adcd21cac6d35f2a00f24938184122d7c7d842c1..5e71266c2177c857d6d5fbd6f34c1d3f59ef68da 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -739,6 +739,7 @@ impl ProjectPanel { }); self.filename_editor.update(cx, |editor, cx| { editor.clear(cx); + println!("focusing"); editor.focus(cx); }); self.update_visible_entries(Some((worktree_id, NEW_ENTRY_ID)), cx); From 6f17cf73370c4482ff7a0ad63bac00ffce45d5b0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 09:25:09 -0700 Subject: [PATCH 19/61] WIP --- crates/gpui2/src/elements/div.rs | 16 +++++++-------- crates/gpui2/src/view.rs | 5 +++++ crates/gpui2/src/window.rs | 34 ++++++++++++++++++++++++-------- debug.plist | 8 ++++++++ 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 debug.plist diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index a102c71a6fbc1d8d0dbc0b2d984efcbdbc677276..5ae051644af2212f28ab4b6aaea8bed4f2789d0b 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -778,28 +778,28 @@ impl Interactivity { }); } - for listener in self.mouse_down_listeners.drain(..) { + for listener in self.mouse_down_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { listener(event, &*interactive_bounds, phase, cx); }) } - for listener in self.mouse_up_listeners.drain(..) { + for listener in self.mouse_up_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { listener(event, &*interactive_bounds, phase, cx); }) } - for listener in self.mouse_move_listeners.drain(..) { + for listener in self.mouse_move_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { listener(event, &*interactive_bounds, phase, cx); }) } - for listener in self.scroll_wheel_listeners.drain(..) { + for listener in self.scroll_wheel_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { listener(event, &*interactive_bounds, phase, cx); @@ -868,8 +868,8 @@ impl Interactivity { } } - let click_listeners = mem::take(&mut self.click_listeners); - let drag_listener = mem::take(&mut self.drag_listener); + let click_listeners = self.click_listeners; + let drag_listener = self.drag_listener; if !click_listeners.is_empty() || drag_listener.is_some() { let pending_mouse_down = element_state.pending_mouse_down.clone(); @@ -1086,13 +1086,13 @@ impl Interactivity { self.key_context.clone(), element_state.focus_handle.clone(), |_, cx| { - for listener in self.key_down_listeners.drain(..) { + for listener in self.key_down_listeners { cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { listener(event, phase, cx); }) } - for listener in self.key_up_listeners.drain(..) { + for listener in self.key_up_listeners { cx.on_key_event(move |event: &KeyUpEvent, phase, cx| { listener(event, phase, cx); }) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 46a90864783b0e8933731a275704cd1ce9dd4d32..d3506e93fa1f4630d620258b77c8995c1c4cc0c3 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -208,11 +208,16 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { + let start_time = std::time::Instant::now(); let (layout_id, rendered_element) = (self.layout)(self, cx); + let duration = start_time.elapsed(); + println!("request layout: {:?}", duration); + let start_time = std::time::Instant::now(); cx.compute_layout(layout_id, available_space); let duration = start_time.elapsed(); println!("compute layout: {:?}", duration); + let start_time = std::time::Instant::now(); (self.paint)(self, rendered_element, cx); let duration = start_time.elapsed(); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 4624bcf20bbc1829c0f6d0cde24c1948d88d1ce0..a6e738db54ea9650417944bec0c15a62e64dc714 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -42,8 +42,26 @@ const ACTIVE_DRAG_Z_INDEX: u32 = 1; /// A global stacking order, which is created by stacking successive z-index values. /// Each z-index will always be interpreted in the context of its parent z-index. -#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)] -pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>); +#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] +pub struct StackingOrder(pub(crate) Arc>); + +impl Default for StackingOrder { + fn default() -> Self { + StackingOrder(Arc::new(Vec::new())) + } +} + +impl StackingOrder { + /// Pushes a new z-index onto the stacking order. + pub fn push(&mut self, z_index: u32) { + Arc::make_mut(&mut self.0).push(z_index); + } + + /// Pops the last z-index off the stacking order. + pub fn pop(&mut self) { + Arc::make_mut(&mut self.0).pop(); + } +} /// Represents the two different phases when dispatching events. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] @@ -2892,12 +2910,12 @@ impl AnyWindowHandle { } } -#[cfg(any(test, feature = "test-support"))] -impl From> for StackingOrder { - fn from(small_vec: SmallVec<[u32; 16]>) -> Self { - StackingOrder(small_vec) - } -} +// #[cfg(any(test, feature = "test-support"))] +// impl From> for StackingOrder { +// fn from(small_vec: SmallVec<[u32; 16]>) -> Self { +// StackingOrder(small_vec) +// } +// } #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { diff --git a/debug.plist b/debug.plist new file mode 100644 index 0000000000000000000000000000000000000000..e09573c9d1c1499f8b1357d3a86bd90d306bded3 --- /dev/null +++ b/debug.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.get-task-allow + + + From 17a80ca09b8ccbb4273d19c89e5cd6c6cf8b3e72 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 14 Dec 2023 09:44:04 -0700 Subject: [PATCH 20/61] Ignore invisible layers for mouse events --- crates/gpui2/src/elements/div.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1954e3086c6b8f2c2b7bd3ea705a76e33c895d6f..6b1dd3e5458ff83347d9e4c1ccfb4a7c878cd6fa 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -890,10 +890,11 @@ impl Interactivity { }); } - if style - .background - .as_ref() - .is_some_and(|fill| fill.color().is_some()) + if style.visibility == Visibility::Visible + && style + .background + .as_ref() + .is_some_and(|fill| fill.color().is_some()) { cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds)) } From c2c7eead8a22baf006bfbb7ae619f5377c87587e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 14 Dec 2023 10:02:54 -0700 Subject: [PATCH 21/61] Robustify checks for visibility --- crates/gpui2/src/elements/div.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 6b1dd3e5458ff83347d9e4c1ccfb4a7c878cd6fa..bb8e76c627fa65916380bec773cf7f69b46fdda5 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -642,10 +642,6 @@ impl Element for Div { &mut element_state.interactive_state, cx, |style, scroll_offset, cx| { - if style.visibility == Visibility::Hidden { - return; - } - let z_index = style.z_index.unwrap_or(0); cx.with_z_index(z_index, |cx| { @@ -779,6 +775,10 @@ impl Interactivity { ) { let style = self.compute_style(Some(bounds), element_state, cx); + if style.visibility == Visibility::Hidden { + return; + } + #[cfg(debug_assertions)] if self.element_id.is_some() && (style.debug || style.debug_below || cx.has_global::()) @@ -890,11 +890,10 @@ impl Interactivity { }); } - if style.visibility == Visibility::Visible - && style - .background - .as_ref() - .is_some_and(|fill| fill.color().is_some()) + if style + .background + .as_ref() + .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent())) { cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds)) } From fd133df896fa465e307052c7d28b62fcc50bd3a2 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 12:13:02 -0500 Subject: [PATCH 22/61] Improve storybook story selection (#3653) This PR builds on top of #3652 by adding a selection prompt to the storybook to allow you to choose from the available list of stories if you don't provide one explicitly: Screenshot 2023-12-14 at 12 00 26 PM This way we don't have to keep generating the `script/storybook` script whenever stories are added/removed. #### Usage (through `cargo`): ```sh # Select from the available stories cargo run -p storybook2 # Run a specific story cargo run -p storybook2 -- components/list_item ``` #### Usage (through `script/storybook`): ```sh # Select from the available stories ./script/storybook # Run a specific story ./script/storybook list_item ``` Release Notes: - N/A --- Cargo.lock | 100 ++++++++++++++---- crates/storybook2/Cargo.toml | 10 +- .../src/bin/print_storybook_script.rs | 74 ------------- crates/storybook2/src/lib.rs | 4 - crates/storybook2/src/{bin => }/storybook2.rs | 25 ++++- script/storybook | 61 +---------- 6 files changed, 106 insertions(+), 168 deletions(-) delete mode 100644 crates/storybook2/src/bin/print_storybook_script.rs delete mode 100644 crates/storybook2/src/lib.rs rename crates/storybook2/src/{bin => }/storybook2.rs (84%) diff --git a/Cargo.lock b/Cargo.lock index 4281553aff5f7100dc91be770eeda88bad9825ac..68919dffbc943d855143e0f9c0688d19b5b9b694 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ "toml 0.7.8", "unicode-width", "vte", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -293,7 +293,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -303,7 +303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -580,7 +580,7 @@ dependencies = [ "futures-lite", "rustix 0.37.23", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2085,6 +2085,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-cstr" version = "0.3.0" @@ -2572,7 +2585,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2781,6 +2794,20 @@ dependencies = [ "workspace2", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "fuzzy-matcher", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" @@ -3026,6 +3053,12 @@ dependencies = [ "serde", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -3104,7 +3137,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3135,7 +3168,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if 1.0.0", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3343,7 +3376,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.3.5", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3722,6 +3755,15 @@ dependencies = [ "util", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "fuzzy2" version = "0.1.0" @@ -4243,7 +4285,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4520,7 +4562,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.3", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4577,7 +4619,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", "rustix 0.38.14", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5000,7 +5042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5493,7 +5535,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -6674,7 +6716,7 @@ dependencies = [ "libc", "log", "pin-project-lite 0.2.13", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7980,7 +8022,7 @@ dependencies = [ "io-lifetimes 1.0.11", "libc", "linux-raw-sys 0.3.8", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7993,7 +8035,7 @@ dependencies = [ "errno 0.3.3", "libc", "linux-raw-sys 0.4.7", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -8106,7 +8148,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -8706,6 +8748,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shellexpand" version = "2.1.2" @@ -8905,7 +8953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -9221,6 +9269,7 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "dialoguer", "editor2", "fuzzy2", "gpui2", @@ -9521,7 +9570,7 @@ dependencies = [ "fastrand 2.0.0", "redox_syscall 0.3.5", "rustix 0.38.14", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -9960,7 +10009,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.4", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -11590,6 +11639,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -11729,7 +11787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 949d07b06f83bc4b18aaa7faa6b6c4ebfa2899d0..d0467df47111ade82c17047dfc7f5a6752ea1ff5 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -3,23 +3,19 @@ name = "storybook2" version = "0.1.0" edition = "2021" publish = false -default-run = "storybook" [[bin]] name = "storybook" -path = "src/bin/storybook2.rs" - -[[bin]] -name = "print_storybook_script" -path = "src/bin/print_storybook_script.rs" +path = "src/storybook2.rs" [dependencies] anyhow.workspace = true # TODO: Remove after diagnosing stack overflow. backtrace-on-stack-overflow = "0.3.0" +chrono = "0.4" clap = { version = "4.4", features = ["derive", "string"] } +dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } editor = { package = "editor2", path = "../editor2" } -chrono = "0.4" fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } itertools = "0.11.0" diff --git a/crates/storybook2/src/bin/print_storybook_script.rs b/crates/storybook2/src/bin/print_storybook_script.rs deleted file mode 100644 index d3409d6121779984a051383ce9ee97842f46625d..0000000000000000000000000000000000000000 --- a/crates/storybook2/src/bin/print_storybook_script.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fs::File; -use std::io::Write; - -use strum::IntoEnumIterator; - -use storybook2::story_selector::ComponentStory; - -// TOOD: Ideally we actually create a more full featured CLI, -// but for the moment I just wanted a easier way to run the stories - -fn main() -> std::io::Result<()> { - let path = std::env::current_dir()?; - let out_file = path.join("script").join("storybook"); - - // the script output file - let mut file = File::create(out_file)?; - - // generate the list of components, in `snake_case` - let components = ComponentStory::iter() - .map(|c| c.to_string()) // Converts enum to string in `snake_case` - .collect::>(); - - // write the bash script - writeln!(file, "#!/bin/bash")?; - writeln!(file, "")?; - writeln!(file, "options=(")?; - for component in &components { - writeln!(file, " \"{}\"", component)?; - } - writeln!(file, ")")?; - writeln!(file, "")?; - - // Check if an argument is provided and if it matches a valid option - writeln!(file, "run_story() {{")?; - writeln!(file, " echo \"Running story: $1\"")?; - writeln!(file, " cargo run -p storybook2 -- \"$1\"")?; - writeln!(file, "}}")?; - writeln!(file, "")?; - - writeln!(file, "if [ \"$#\" -gt 0 ]; then")?; - writeln!(file, " story_arg=\"$1\"")?; - writeln!(file, " # Add prefix 'components/' if not present")?; - writeln!(file, " if [[ $story_arg != components/* ]]; then")?; - writeln!(file, " story_arg=\"components/$story_arg\"")?; - writeln!(file, " fi")?; - writeln!(file, " # Check if the provided story is a valid option")?; - writeln!(file, " for opt in \"${{options[@]}}\"; do")?; - writeln!( - file, - " if [[ \"components/$opt\" == \"$story_arg\" ]]; then" - )?; - writeln!(file, " run_story \"$story_arg\"")?; - writeln!(file, " exit")?; - writeln!(file, " fi")?; - writeln!(file, " done")?; - writeln!(file, " echo \"Invalid story name: $1\"")?; - writeln!(file, " exit 1")?; - writeln!(file, "fi")?; - writeln!(file, "")?; - - // Existing selection prompt - writeln!(file, "prompt=\"Please select a story:\"")?; - writeln!(file, "PS3=\"$prompt \"")?; - writeln!(file, "select story in \"${{options[@]}}\"; do")?; - writeln!(file, " if [[ -n $story ]]; then")?; - writeln!(file, " run_story \"components/$story\"")?; - writeln!(file, " break")?; - writeln!(file, " else")?; - writeln!(file, " echo \"Invalid option\"")?; - writeln!(file, " fi")?; - writeln!(file, "done")?; - - Ok(()) -} diff --git a/crates/storybook2/src/lib.rs b/crates/storybook2/src/lib.rs deleted file mode 100644 index f9c945f06ab73c0b314f78997cb9dafc94624118..0000000000000000000000000000000000000000 --- a/crates/storybook2/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod assets; - -pub mod stories; -pub mod story_selector; diff --git a/crates/storybook2/src/bin/storybook2.rs b/crates/storybook2/src/storybook2.rs similarity index 84% rename from crates/storybook2/src/bin/storybook2.rs rename to crates/storybook2/src/storybook2.rs index 43f91db1a471f01d5d48e47161f6163f84dde62b..9cc2dd51f7c73388679bee01d480ecbac328cec8 100644 --- a/crates/storybook2/src/bin/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -1,6 +1,11 @@ +mod assets; +mod stories; +mod story_selector; + use std::sync::Arc; use clap::Parser; +use dialoguer::FuzzySelect; use gpui::{ div, px, size, AnyView, AppContext, Bounds, Div, Render, ViewContext, VisualContext, WindowBounds, WindowOptions, @@ -8,12 +13,12 @@ use gpui::{ use log::LevelFilter; use settings2::{default_settings, Settings, SettingsStore}; use simplelog::SimpleLogger; +use strum::IntoEnumIterator; use theme2::{ThemeRegistry, ThemeSettings}; use ui::prelude::*; -use storybook2::assets::Assets; -pub use storybook2::story_selector::*; -// pub use crate::story_selector::{ComponentStory, StorySelector}; +use crate::assets::Assets; +use crate::story_selector::{ComponentStory, StorySelector}; // gpui::actions! { // storybook, @@ -40,7 +45,17 @@ fn main() { let args = Args::parse(); - let story_selector = args.story.clone(); + let story_selector = args.story.clone().unwrap_or_else(|| { + let stories = ComponentStory::iter().collect::>(); + + let selection = FuzzySelect::new() + .with_prompt("Choose a story to run:") + .items(&stories) + .interact() + .unwrap(); + + StorySelector::Component(stories[selection]) + }); let theme_name = args.theme.unwrap_or("One Dark".to_string()); let asset_source = Arc::new(Assets); @@ -55,7 +70,7 @@ fn main() { theme2::init(theme2::LoadThemes::All, cx); - let selector = story_selector.unwrap_or(StorySelector::KitchenSink); + let selector = story_selector; let theme_registry = cx.global::(); let mut theme_settings = ThemeSettings::get_global(cx).clone(); diff --git a/script/storybook b/script/storybook index 2978a3d8f46c162a0108f0ef29cb7931ae6afd21..7f5d5e8893c40950f9b49ee7cffafe099f88cb75 100755 --- a/script/storybook +++ b/script/storybook @@ -1,60 +1,7 @@ #!/bin/bash -options=( - "auto_height_editor" - "avatar" - "button" - "checkbox" - "context_menu" - "cursor" - "disclosure" - "focus" - "icon" - "icon_button" - "keybinding" - "label" - "list" - "list_header" - "list_item" - "overflow_scroll" - "scroll" - "tab" - "tab_bar" - "text" - "viewport_units" - "z_index" - "picker" -) - -run_story() { - echo "Running story: $1" - cargo run -p storybook2 -- "$1" -} - -if [ "$#" -gt 0 ]; then - story_arg="$1" - # Add prefix 'components/' if not present - if [[ $story_arg != components/* ]]; then - story_arg="components/$story_arg" - fi - # Check if the provided story is a valid option - for opt in "${options[@]}"; do - if [[ "components/$opt" == "$story_arg" ]]; then - run_story "$story_arg" - exit - fi - done - echo "Invalid story name: $1" - exit 1 +if [ -z "$1" ]; then + cargo run -p storybook2 +else + cargo run -p storybook2 -- "components/$1" fi - -prompt="Please select a story:" -PS3="$prompt " -select story in "${options[@]}"; do - if [[ -n $story ]]; then - run_story "components/$story" - break - else - echo "Invalid option" - fi -done From 1ae25f52a114919abe9062e3ea3506473164345f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 10:31:45 -0700 Subject: [PATCH 23/61] WIP --- crates/collab_ui2/src/face_pile.rs | 2 +- crates/gpui2/src/elements/div.rs | 65 ++++++++++++++++++------ crates/gpui2/src/style.rs | 2 +- crates/gpui2/src/styled.rs | 2 +- crates/gpui2/src/window.rs | 24 +++++---- crates/storybook2/src/stories/z_index.rs | 4 +- crates/ui2/src/styles/elevation.rs | 12 ++--- crates/workspace2/src/modal_layer.rs | 2 +- 8 files changed, 77 insertions(+), 36 deletions(-) diff --git a/crates/collab_ui2/src/face_pile.rs b/crates/collab_ui2/src/face_pile.rs index d181509c46f1e568460b935f6d403dd6b15a1fff..fd675127e4a0786da76f3f5eb044d836652f7fab 100644 --- a/crates/collab_ui2/src/face_pile.rs +++ b/crates/collab_ui2/src/face_pile.rs @@ -17,7 +17,7 @@ impl RenderOnce for FacePile { let isnt_last = ix < player_count - 1; div() - .z_index((player_count - ix) as u32) + .z_index((player_count - ix) as u8) .when(isnt_last, |div| div.neg_mr_1()) .child(player) }); diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 5ae051644af2212f28ab4b6aaea8bed4f2789d0b..cc82ff3cb09dbba6c5b5fff18795f27ef3b8c77a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -646,7 +646,10 @@ pub struct DivState { impl DivState { pub fn is_active(&self) -> bool { - self.interactive_state.pending_mouse_down.borrow().is_some() + self.interactive_state + .pending_mouse_down + .as_ref() + .map_or(false, |pending| pending.borrow().is_some()) } } @@ -872,11 +875,17 @@ impl Interactivity { let drag_listener = self.drag_listener; if !click_listeners.is_empty() || drag_listener.is_some() { - let pending_mouse_down = element_state.pending_mouse_down.clone(); + let pending_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); let mouse_down = pending_mouse_down.borrow().clone(); if let Some(mouse_down) = mouse_down { if let Some(drag_listener) = drag_listener { - let active_state = element_state.clicked_state.clone(); + let active_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .clone(); let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { @@ -929,8 +938,14 @@ impl Interactivity { } if let Some(hover_listener) = self.hover_listener.take() { - let was_hovered = element_state.hover_state.clone(); - let has_mouse_down = element_state.pending_mouse_down.clone(); + let was_hovered = element_state + .hover_state + .get_or_insert_with(Default::default) + .clone(); + let has_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { @@ -951,8 +966,14 @@ impl Interactivity { } if let Some(tooltip_builder) = self.tooltip_builder.take() { - let active_tooltip = element_state.active_tooltip.clone(); - let pending_mouse_down = element_state.pending_mouse_down.clone(); + let active_tooltip = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .clone(); + let pending_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { @@ -994,19 +1015,30 @@ impl Interactivity { } }); - let active_tooltip = element_state.active_tooltip.clone(); + let active_tooltip = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .clone(); cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { active_tooltip.borrow_mut().take(); }); - if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() { + if let Some(active_tooltip) = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .borrow() + .as_ref() + { if active_tooltip.tooltip.is_some() { cx.active_tooltip = active_tooltip.tooltip.clone() } } } - let active_state = element_state.clicked_state.clone(); + let active_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .clone(); if active_state.borrow().is_clicked() { cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Capture { @@ -1180,7 +1212,10 @@ impl Interactivity { } } - let clicked_state = element_state.clicked_state.borrow(); + let clicked_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .borrow(); if clicked_state.group { if let Some(group) = self.group_active_style.as_ref() { style.refine(&group.style) @@ -1235,11 +1270,11 @@ impl Default for Interactivity { #[derive(Default)] pub struct InteractiveElementState { pub focus_handle: Option, - pub clicked_state: Rc>, - pub hover_state: Rc>, - pub pending_mouse_down: Rc>>, + pub clicked_state: Option>>, + pub hover_state: Option>>, + pub pending_mouse_down: Option>>>, pub scroll_offset: Option>>>, - pub active_tooltip: Rc>>, + pub active_tooltip: Option>>>, } pub struct ActiveTooltip { diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 04f247d07627ac6572fe55724f1eb7b29c76463a..d330e73585c5cf31046c782a55c19d570e991194 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -107,7 +107,7 @@ pub struct Style { /// The mouse cursor style shown when the mouse pointer is over an element. pub mouse_cursor: Option, - pub z_index: Option, + pub z_index: Option, } impl Styled for StyleRefinement { diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index a39e5f9cf955883405437347af5220d6dff6ab40..c7312da7ac4bafc3edc800ebe3222f50971581ee 100644 --- a/crates/gpui2/src/styled.rs +++ b/crates/gpui2/src/styled.rs @@ -12,7 +12,7 @@ pub trait Styled: Sized { gpui2_macros::style_helpers!(); - fn z_index(mut self, z_index: u32) -> Self { + fn z_index(mut self, z_index: u8) -> Self { self.style().z_index = Some(z_index); self } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a6e738db54ea9650417944bec0c15a62e64dc714..31e9afc695378b4dfe974173a02ea1a2a371113b 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -38,28 +38,34 @@ use std::{ }; use util::ResultExt; -const ACTIVE_DRAG_Z_INDEX: u32 = 1; +const ACTIVE_DRAG_Z_INDEX: u8 = 1; /// A global stacking order, which is created by stacking successive z-index values. /// Each z-index will always be interpreted in the context of its parent z-index. -#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] -pub struct StackingOrder(pub(crate) Arc>); +#[derive(Deref, DerefMut, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)] +pub struct StackingOrder { + #[deref] + #[deref_mut] + z_indices: Arc>, +} impl Default for StackingOrder { fn default() -> Self { - StackingOrder(Arc::new(Vec::new())) + StackingOrder { + z_indices: Arc::new(SmallVec::new()), + } } } impl StackingOrder { /// Pushes a new z-index onto the stacking order. - pub fn push(&mut self, z_index: u32) { - Arc::make_mut(&mut self.0).push(z_index); + pub fn push(&mut self, z_index: u8) { + Arc::make_mut(&mut self.z_indices).push(z_index); } /// Pops the last z-index off the stacking order. pub fn pop(&mut self) { - Arc::make_mut(&mut self.0).pop(); + Arc::make_mut(&mut self.z_indices).pop(); } } @@ -905,7 +911,7 @@ impl<'a> WindowContext<'a> { /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. - pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + pub fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { self.window.next_frame.z_index_stack.push(z_index); let result = f(self); self.window.next_frame.z_index_stack.pop(); @@ -2233,7 +2239,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } - pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + pub fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { self.window.next_frame.z_index_stack.push(z_index); let result = f(self); self.window.next_frame.z_index_stack.pop(); diff --git a/crates/storybook2/src/stories/z_index.rs b/crates/storybook2/src/stories/z_index.rs index c6a4b68cc369f4e0bf409f714e585c2bde6f47f4..9579b8c7fcf891c4fcda7ef691d37f583b2bd77a 100644 --- a/crates/storybook2/src/stories/z_index.rs +++ b/crates/storybook2/src/stories/z_index.rs @@ -78,7 +78,7 @@ impl Styles for Div {} #[derive(IntoElement)] struct ZIndexExample { - z_index: u32, + z_index: u8, } impl RenderOnce for ZIndexExample { @@ -170,7 +170,7 @@ impl RenderOnce for ZIndexExample { } impl ZIndexExample { - pub fn new(z_index: u32) -> Self { + pub fn new(z_index: u8) -> Self { Self { z_index } } } diff --git a/crates/ui2/src/styles/elevation.rs b/crates/ui2/src/styles/elevation.rs index 88b2ff2e56aeaf5d2e212fca7218afa3940a4742..7b3835c2e54b3e60528ab212311d245dfd7223d3 100644 --- a/crates/ui2/src/styles/elevation.rs +++ b/crates/ui2/src/styles/elevation.rs @@ -20,14 +20,14 @@ pub enum ElevationIndex { } impl ElevationIndex { - pub fn z_index(self) -> u32 { + pub fn z_index(self) -> u8 { match self { ElevationIndex::Background => 0, - ElevationIndex::Surface => 100, - ElevationIndex::ElevatedSurface => 200, - ElevationIndex::Wash => 250, - ElevationIndex::ModalSurface => 300, - ElevationIndex::DraggedElement => 900, + ElevationIndex::Surface => 42, + ElevationIndex::ElevatedSurface => 84, + ElevationIndex::Wash => 126, + ElevationIndex::ModalSurface => 168, + ElevationIndex::DraggedElement => 210, } } diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index a428ba3e18352ec7750f13ecd19289b5682a75be..e0a6395f31a7024eff0cfe3d62a37e8d7f577a2c 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -116,7 +116,7 @@ impl Render for ModalLayer { .size_full() .top_0() .left_0() - .z_index(400) + .z_index(169) .child( v_stack() .h(px(0.0)) From d8cb0e8a2afe5eff4edfe6fd57a4b86f1d823123 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 14 Dec 2023 10:36:16 -0700 Subject: [PATCH 24/61] Fix z-indexes in editor element * Ensure that scroll events from blocks scroll the editor * Ensure that scroll bars show behind hover things --- crates/editor2/src/element.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 2b6db125daed950c21536d876d0b22add51a5ec0..b0341e2d4b637f7a44354a98f01897a1d21352a7 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -1095,7 +1095,7 @@ impl EditorElement { } }); - cx.with_z_index(1, |cx| { + cx.with_z_index(2, |cx| { if let Some((position, mut context_menu)) = layout.context_menu.take() { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); @@ -2825,14 +2825,16 @@ impl Element for EditorElement { self.paint_text(text_bounds, &mut layout, cx); if !layout.blocks.is_empty() { - cx.with_z_index(1, |cx| { + // z-index of 0 so that it is on the same mouse event layer as the editor's + // mouse events + cx.with_z_index(0, |cx| { cx.with_element_id(Some("editor_blocks"), |cx| { self.paint_blocks(bounds, &mut layout, cx); }); }) } - cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); + cx.with_z_index(1, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); }); }); }) From 4e1b4c439047dec2e44f0afa58458defb0b34909 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 14 Dec 2023 10:48:15 -0700 Subject: [PATCH 25/61] Refactor editor to be more clear about stacking --- crates/editor2/src/element.rs | 249 +++++++++++++++++----------------- 1 file changed, 121 insertions(+), 128 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index b0341e2d4b637f7a44354a98f01897a1d21352a7..9b95e256a65e0342a6b4191f8b300b98166232f8 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -1094,121 +1094,118 @@ impl EditorElement { cursor.paint(content_origin, cx); } }); + }, + ) + } - cx.with_z_index(2, |cx| { - if let Some((position, mut context_menu)) = layout.context_menu.take() { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - let context_menu_size = context_menu.measure(available_space, cx); + fn paint_overlays( + &mut self, + text_bounds: Bounds, + layout: &mut LayoutState, + cx: &mut WindowContext, + ) { + let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let start_row = layout.visible_display_row_range.start; + if let Some((position, mut context_menu)) = layout.context_menu.take() { + let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); + let context_menu_size = context_menu.measure(available_space, cx); + + let cursor_row_layout = + &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; + let x = cursor_row_layout.x_for_index(position.column() as usize) + - layout.position_map.scroll_position.x; + let y = (position.row() + 1) as f32 * layout.position_map.line_height + - layout.position_map.scroll_position.y; + let mut list_origin = content_origin + point(x, y); + let list_width = context_menu_size.width; + let list_height = context_menu_size.height; + + // Snap the right edge of the list to the right edge of the window if + // its horizontal bounds overflow. + if list_origin.x + list_width > cx.viewport_size().width { + list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO); + } - let cursor_row_layout = &layout.position_map.line_layouts - [(position.row() - start_row) as usize] - .line; - let x = cursor_row_layout.x_for_index(position.column() as usize) - - layout.position_map.scroll_position.x; - let y = (position.row() + 1) as f32 * layout.position_map.line_height - - layout.position_map.scroll_position.y; - let mut list_origin = content_origin + point(x, y); - let list_width = context_menu_size.width; - let list_height = context_menu_size.height; - - // Snap the right edge of the list to the right edge of the window if - // its horizontal bounds overflow. - if list_origin.x + list_width > cx.viewport_size().width { - list_origin.x = - (cx.viewport_size().width - list_width).max(Pixels::ZERO); - } + if list_origin.y + list_height > text_bounds.lower_right().y { + list_origin.y -= layout.position_map.line_height + list_height; + } - if list_origin.y + list_height > text_bounds.lower_right().y { - list_origin.y -= layout.position_map.line_height + list_height; - } + cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx)); + } - cx.break_content_mask(|cx| { - context_menu.draw(list_origin, available_space, cx) - }); + if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { + let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); + + // This is safe because we check on layout whether the required row is available + let hovered_row_layout = + &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; + + // Minimum required size: Take the first popover, and add 1.5 times the minimum popover + // height. This is the size we will use to decide whether to render popovers above or below + // the hovered line. + let first_size = hover_popovers[0].measure(available_space, cx); + let height_to_reserve = + first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height; + + // Compute Hovered Point + let x = hovered_row_layout.x_for_index(position.column() as usize) + - layout.position_map.scroll_position.x; + let y = position.row() as f32 * layout.position_map.line_height + - layout.position_map.scroll_position.y; + let hovered_point = content_origin + point(x, y); + + if hovered_point.y - height_to_reserve > Pixels::ZERO { + // There is enough space above. Render popovers above the hovered point + let mut current_y = hovered_point.y; + for mut hover_popover in hover_popovers { + let size = hover_popover.measure(available_space, cx); + let mut popover_origin = point(hovered_point.x, current_y - size.height); + + let x_out_of_bounds = + text_bounds.upper_right().x - (popover_origin.x + size.width); + if x_out_of_bounds < Pixels::ZERO { + popover_origin.x = popover_origin.x + x_out_of_bounds; } - if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - - // This is safe because we check on layout whether the required row is available - let hovered_row_layout = &layout.position_map.line_layouts - [(position.row() - start_row) as usize] - .line; - - // Minimum required size: Take the first popover, and add 1.5 times the minimum popover - // height. This is the size we will use to decide whether to render popovers above or below - // the hovered line. - let first_size = hover_popovers[0].measure(available_space, cx); - let height_to_reserve = first_size.height - + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height; - - // Compute Hovered Point - let x = hovered_row_layout.x_for_index(position.column() as usize) - - layout.position_map.scroll_position.x; - let y = position.row() as f32 * layout.position_map.line_height - - layout.position_map.scroll_position.y; - let hovered_point = content_origin + point(x, y); - - if hovered_point.y - height_to_reserve > Pixels::ZERO { - // There is enough space above. Render popovers above the hovered point - let mut current_y = hovered_point.y; - for mut hover_popover in hover_popovers { - let size = hover_popover.measure(available_space, cx); - let mut popover_origin = - point(hovered_point.x, current_y - size.height); - - let x_out_of_bounds = - text_bounds.upper_right().x - (popover_origin.x + size.width); - if x_out_of_bounds < Pixels::ZERO { - popover_origin.x = popover_origin.x + x_out_of_bounds; - } - - cx.break_content_mask(|cx| { - hover_popover.draw(popover_origin, available_space, cx) - }); + cx.break_content_mask(|cx| { + hover_popover.draw(popover_origin, available_space, cx) + }); - current_y = popover_origin.y - HOVER_POPOVER_GAP; - } - } else { - // There is not enough space above. Render popovers below the hovered point - let mut current_y = hovered_point.y + layout.position_map.line_height; - for mut hover_popover in hover_popovers { - let size = hover_popover.measure(available_space, cx); - let mut popover_origin = point(hovered_point.x, current_y); - - let x_out_of_bounds = - text_bounds.upper_right().x - (popover_origin.x + size.width); - if x_out_of_bounds < Pixels::ZERO { - popover_origin.x = popover_origin.x + x_out_of_bounds; - } + current_y = popover_origin.y - HOVER_POPOVER_GAP; + } + } else { + // There is not enough space above. Render popovers below the hovered point + let mut current_y = hovered_point.y + layout.position_map.line_height; + for mut hover_popover in hover_popovers { + let size = hover_popover.measure(available_space, cx); + let mut popover_origin = point(hovered_point.x, current_y); + + let x_out_of_bounds = + text_bounds.upper_right().x - (popover_origin.x + size.width); + if x_out_of_bounds < Pixels::ZERO { + popover_origin.x = popover_origin.x + x_out_of_bounds; + } - hover_popover.draw(popover_origin, available_space, cx); + hover_popover.draw(popover_origin, available_space, cx); - current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; - } - } - } + current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; + } + } + } - if let Some(mouse_context_menu) = - self.editor.read(cx).mouse_context_menu.as_ref() - { - let element = overlay() - .position(mouse_context_menu.position) - .child(mouse_context_menu.context_menu.clone()) - .anchor(AnchorCorner::TopLeft) - .snap_to_window(); - element.draw( - gpui::Point::default(), - size(AvailableSpace::MinContent, AvailableSpace::MinContent), - cx, - |_, _| {}, - ); - } - }) - }, - ) + if let Some(mouse_context_menu) = self.editor.read(cx).mouse_context_menu.as_ref() { + let element = overlay() + .position(mouse_context_menu.position) + .child(mouse_context_menu.context_menu.clone()) + .anchor(AnchorCorner::TopLeft) + .snap_to_window(); + element.draw( + gpui::Point::default(), + size(AvailableSpace::MinContent, AvailableSpace::MinContent), + cx, + |_, _| {}, + ); + } } fn scrollbar_left(&self, bounds: &Bounds) -> Pixels { @@ -2807,34 +2804,30 @@ impl Element for EditorElement { self.register_actions(cx); self.register_key_listeners(cx); - // We call with_z_index to establish a new stacking context. - cx.with_z_index(0, |cx| { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor - // take precedence. - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); - }); - let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); - cx.handle_input(&focus_handle, input_handler); + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); + + cx.with_z_index(0, |cx| { + self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); if !layout.blocks.is_empty() { - // z-index of 0 so that it is on the same mouse event layer as the editor's - // mouse events - cx.with_z_index(0, |cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }); - }) + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); + }); } + }); + + cx.with_z_index(1, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); - cx.with_z_index(1, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); + cx.with_z_index(2, |cx| { + self.paint_overlays(text_bounds, &mut layout, cx); }); }); }) From c6e44683e652decbe2de1196d583846e5e22d7a0 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 13:02:27 -0500 Subject: [PATCH 26/61] Hide the toolbar if it has no visible items (#3654) This PR makes the toolbar hide itself if it has no visible items. This removes the double border beneath the tab bar when there are no visible tools in the toolbar. Release Notes: - N/A --- crates/workspace2/src/toolbar.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index 7971df40ff87ba0ad05ec39d39dff1e96d42659e..b6d7b3e2cbf3d0a25a9513dc5884924262791fff 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -55,6 +55,12 @@ pub struct Toolbar { } impl Toolbar { + fn has_any_visible_items(&self) -> bool { + self.items + .iter() + .any(|(_item, location)| *location != ToolbarItemLocation::Hidden) + } + fn left_items(&self) -> impl Iterator { self.items.iter().filter_map(|(item, location)| { if *location == ToolbarItemLocation::PrimaryLeft { @@ -90,6 +96,10 @@ impl Render for Toolbar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + if !self.has_any_visible_items() { + return div(); + } + let secondary_item = self.secondary_items().next().map(|item| item.to_any()); v_stack() From 8a361c93e2ecf62b063b3c22ef31566d716be881 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 14 Dec 2023 02:06:47 -0500 Subject: [PATCH 27/61] Prep feedback code for testing --- crates/feedback2/src/feedback_modal.rs | 160 ++++++++++++++++++++----- 1 file changed, 132 insertions(+), 28 deletions(-) diff --git a/crates/feedback2/src/feedback_modal.rs b/crates/feedback2/src/feedback_modal.rs index 5f9b6c4120b313dbc930d9b023130091a652d797..3130c4bad6f3a31f0ec89312f6a08c3a990c692a 100644 --- a/crates/feedback2/src/feedback_modal.rs +++ b/crates/feedback2/src/feedback_modal.rs @@ -48,19 +48,31 @@ struct FeedbackRequestBody<'a> { token: &'a str, } -#[derive(PartialEq)] -enum FeedbackModalState { +#[derive(Debug, Clone, PartialEq)] +enum InvalidStateIssue { + EmailAddress, + CharacterCount, +} + +#[derive(Debug, Clone, PartialEq)] +enum CannotSubmitReason { + InvalidState { issues: Vec }, + AwaitingSubmission, +} + +#[derive(Debug, Clone, PartialEq)] +enum SubmissionState { CanSubmit, - DismissModal, - AwaitingSubmissionResponse, + CannotSubmit { reason: CannotSubmitReason }, } pub struct FeedbackModal { system_specs: SystemSpecs, feedback_editor: View, email_address_editor: View, + submission_state: Option, + dismiss_modal: bool, character_count: i32, - state: FeedbackModalState, } impl FocusableView for FeedbackModal { @@ -72,7 +84,7 @@ impl EventEmitter for FeedbackModal {} impl ModalView for FeedbackModal { fn on_before_dismiss(&mut self, cx: &mut ViewContext) -> bool { - if self.state == FeedbackModalState::DismissModal { + if self.dismiss_modal { return true; } @@ -86,7 +98,7 @@ impl ModalView for FeedbackModal { cx.spawn(move |this, mut cx| async move { if answer.await.ok() == Some(0) { this.update(&mut cx, |this, cx| { - this.state = FeedbackModalState::DismissModal; + this.dismiss_modal = true; cx.emit(DismissEvent) }) .log_err(); @@ -179,7 +191,8 @@ impl FeedbackModal { system_specs: system_specs.clone(), feedback_editor, email_address_editor, - state: FeedbackModalState::CanSubmit, + submission_state: None, + dismiss_modal: false, character_count: 0, } } @@ -214,7 +227,9 @@ impl FeedbackModal { }; this.update(&mut cx, |this, cx| { - this.state = FeedbackModalState::AwaitingSubmissionResponse; + this.submission_state = Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::AwaitingSubmission, + }); cx.notify(); }) .log_err(); @@ -225,7 +240,7 @@ impl FeedbackModal { match res { Ok(_) => { this.update(&mut cx, |this, cx| { - this.state = FeedbackModalState::DismissModal; + this.dismiss_modal = true; cx.notify(); cx.emit(DismissEvent) }) @@ -244,7 +259,7 @@ impl FeedbackModal { }) .detach(); - this.state = FeedbackModalState::CanSubmit; + this.submission_state = Some(SubmissionState::CanSubmit); cx.notify(); }) .log_err(); @@ -302,29 +317,79 @@ impl FeedbackModal { Ok(()) } - fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(DismissEvent) - } -} + fn update_submission_state(&mut self, cx: &mut ViewContext) { + if self.awaiting_submission() { + return; + } -impl Render for FeedbackModal { - type Element = Div; + let mut invalid_state_issues = Vec::new(); - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) { Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address), None => true, }; - let valid_character_count = FEEDBACK_CHAR_LIMIT.contains(&self.character_count); + if !valid_email_address { + invalid_state_issues.push(InvalidStateIssue::EmailAddress); + } + + if !FEEDBACK_CHAR_LIMIT.contains(&self.character_count) { + invalid_state_issues.push(InvalidStateIssue::CharacterCount); + } + + if invalid_state_issues.is_empty() { + self.submission_state = Some(SubmissionState::CanSubmit); + } else { + self.submission_state = Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::InvalidState { + issues: invalid_state_issues, + }, + }); + } + } + + fn valid_email_address(&self) -> bool { + !self.in_invalid_state(InvalidStateIssue::EmailAddress) + } - let awaiting_submission_response = - self.state == FeedbackModalState::AwaitingSubmissionResponse; + fn valid_character_count(&self) -> bool { + !self.in_invalid_state(InvalidStateIssue::CharacterCount) + } - let allow_submission = - valid_character_count && valid_email_address && !awaiting_submission_response; + fn in_invalid_state(&self, a: InvalidStateIssue) -> bool { + match self.submission_state { + Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::InvalidState { ref issues }, + }) => issues.contains(&a), + _ => false, + } + } - let submit_button_text = if awaiting_submission_response { + fn awaiting_submission(&self) -> bool { + matches!( + self.submission_state, + Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::AwaitingSubmission + }) + ) + } + + fn can_submit(&self) -> bool { + matches!(self.submission_state, Some(SubmissionState::CanSubmit)) + } + + fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + cx.emit(DismissEvent) + } +} + +impl Render for FeedbackModal { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + self.update_submission_state(cx); + + let submit_button_text = if self.awaiting_submission() { "Submitting..." } else { "Submit" @@ -362,7 +427,7 @@ impl Render for FeedbackModal { *FEEDBACK_CHAR_LIMIT.end() - self.character_count ) }) - .color(if valid_character_count { + .color(if self.valid_character_count() { Color::Success } else { Color::Error @@ -386,7 +451,7 @@ impl Render for FeedbackModal { .p_2() .border() .rounded_md() - .border_color(if valid_email_address { + .border_color(if self.valid_email_address() { cx.theme().colors().border } else { red() @@ -423,7 +488,7 @@ impl Render for FeedbackModal { })), ) .child( - Button::new("send_feedback", submit_button_text) + Button::new("submit_feedback", submit_button_text) .color(Color::Accent) .style(ButtonStyle::Filled) .on_click(cx.listener(|this, _, cx| { @@ -437,7 +502,7 @@ impl Render for FeedbackModal { cx, ) }) - .when(!allow_submission, |this| this.disabled(true)), + .when(!self.can_submit(), |this| this.disabled(true)), ), ), ), @@ -447,3 +512,42 @@ impl Render for FeedbackModal { // TODO: Maybe store email address whenever the modal is closed, versus just on submit, so users can remove it if they want without submitting // TODO: Testing of various button states, dismissal prompts, etc. + +// #[cfg(test)] +// mod test { +// use super::*; + +// #[test] +// fn test_invalid_email_addresses() { +// let markdown = markdown.await.log_err(); +// let buffer = project.update(&mut cx, |project, cx| { +// project.create_buffer("", markdown, cx) +// })??; + +// workspace.update(&mut cx, |workspace, cx| { +// let system_specs = SystemSpecs::new(cx); + +// workspace.toggle_modal(cx, move |cx| { +// let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx); + +// assert!(!feedback_modal.can_submit()); +// assert!(!feedback_modal.valid_email_address(cx)); +// assert!(!feedback_modal.valid_character_count()); + +// feedback_modal +// .email_address_editor +// .update(cx, |this, cx| this.set_text("a", cx)); +// feedback_modal.set_submission_state(cx); + +// assert!(!feedback_modal.valid_email_address(cx)); + +// feedback_modal +// .email_address_editor +// .update(cx, |this, cx| this.set_text("a&b.com", cx)); +// feedback_modal.set_submission_state(cx); + +// assert!(feedback_modal.valid_email_address(cx)); +// }); +// })?; +// } +// } From 0d30b698a493f92b774ae70675f4d5e825c98d65 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 11:28:52 -0700 Subject: [PATCH 28/61] Don't allocate interactive bounds --- crates/gpui2/src/elements/div.rs | 12 ++++++------ crates/gpui2/src/window.rs | 16 ++-------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index cc82ff3cb09dbba6c5b5fff18795f27ef3b8c77a..fa8ef50bbb749a0430887e62139f71438e74b2d3 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -748,10 +748,10 @@ impl Interactivity { cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds)) } - let interactive_bounds = Rc::new(InteractiveBounds { + let interactive_bounds = InteractiveBounds { bounds: bounds.intersect(&cx.content_mask().bounds), stacking_order: cx.stacking_order().clone(), - }); + }; if let Some(mouse_cursor) = style.mouse_cursor { let mouse_position = &cx.mouse_position(); @@ -784,28 +784,28 @@ impl Interactivity { for listener in self.mouse_down_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } for listener in self.mouse_up_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } for listener in self.mouse_move_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } for listener in self.scroll_wheel_listeners { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 31e9afc695378b4dfe974173a02ea1a2a371113b..d43263f815749eb66d55760e935721bb1d781c88 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -46,29 +46,17 @@ const ACTIVE_DRAG_Z_INDEX: u8 = 1; pub struct StackingOrder { #[deref] #[deref_mut] - z_indices: Arc>, + z_indices: SmallVec<[u8; 32]>, } impl Default for StackingOrder { fn default() -> Self { StackingOrder { - z_indices: Arc::new(SmallVec::new()), + z_indices: SmallVec::new(), } } } -impl StackingOrder { - /// Pushes a new z-index onto the stacking order. - pub fn push(&mut self, z_index: u8) { - Arc::make_mut(&mut self.z_indices).push(z_index); - } - - /// Pops the last z-index off the stacking order. - pub fn pop(&mut self) { - Arc::make_mut(&mut self.z_indices).pop(); - } -} - /// Represents the two different phases when dispatching events. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] pub enum DispatchPhase { From 3d1dae9a0610c4b023cd348e01c57001c04d8396 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 11:37:48 -0700 Subject: [PATCH 29/61] Make z_indices bigger in StackingOrder --- crates/gpui2/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index d43263f815749eb66d55760e935721bb1d781c88..e3b1cb4eb523cbdca8df63e96a47024f60958e50 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -46,7 +46,7 @@ const ACTIVE_DRAG_Z_INDEX: u8 = 1; pub struct StackingOrder { #[deref] #[deref_mut] - z_indices: SmallVec<[u8; 32]>, + z_indices: SmallVec<[u8; 64]>, } impl Default for StackingOrder { From ed098c834eb9053a3f1abd7be44486b715a37f3e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 13:58:11 -0500 Subject: [PATCH 30/61] Style inputs in project search (#3655) This PR styles the inputs in the project search. Screenshot 2023-12-14 at 1 53 28 PM Release Notes: - N/A --- crates/search2/src/project_search.rs | 200 ++++++++++++++++----------- 1 file changed, 118 insertions(+), 82 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 09b23c74b0b6bd953c132c133052b837516a8b53..6e71253b1c9f17174aa7a441b668fb4a20f6b162 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -10,11 +10,13 @@ use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; +use editor::{EditorElement, EditorStyle}; use gpui::{ - actions, div, white, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId, - EventEmitter, FocusableView, InteractiveElement, IntoElement, KeyContext, Model, ModelContext, - ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, View, - ViewContext, VisualContext, WeakModel, WeakView, WindowContext, + actions, div, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId, + EventEmitter, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, + KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled, + Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView, + WhiteSpace, WindowContext, }; use menu::Confirm; use project::{ @@ -23,6 +25,7 @@ use project::{ }; use semantic_index::{SemanticIndex, SemanticIndexStatus}; +use settings::Settings; use smol::stream::StreamExt; use std::{ any::{Any, TypeId}, @@ -32,6 +35,7 @@ use std::{ path::PathBuf, time::{Duration, Instant}, }; +use theme::ThemeSettings; use ui::{ h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon, @@ -1425,6 +1429,36 @@ impl ProjectSearchBar { }; new_placeholder_text } + + fn render_text_input(&self, editor: &View, cx: &ViewContext) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if editor.read(cx).read_only() { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + + EditorElement::new( + &editor, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + } } impl Render for ProjectSearchBar { @@ -1445,85 +1479,78 @@ impl Render for ProjectSearchBar { } let search = search.read(cx); let semantic_is_available = SemanticIndex::enabled(cx); - let query_column = v_stack() - //.flex_1() - .child( - h_stack() - .min_w_80() - .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) - .on_action( - cx.listener(|this, action, cx| this.previous_history_query(action, cx)), - ) - .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) - .child(IconElement::new(Icon::MagnifyingGlass)) - .child(search.query_editor.clone()) - .child( - h_stack() - .child( - IconButton::new("project-search-filter-button", Icon::Filter) - .tooltip(|cx| { - Tooltip::for_action("Toggle filters", &ToggleFilters, cx) - }) - .on_click(cx.listener(|this, _, cx| { - this.toggle_filters(cx); - })) - .selected( - self.active_project_search - .as_ref() - .map(|search| search.read(cx).filters_enabled) - .unwrap_or_default(), - ), - ) - .when(search.current_mode != SearchMode::Semantic, |this| { - this.child( - IconButton::new( - "project-search-case-sensitive", - Icon::CaseSensitive, + let query_column = v_stack().child( + h_stack() + .min_w(rems(512. / 16.)) + .px_2() + .py_1() + .gap_2() + .bg(cx.theme().colors().editor_background) + .border_1() + .border_color(cx.theme().colors().border) + .rounded_lg() + .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) + .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) + .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) + .child(IconElement::new(Icon::MagnifyingGlass)) + .child(self.render_text_input(&search.query_editor, cx)) + .child( + h_stack() + .child( + IconButton::new("project-search-filter-button", Icon::Filter) + .tooltip(|cx| { + Tooltip::for_action("Toggle filters", &ToggleFilters, cx) + }) + .on_click(cx.listener(|this, _, cx| { + this.toggle_filters(cx); + })) + .selected( + self.active_project_search + .as_ref() + .map(|search| search.read(cx).filters_enabled) + .unwrap_or_default(), + ), + ) + .when(search.current_mode != SearchMode::Semantic, |this| { + this.child( + IconButton::new( + "project-search-case-sensitive", + Icon::CaseSensitive, + ) + .tooltip(|cx| { + Tooltip::for_action( + "Toggle case sensitive", + &ToggleCaseSensitive, + cx, ) + }) + .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) + .on_click(cx.listener( + |this, _, cx| { + this.toggle_search_option( + SearchOptions::CASE_SENSITIVE, + cx, + ); + }, + )), + ) + .child( + IconButton::new("project-search-whole-word", Icon::WholeWord) .tooltip(|cx| { Tooltip::for_action( - "Toggle case sensitive", - &ToggleCaseSensitive, + "Toggle whole word", + &ToggleWholeWord, cx, ) }) - .selected( - self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx), - ) - .on_click(cx.listener( - |this, _, cx| { - this.toggle_search_option( - SearchOptions::CASE_SENSITIVE, - cx, - ); - }, - )), - ) - .child( - IconButton::new("project-search-whole-word", Icon::WholeWord) - .tooltip(|cx| { - Tooltip::for_action( - "Toggle whole word", - &ToggleWholeWord, - cx, - ) - }) - .selected( - self.is_option_enabled(SearchOptions::WHOLE_WORD, cx), - ) - .on_click(cx.listener(|this, _, cx| { - this.toggle_search_option( - SearchOptions::WHOLE_WORD, - cx, - ); - })), - ) - }), - ) - .border_2() - .bg(white()) - .rounded_lg(), - ); + .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) + .on_click(cx.listener(|this, _, cx| { + this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + })), + ) + }), + ), + ); let mode_column = v_stack().items_start().justify_start().child( h_stack() .child( @@ -1649,6 +1676,7 @@ impl Render for ProjectSearchBar { .key_context(key_context) .p_1() .m_2() + .gap_2() .justify_between() .on_action(cx.listener(|this, _: &ToggleFilters, cx| { this.toggle_filters(cx); @@ -1711,15 +1739,19 @@ impl Render for ProjectSearchBar { .when(search.filters_enabled, |this| { this.child( h_stack() - .mt_2() .flex_1() + .gap_2() .justify_between() .child( h_stack() .flex_1() + .h_full() + .px_2() + .py_1() .border_1() - .mr_2() - .child(search.included_files_editor.clone()) + .border_color(cx.theme().colors().border) + .rounded_lg() + .child(self.render_text_input(&search.included_files_editor, cx)) .when(search.current_mode != SearchMode::Semantic, |this| { this.child( SearchOptions::INCLUDE_IGNORED.as_button( @@ -1739,9 +1771,13 @@ impl Render for ProjectSearchBar { .child( h_stack() .flex_1() + .h_full() + .px_2() + .py_1() .border_1() - .ml_2() - .child(search.excluded_files_editor.clone()), + .border_color(cx.theme().colors().border) + .rounded_lg() + .child(self.render_text_input(&search.excluded_files_editor, cx)), ), ) }) From 2484a6969a96794e5ccc2c3821e20d9acf3bc341 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 15:57:06 -0500 Subject: [PATCH 31/61] Fix toolbar flex sizing --- crates/workspace2/src/toolbar.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index b6d7b3e2cbf3d0a25a9513dc5884924262791fff..a6c8f9d0bff03ec3edd42779b49bdf4382a15a25 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -109,8 +109,22 @@ impl Render for Toolbar { .child( h_stack() .justify_between() - .child(h_stack().children(self.left_items().map(|item| item.to_any()))) - .child(h_stack().children(self.right_items().map(|item| item.to_any()))), + .when(self.left_items().count() > 0, |this| { + this.child( + h_stack() + .flex_1() + .justify_start() + .children(self.left_items().map(|item| item.to_any())), + ) + }) + .when(self.right_items().count() > 0, |this| { + this.child( + h_stack() + .flex_1() + .justify_end() + .children(self.right_items().map(|item| item.to_any())), + ) + }), ) .children(secondary_item) } From c166311c06669839710e2d14e4c2506d8fc1f62d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 15:58:17 -0500 Subject: [PATCH 32/61] Style "Replace in project" input --- crates/search2/src/project_search.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 6e71253b1c9f17174aa7a441b668fb4a20f6b162..059003e909c7ac0ca2b64f78236e4c90d1f8b620 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -1606,12 +1606,16 @@ impl Render for ProjectSearchBar { ); let replace_column = if search.replace_enabled { h_stack() - .p_1() .flex_1() - .border_2() + .h_full() + .gap_2() + .px_2() + .py_1() + .border_1() + .border_color(cx.theme().colors().border) .rounded_lg() .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small)) - .child(search.replacement_editor.clone()) + .child(self.render_text_input(&search.replacement_editor, cx)) } else { // Fill out the space if we don't have a replacement editor. h_stack().flex_1() @@ -1674,10 +1678,10 @@ impl Render for ProjectSearchBar { ]); v_stack() .key_context(key_context) + .flex_grow() .p_1() .m_2() .gap_2() - .justify_between() .on_action(cx.listener(|this, _: &ToggleFilters, cx| { this.toggle_filters(cx); })) @@ -1731,6 +1735,7 @@ impl Render for ProjectSearchBar { }) .child( h_stack() + .justify_between() .child(query_column) .child(mode_column) .child(replace_column) From 0dd6c50a200bdc3b1cad2d48b8f38b85f9f5a458 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 14:06:19 -0700 Subject: [PATCH 33/61] Use FxHashMap for element state --- crates/gpui2/src/window.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e3b1cb4eb523cbdca8df63e96a47024f60958e50..fc8754afefbc7b628603cf9973ee2ee6720f39ff 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -12,7 +12,7 @@ use crate::{ VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::HashMap; +use collections::FxHashMap; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -263,8 +263,8 @@ pub(crate) struct ElementStateBox { // #[derive(Default)] pub(crate) struct Frame { - pub(crate) element_states: HashMap, - mouse_listeners: HashMap>, + pub(crate) element_states: FxHashMap, + mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, @@ -277,8 +277,8 @@ pub(crate) struct Frame { impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { - element_states: HashMap::default(), - mouse_listeners: HashMap::default(), + element_states: FxHashMap::default(), + mouse_listeners: FxHashMap::default(), dispatch_tree, focus_listeners: Vec::new(), scene_builder: SceneBuilder::default(), From 8791f7cefccc84982c2cc4e0516ea44cca0e35a3 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 14 Dec 2023 12:03:17 -0800 Subject: [PATCH 34/61] Enable dragging from project panel to panes Rework gpui2 drag API so that receivers need not specify the dragged view type. co-authored-by: Max co-authored-by: Conrad --- crates/collab_ui2/src/collab_panel.rs | 11 +- crates/gpui2/src/app.rs | 7 +- crates/gpui2/src/elements/div.rs | 89 +++++++----- crates/gpui2/src/window.rs | 3 +- crates/project_panel2/src/project_panel.rs | 37 +++-- crates/terminal_view2/src/terminal_element.rs | 1 - crates/workspace2/src/dock.rs | 4 +- crates/workspace2/src/pane.rs | 134 +++++++++++++----- crates/workspace2/src/workspace2.rs | 4 +- 9 files changed, 190 insertions(+), 100 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 298c7682eb663bdd470e80826de26f30d83ed807..65a994e6d94326e19245cb68120c1fdb35ea335b 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2552,12 +2552,11 @@ impl CollabPanel { .group("") .flex() .w_full() - .on_drag({ - let channel = channel.clone(); - move |cx| { - let channel = channel.clone(); - cx.build_view(|cx| DraggedChannelView { channel, width }) - } + .on_drag(channel.clone(), move |channel, cx| { + cx.build_view(|cx| DraggedChannelView { + channel: channel.clone(), + width, + }) }) .drag_over::(|style| { style.bg(cx.theme().colors().ghost_element_hover) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 18f688f179e34c6a272351a7bcf7cae9e5dc7bd3..bfbdc6b4a69421605d0ced7c54280fe84e890293 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -1139,8 +1139,10 @@ impl AppContext { self.active_drag.is_some() } - pub fn active_drag(&self) -> Option { - self.active_drag.as_ref().map(|drag| drag.view.clone()) + pub fn active_drag(&self) -> Option<&T> { + self.active_drag + .as_ref() + .and_then(|drag| drag.value.downcast_ref()) } } @@ -1296,6 +1298,7 @@ impl DerefMut for GlobalLease { /// within the window or by dragging into the app from the underlying platform. pub struct AnyDrag { pub view: AnyView, + pub value: Box, pub cursor_offset: Point, } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1954e3086c6b8f2c2b7bd3ea705a76e33c895d6f..4eed40f2ebf2be8e69a8514f2487be3166cfb4ca 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -15,6 +15,7 @@ use std::{ cell::RefCell, cmp::Ordering, fmt::Debug, + marker::PhantomData, mem, rc::Rc, time::Duration, @@ -30,9 +31,18 @@ pub struct GroupStyle { pub style: Box, } -pub struct DragMoveEvent { +pub struct DragMoveEvent { pub event: MouseMoveEvent, - pub drag: View, + drag: PhantomData, +} + +impl DragMoveEvent { + pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T { + cx.active_drag + .as_ref() + .and_then(|drag| drag.value.downcast_ref::()) + .expect("DragMoveEvent is only valid when the stored active drag is of the same type.") + } } pub trait InteractiveElement: Sized { @@ -198,24 +208,27 @@ pub trait InteractiveElement: Sized { self } - fn on_drag_move( + fn on_drag_move( mut self, - listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, + listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, ) -> Self where - W: Render, + T: Render, { self.interactivity().mouse_move_listeners.push(Box::new( move |event, bounds, phase, cx| { if phase == DispatchPhase::Capture && bounds.drag_target_contains(&event.position, cx) { - if let Some(view) = cx.active_drag().and_then(|view| view.downcast::().ok()) + if cx + .active_drag + .as_ref() + .is_some_and(|drag| drag.value.type_id() == TypeId::of::()) { (listener)( &DragMoveEvent { event: event.clone(), - drag: view, + drag: PhantomData, }, cx, ); @@ -363,14 +376,11 @@ pub trait InteractiveElement: Sized { self } - fn on_drop( - mut self, - listener: impl Fn(&View, &mut WindowContext) + 'static, - ) -> Self { + fn on_drop(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self { self.interactivity().drop_listeners.push(( - TypeId::of::(), - Box::new(move |dragged_view, cx| { - listener(&dragged_view.downcast().unwrap(), cx); + TypeId::of::(), + Box::new(move |dragged_value, cx| { + listener(dragged_value.downcast_ref().unwrap(), cx); }), )); self @@ -437,19 +447,24 @@ pub trait StatefulInteractiveElement: InteractiveElement { self } - fn on_drag(mut self, constructor: impl Fn(&mut WindowContext) -> View + 'static) -> Self + fn on_drag( + mut self, + value: T, + constructor: impl Fn(&T, &mut WindowContext) -> View + 'static, + ) -> Self where Self: Sized, + T: 'static, W: 'static + Render, { debug_assert!( self.interactivity().drag_listener.is_none(), "calling on_drag more than once on the same element is not supported" ); - self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag { - view: constructor(cx).into(), - cursor_offset, - })); + self.interactivity().drag_listener = Some(( + Box::new(value), + Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()), + )); self } @@ -513,9 +528,9 @@ pub type ScrollWheelListener = pub type ClickListener = Box; -pub type DragListener = Box, &mut WindowContext) -> AnyDrag + 'static>; +pub type DragListener = Box AnyView + 'static>; -type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static; +type DropListener = Box; pub type TooltipBuilder = Rc AnyView + 'static>; @@ -712,9 +727,9 @@ pub struct Interactivity { pub key_down_listeners: Vec, pub key_up_listeners: Vec, pub action_listeners: Vec<(TypeId, ActionListener)>, - pub drop_listeners: Vec<(TypeId, Box)>, + pub drop_listeners: Vec<(TypeId, DropListener)>, pub click_listeners: Vec, - pub drag_listener: Option, + pub drag_listener: Option<(Box, DragListener)>, pub hover_listener: Option>, pub tooltip_builder: Option, @@ -998,8 +1013,10 @@ impl Interactivity { if phase == DispatchPhase::Bubble && interactive_bounds.drag_target_contains(&event.position, cx) { - if let Some(drag_state_type) = - cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) + if let Some(drag_state_type) = cx + .active_drag + .as_ref() + .map(|drag| drag.value.as_ref().type_id()) { for (drop_state_type, listener) in &drop_listeners { if *drop_state_type == drag_state_type { @@ -1008,7 +1025,7 @@ impl Interactivity { .take() .expect("checked for type drag state type above"); - listener(drag.view.clone(), cx); + listener(drag.value.as_ref(), cx); cx.notify(); cx.stop_propagation(); } @@ -1022,13 +1039,13 @@ impl Interactivity { } let click_listeners = mem::take(&mut self.click_listeners); - let drag_listener = mem::take(&mut self.drag_listener); + let mut drag_listener = mem::take(&mut self.drag_listener); if !click_listeners.is_empty() || drag_listener.is_some() { let pending_mouse_down = element_state.pending_mouse_down.clone(); let mouse_down = pending_mouse_down.borrow().clone(); if let Some(mouse_down) = mouse_down { - if let Some(drag_listener) = drag_listener { + if drag_listener.is_some() { let active_state = element_state.clicked_state.clone(); let interactive_bounds = interactive_bounds.clone(); @@ -1041,10 +1058,18 @@ impl Interactivity { && interactive_bounds.visibly_contains(&event.position, cx) && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD { + let (drag_value, drag_listener) = drag_listener + .take() + .expect("The notify below should invalidate this callback"); + *active_state.borrow_mut() = ElementClickedState::default(); let cursor_offset = event.position - bounds.origin; - let drag = drag_listener(cursor_offset, cx); - cx.active_drag = Some(drag); + let drag = (drag_listener)(drag_value.as_ref(), cx); + cx.active_drag = Some(AnyDrag { + view: drag, + value: drag_value, + cursor_offset, + }); cx.notify(); cx.stop_propagation(); } @@ -1312,7 +1337,7 @@ impl Interactivity { if let Some(drag) = cx.active_drag.take() { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { - if *state_type == drag.view.entity_type() + if *state_type == drag.value.as_ref().type_id() && group_bounds.contains(&mouse_position) { style.refine(&group_drag_style.style); @@ -1321,7 +1346,7 @@ impl Interactivity { } for (state_type, drag_over_style) in &self.drag_over_styles { - if *state_type == drag.view.entity_type() + if *state_type == drag.value.as_ref().type_id() && bounds .intersect(&cx.content_mask().bounds) .contains(&mouse_position) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 585db90c6fe8bc6da15e250e14cf89ab4e8b8802..fd297419868e699c78231e058261b06b67368eb4 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -806,7 +806,7 @@ impl<'a> WindowContext<'a> { /// a specific need to register a global listener. pub fn on_mouse_event( &mut self, - handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, + mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { let order = self.window.next_frame.z_index_stack.clone(); self.window @@ -1379,6 +1379,7 @@ impl<'a> WindowContext<'a> { self.window.mouse_position = position; if self.active_drag.is_none() { self.active_drag = Some(AnyDrag { + value: Box::new(files.clone()), view: self.build_view(|_| files).into(), cursor_offset: position, }); diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index adcd21cac6d35f2a00f24938184122d7c7d842c1..417b351df761afd6e094165e1b580524218a710d 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1377,33 +1377,28 @@ impl ProjectPanel { }) .unwrap_or(theme.status().info); + let file_name = details.filename.clone(); + let icon = details.icon.clone(); + let depth = details.depth; div() .id(entry_id.to_proto() as usize) - .on_drag({ - let details = details.clone(); - move |cx| { - let details = details.clone(); - cx.build_view(|_| DraggedProjectEntryView { - details, - width, - entry_id, - }) - } - }) - .drag_over::(|style| { - style.bg(cx.theme().colors().ghost_element_hover) + .on_drag(entry_id, move |entry_id, cx| { + cx.build_view(|_| DraggedProjectEntryView { + details: details.clone(), + width, + entry_id: *entry_id, + }) }) - .on_drop(cx.listener( - move |this, dragged_view: &View, cx| { - this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx); - }, - )) + .drag_over::(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| { + this.move_entry(*dragged_id, entry_id, kind.is_file(), cx); + })) .child( ListItem::new(entry_id.to_proto() as usize) - .indent_level(details.depth) + .indent_level(depth) .indent_step_size(px(settings.indent_size)) .selected(is_selected) - .child(if let Some(icon) = &details.icon { + .child(if let Some(icon) = &icon { div().child(IconElement::from_path(icon.to_string())) } else { div() @@ -1414,7 +1409,7 @@ impl ProjectPanel { } else { div() .text_color(filename_text_color) - .child(Label::new(details.filename.clone())) + .child(Label::new(file_name)) } .ml_1(), ) diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 7358f2e1d735bd9170ab2815035d4232c3e4f933..bced03e7eabd263ea8b74ff8fe29d7ac361e2719 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -792,7 +792,6 @@ impl Element for TerminalElement { .on_drop::(move |external_paths, cx| { cx.focus(&terminal_focus_handle); let mut new_text = external_paths - .read(cx) .paths() .iter() .map(|path| format!(" {path:?}")) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index f9e294763b3314cfb0b01930c0d4581c76be000e..6a4740b6e270e5ef4e813ef6ca44081bfbc3f07c 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -493,7 +493,9 @@ impl Render for Dock { let handler = div() .id("resize-handle") .bg(cx.theme().colors().border) - .on_drag(move |cx| cx.build_view(|_| DraggedDock(position))) + .on_drag(DraggedDock(position), |dock, cx| { + cx.build_view(|_| dock.clone()) + }) .on_click(cx.listener(|v, e: &ClickEvent, cx| { if e.down.button == MouseButton::Left && e.down.click_count == 2 { v.resize_active_panel(None, cx) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index a55469fbadd096bc3a041b3ebb3cc12614be871d..2f6dec5bc493dce504486044cb52424b841ca345 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -231,6 +231,7 @@ pub struct NavigationEntry { pub timestamp: usize, } +#[derive(Clone)] struct DraggedTab { pub pane: View, pub ix: usize, @@ -1514,24 +1515,25 @@ impl Pane { .on_click(cx.listener(move |pane: &mut Self, event, cx| { pane.activate_item(ix, true, true, cx) })) - .on_drag({ - let pane = cx.view().clone(); - move |cx| { - cx.build_view(|cx| DraggedTab { - pane: pane.clone(), - detail, - item_id, - is_active, - ix, - }) - } - }) - .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) - .on_drop( - cx.listener(move |this, dragged_tab: &View, cx| { - this.handle_tab_drop(dragged_tab, ix, cx) - }), + .on_drag( + DraggedTab { + pane: cx.view().clone(), + detail, + item_id, + is_active, + ix, + }, + |tab, cx| cx.build_view(|cx| tab.clone()), ) + .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) + .drag_over::(|tab| tab.bg(gpui::red())) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.handle_tab_drop(dragged_tab, ix, cx) + })) + .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + dbg!(entry_id); + this.handle_project_entry_drop(entry_id, ix, cx) + })) .when_some(item.tab_tooltip_text(cx), |tab, text| { tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) }) @@ -1677,11 +1679,13 @@ impl Pane { .drag_over::(|bar| { bar.bg(cx.theme().colors().tab_active_background) }) - .on_drop( - cx.listener(move |this, dragged_tab: &View, cx| { - this.handle_tab_drop(dragged_tab, this.items.len(), cx) - }), - ), + .drag_over::(|bar| bar.bg(gpui::red())) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.handle_tab_drop(dragged_tab, this.items.len(), cx) + })) + .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.handle_project_entry_drop(entry_id, this.items.len(), cx) + })), ) } @@ -1743,11 +1747,10 @@ impl Pane { fn handle_tab_drop( &mut self, - dragged_tab: &View, + dragged_tab: &DraggedTab, ix: usize, cx: &mut ViewContext<'_, Pane>, ) { - let dragged_tab = dragged_tab.read(cx); let item_id = dragged_tab.item_id; let from_pane = dragged_tab.pane.clone(); let to_pane = cx.view().clone(); @@ -1760,13 +1763,37 @@ impl Pane { .log_err(); } + fn handle_project_entry_drop( + &mut self, + project_entry_id: &ProjectEntryId, + ix: usize, + cx: &mut ViewContext<'_, Pane>, + ) { + let to_pane = cx.view().downgrade(); + let project_entry_id = *project_entry_id; + self.workspace + .update(cx, |workspace, cx| { + cx.defer(move |workspace, cx| { + if let Some(path) = workspace + .project() + .read(cx) + .path_for_entry(project_entry_id, cx) + { + workspace + .open_path(path, Some(to_pane), true, cx) + .detach_and_log_err(cx); + } + }); + }) + .log_err(); + } + fn handle_split_tab_drop( &mut self, - dragged_tab: &View, + dragged_tab: &DraggedTab, split_direction: SplitDirection, cx: &mut ViewContext<'_, Pane>, ) { - let dragged_tab = dragged_tab.read(cx); let item_id = dragged_tab.item_id; let from_pane = dragged_tab.pane.clone(); let to_pane = cx.view().clone(); @@ -1780,13 +1807,40 @@ impl Pane { .map(|item| item.boxed_clone()); if let Some(item) = item { if let Some(item) = item.clone_on_split(workspace.database_id(), cx) { - workspace.split_item(split_direction, item, cx); + let pane = workspace.split_pane(to_pane, split_direction, cx); + workspace.move_item(from_pane, pane, item_id, 0, cx); } } }); }) .log_err(); } + + fn handle_split_project_entry_drop( + &mut self, + project_entry_id: &ProjectEntryId, + split_direction: SplitDirection, + cx: &mut ViewContext<'_, Pane>, + ) { + let project_entry_id = *project_entry_id; + let current_pane = cx.view().clone(); + self.workspace + .update(cx, |workspace, cx| { + cx.defer(move |workspace, cx| { + if let Some(path) = workspace + .project() + .read(cx) + .path_for_entry(project_entry_id, cx) + { + let pane = workspace.split_pane(current_pane, split_direction, cx); + workspace + .open_path(path, Some(pane.downgrade()), true, cx) + .detach_and_log_err(cx); + } + }); + }) + .log_err(); + } } impl FocusableView for Pane { @@ -1894,11 +1948,17 @@ impl Render for Pane { .full() .z_index(1) .drag_over::(|style| style.bg(drag_target_color)) - .on_drop(cx.listener( - move |this, dragged_tab: &View, cx| { - this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) - }, - )), + .drag_over::(|style| style.bg(gpui::red())) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) + })) + .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.handle_project_entry_drop( + entry_id, + this.active_item_index(), + cx, + ) + })), ) .children( [ @@ -1915,9 +1975,15 @@ impl Render for Pane { .invisible() .bg(drag_target_color) .drag_over::(|style| style.visible()) + .drag_over::(|style| style.visible()) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.handle_split_tab_drop(dragged_tab, direction, cx) + })) .on_drop(cx.listener( - move |this, dragged_tab: &View, cx| { - this.handle_split_tab_drop(dragged_tab, direction, cx) + move |this, entry_id: &ProjectEntryId, cx| { + this.handle_split_project_entry_drop( + entry_id, direction, cx, + ) }, )); match direction { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a07c5818a0d0ce7fca252882ca43f41a32732c02..87083b09292dfca85bd20264bf8148d81f815b6f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3580,7 +3580,7 @@ impl FocusableView for Workspace { struct WorkspaceBounds(Bounds); -#[derive(Render)] +#[derive(Clone, Render)] struct DraggedDock(DockPosition); impl Render for Workspace { @@ -3636,7 +3636,7 @@ impl Render for Workspace { ) .on_drag_move( cx.listener(|workspace, e: &DragMoveEvent, cx| { - match e.drag.read(cx).0 { + match e.drag(cx).0 { DockPosition::Left => { let size = workspace.bounds.left() + e.event.position.x; workspace.left_dock.update(cx, |left_dock, cx| { From 6b06bb4ffe924a54f939f0c6825c566c8ff540a4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 13:30:11 -0800 Subject: [PATCH 35/61] Re-enable cmd- key bindings for activating panes co-authored-by: Conrad --- crates/workspace2/src/workspace2.rs | 45 +++++------------------------ 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a07c5818a0d0ce7fca252882ca43f41a32732c02..991a124693527eab8f2943e274ec4f0546845a1f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3,13 +3,12 @@ pub mod dock; pub mod item; +mod modal_layer; pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; pub mod searchable; -// todo!() -mod modal_layer; pub mod shared_screen; mod status_bar; mod toolbar; @@ -3313,42 +3312,12 @@ impl Workspace { ) .on_action(cx.listener(Workspace::open)) .on_action(cx.listener(Workspace::close_window)) - - // cx.add_action(Workspace::activate_pane_at_index); - // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { - // workspace.reopen_closed_item(cx).detach(); - // }); - // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { - // workspace - // .go_back(workspace.active_pane().downgrade(), cx) - // .detach(); - // }); - // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { - // workspace - // .go_forward(workspace.active_pane().downgrade(), cx) - // .detach(); - // }); - - // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { - // cx.spawn(|workspace, mut cx| async move { - // let err = install_cli::install_cli(&cx) - // .await - // .context("Failed to create CLI symlink"); - - // workspace.update(&mut cx, |workspace, cx| { - // if matches!(err, Err(_)) { - // err.notify_err(workspace, cx); - // } else { - // workspace.show_notification(1, cx, |cx| { - // cx.build_view(|_| { - // MessageNotification::new("Successfully installed the `zed` binary") - // }) - // }); - // } - // }) - // }) - // .detach(); - // }); + .on_action(cx.listener(Workspace::activate_pane_at_index)) + .on_action( + cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { + workspace.reopen_closed_item(cx).detach(); + }), + ) } #[cfg(any(test, feature = "test-support"))] From bbbdb9ff3e6cc90cf35c823e255cd694aa135af8 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Dec 2023 17:11:00 -0500 Subject: [PATCH 36/61] Render panel in this test to make sure it can receive focus --- crates/gpui2/src/app.rs | 1 - crates/gpui2/src/window.rs | 11 ++++++----- crates/project_panel2/src/project_panel.rs | 10 +++++++--- crates/workspace2/src/workspace2.rs | 1 + 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index deb03498bc7d1783aac36acdd8bb88428d7c5c64..771b56115198441d4f14506639d391cb7897747f 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -611,7 +611,6 @@ impl AppContext { .values() .filter_map(|window| { let window = window.as_ref()?; - dbg!(window.focus_invalidated); (window.dirty || window.focus_invalidated).then_some(window.handle) }) .collect::>() diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 895ae788627aa7d608762cef2ca0c4b6b4bbd15e..557b2155f86caa58f3509c8120eda6bf223c28e7 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -470,6 +470,7 @@ impl<'a> WindowContext<'a> { #[cfg(any(test, feature = "test-support"))] { + println!("invalidating focus"); self.window.focus_invalidated = true; } @@ -1237,6 +1238,11 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = false; + } + self.text_system().start_frame(); self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); @@ -1290,11 +1296,6 @@ impl<'a> WindowContext<'a> { let current_focus_path = self.window.rendered_frame.focus_path(); if previous_focus_path != current_focus_path { - #[cfg(any(test, feature = "test-support"))] - { - self.window.focus_invalidated = false; - } - if !previous_focus_path.is_empty() && current_focus_path.is_empty() { self.window .blur_listeners diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 5e71266c2177c857d6d5fbd6f34c1d3f59ef68da..f7789c1a62af282840bc53acdcf5758a6257dd8c 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -739,7 +739,6 @@ impl ProjectPanel { }); self.filename_editor.update(cx, |editor, cx| { editor.clear(cx); - println!("focusing"); editor.focus(cx); }); self.update_visible_entries(Some((worktree_id, NEW_ENTRY_ID)), cx); @@ -1672,7 +1671,7 @@ mod tests { path::{Path, PathBuf}, sync::atomic::{self, AtomicUsize}, }; - use workspace::AppState; + use workspace::{dock::PanelHandle, AppState}; #[gpui::test] async fn test_visible_list(cx: &mut gpui::TestAppContext) { @@ -2281,7 +2280,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "root1", cx); diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a7dc76f41da49fe4bf9139ed2bf7b0ae8b6f4b23..3cfb1f2458dfb2cfd90a3aa28790c457642ce3a6 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -532,6 +532,7 @@ impl Workspace { cx.notify() }) .detach(); + cx.on_blur_window(|this, cx| { let focus_handle = this.focus_handle(cx); cx.focus(&focus_handle); From d13a21c2388c6507ec66775529ccaa7ac1783e2b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 15:15:18 -0700 Subject: [PATCH 37/61] Don't move in paint --- crates/editor2/src/element.rs | 9 +- crates/gpui2/src/element.rs | 24 +- crates/gpui2/src/elements/canvas.rs | 8 +- crates/gpui2/src/elements/div.rs | 516 +++++++++++------- crates/gpui2/src/elements/img.rs | 5 +- crates/gpui2/src/elements/list.rs | 4 +- crates/gpui2/src/elements/overlay.rs | 4 +- crates/gpui2/src/elements/svg.rs | 8 +- crates/gpui2/src/elements/text.rs | 15 +- crates/gpui2/src/elements/uniform_list.rs | 6 +- crates/gpui2/src/view.rs | 16 +- crates/terminal_view2/src/terminal_element.rs | 299 +++++----- crates/ui2/src/components/popover_menu.rs | 6 +- crates/ui2/src/components/right_click_menu.rs | 8 +- crates/workspace2/src/pane_group.rs | 4 +- 15 files changed, 538 insertions(+), 394 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 9b95e256a65e0342a6b4191f8b300b98166232f8..831b6cd35ae8c80282ba312bc965fb8577e99bd3 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -933,7 +933,7 @@ impl EditorElement { cx.stop_propagation(); }, )) - .draw( + .draw2( fold_bounds.origin, fold_bounds.size, cx, @@ -1199,11 +1199,10 @@ impl EditorElement { .child(mouse_context_menu.context_menu.clone()) .anchor(AnchorCorner::TopLeft) .snap_to_window(); - element.draw( + element.into_any().draw( gpui::Point::default(), size(AvailableSpace::MinContent, AvailableSpace::MinContent), cx, - |_, _| {}, ); } } @@ -1496,7 +1495,7 @@ impl EditorElement { let scroll_left = scroll_position.x * layout.position_map.em_width; let scroll_top = scroll_position.y * layout.position_map.line_height; - for block in layout.blocks.drain(..) { + for mut block in layout.blocks.drain(..) { let mut origin = bounds.origin + point( Pixels::ZERO, @@ -2781,7 +2780,7 @@ impl Element for EditorElement { } fn paint( - mut self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut gpui::WindowContext, diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index e5ecd195baa14694bc65a15e9102d5cbd56be10a..f4bcb17f3c8180be4bef821092d6ef7ea1b0f88d 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -23,7 +23,7 @@ pub trait IntoElement: Sized { self.into_element().into_any() } - fn draw( + fn draw2( self, origin: Point, available_space: Size, @@ -92,7 +92,7 @@ pub trait Element: 'static + IntoElement { cx: &mut WindowContext, ) -> (LayoutId, Self::State); - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); fn into_any(self) -> AnyElement { AnyElement::new(self) @@ -150,8 +150,8 @@ impl Element for Component { } } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - let element = state.rendered_element.take().unwrap(); + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + let mut element = state.rendered_element.take().unwrap(); if let Some(element_id) = element.element_id() { cx.with_element_state(element_id, |element_state, cx| { let mut element_state = element_state.unwrap(); @@ -420,7 +420,7 @@ impl AnyElement { self.0.layout(cx) } - pub fn paint(mut self, cx: &mut WindowContext) { + pub fn paint(&mut self, cx: &mut WindowContext) { self.0.paint(cx) } @@ -435,7 +435,7 @@ impl AnyElement { /// Initializes this element and performs layout in the available space, then paints it at the given origin. pub fn draw( - mut self, + &mut self, origin: Point, available_space: Size, cx: &mut WindowContext, @@ -465,8 +465,8 @@ impl Element for AnyElement { (layout_id, ()) } - fn paint(self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { - self.paint(cx); + fn paint(&mut self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { + self.paint(cx) } } @@ -508,5 +508,11 @@ impl Element for () { (cx.request_layout(&crate::Style::default(), None), ()) } - fn paint(self, _bounds: Bounds, _state: &mut Self::State, _cx: &mut WindowContext) {} + fn paint( + &mut self, + _bounds: Bounds, + _state: &mut Self::State, + _cx: &mut WindowContext, + ) { + } } diff --git a/crates/gpui2/src/elements/canvas.rs b/crates/gpui2/src/elements/canvas.rs index b3afd335d41d4544267a9453e8ffedea5c990b18..56cfef4553470a146913d4664e862f4dab3ec328 100644 --- a/crates/gpui2/src/elements/canvas.rs +++ b/crates/gpui2/src/elements/canvas.rs @@ -4,13 +4,13 @@ use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut WindowContext)) -> Canvas { Canvas { - paint_callback: Box::new(callback), + paint_callback: Some(Box::new(callback)), style: StyleRefinement::default(), } } pub struct Canvas { - paint_callback: Box, &mut WindowContext)>, + paint_callback: Option, &mut WindowContext)>>, style: StyleRefinement, } @@ -40,8 +40,8 @@ impl Element for Canvas { (layout_id, ()) } - fn paint(self, bounds: Bounds, _: &mut (), cx: &mut WindowContext) { - (self.paint_callback)(&bounds, cx) + fn paint(&mut self, bounds: Bounds, _: &mut (), cx: &mut WindowContext) { + (self.paint_callback.take().unwrap())(&bounds, cx) } } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index c24b5617d5d9e795de212e9e42cfae27e82d35e3..ff2e87e8e7f15aeff8b8157a0c689765b5105efc 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -35,6 +35,281 @@ pub struct DragMoveEvent { pub drag: View, } +impl Interactivity { + pub fn on_mouse_down( + &mut self, + button: MouseButton, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) { + self.mouse_down_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == button + && bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx) + } + })); + } + + pub fn on_any_mouse_down( + &mut self, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) { + self.mouse_down_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx) + } + })); + } + + pub fn on_mouse_up( + &mut self, + button: MouseButton, + listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, + ) { + self.mouse_up_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == button + && bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx) + } + })); + } + + pub fn on_any_mouse_up( + &mut self, + listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, + ) { + self.mouse_up_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx) + } + })); + } + + pub fn on_mouse_down_out( + &mut self, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) { + self.mouse_down_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx) + } + })); + } + + pub fn on_mouse_up_out( + &mut self, + button: MouseButton, + listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, + ) { + self.mouse_up_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture + && event.button == button + && !bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx); + } + })); + } + + pub fn on_mouse_move( + &mut self, + listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, + ) { + self.mouse_move_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx); + } + })); + } + + pub fn on_drag_move( + &mut self, + listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, + ) where + W: Render, + { + self.mouse_move_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture + && bounds.drag_target_contains(&event.position, cx) + { + if let Some(view) = cx.active_drag().and_then(|view| view.downcast::().ok()) + { + (listener)( + &DragMoveEvent { + event: event.clone(), + drag: view, + }, + cx, + ); + } + } + })); + } + + pub fn on_scroll_wheel( + &mut self, + listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, + ) { + self.scroll_wheel_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx); + } + })); + } + + pub fn capture_action( + &mut self, + listener: impl Fn(&A, &mut WindowContext) + 'static, + ) { + self.action_listeners.push(( + TypeId::of::(), + Box::new(move |action, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Capture { + (listener)(action, cx) + } + }), + )); + } + + pub fn on_action(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) { + self.action_listeners.push(( + TypeId::of::(), + Box::new(move |action, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Bubble { + (listener)(action, cx) + } + }), + )); + } + + pub fn on_boxed_action( + &mut self, + action: &Box, + listener: impl Fn(&Box, &mut WindowContext) + 'static, + ) { + let action = action.boxed_clone(); + self.action_listeners.push(( + (*action).type_id(), + Box::new(move |_, phase, cx| { + if phase == DispatchPhase::Bubble { + (listener)(&action, cx) + } + }), + )); + } + + pub fn on_key_down(&mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static) { + self.key_down_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Bubble { + (listener)(event, cx) + } + })); + } + + pub fn capture_key_down( + &mut self, + listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, + ) { + self.key_down_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Capture { + listener(event, cx) + } + })); + } + + pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) { + self.key_up_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Bubble { + listener(event, cx) + } + })); + } + + pub fn capture_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) { + self.key_up_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Capture { + listener(event, cx) + } + })); + } + + pub fn on_drop( + &mut self, + listener: impl Fn(&View, &mut WindowContext) + 'static, + ) { + self.drop_listeners.push(( + TypeId::of::(), + Box::new(move |dragged_view, cx| { + listener(&dragged_view.downcast().unwrap(), cx); + }), + )); + } + + pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) + where + Self: Sized, + { + self.click_listeners + .push(Box::new(move |event, cx| listener(event, cx))); + } + + pub fn on_drag(&mut self, constructor: impl Fn(&mut WindowContext) -> View + 'static) + where + Self: Sized, + W: 'static + Render, + { + debug_assert!( + self.drag_listener.is_none(), + "calling on_drag more than once on the same element is not supported" + ); + self.drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag { + view: constructor(cx).into(), + cursor_offset, + })); + } + + pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) + where + Self: Sized, + { + debug_assert!( + self.hover_listener.is_none(), + "calling on_hover more than once on the same element is not supported" + ); + self.hover_listener = Some(Box::new(listener)); + } + + pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) + where + Self: Sized, + { + debug_assert!( + self.tooltip_builder.is_none(), + "calling tooltip more than once on the same element is not supported" + ); + self.tooltip_builder = Some(Rc::new(build_tooltip)); + } +} + pub trait InteractiveElement: Sized { fn interactivity(&mut self) -> &mut Interactivity; @@ -92,16 +367,7 @@ pub trait InteractiveElement: Sized { button: MouseButton, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx) - } - }, - )); + self.interactivity().on_mouse_down(button, listener); self } @@ -109,13 +375,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx) - } - }, - )); + self.interactivity().on_any_mouse_down(listener); self } @@ -124,30 +384,7 @@ pub trait InteractiveElement: Sized { button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx) - } - })); - self - } - - fn on_any_mouse_up( - mut self, - listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, - ) -> Self { - self.interactivity() - .mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx) - } - })); + self.interactivity().on_mouse_up(button, listener); self } @@ -155,14 +392,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx) - } - }, - )); + self.interactivity().on_mouse_down_out(listener); self } @@ -171,16 +401,7 @@ pub trait InteractiveElement: Sized { button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture - && event.button == button - && !bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx); - } - })); + self.interactivity().on_mouse_up_out(button, listener); self } @@ -188,13 +409,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_move_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx); - } - }, - )); + self.interactivity().on_mouse_move(listener); self } @@ -205,24 +420,7 @@ pub trait InteractiveElement: Sized { where W: Render, { - self.interactivity().mouse_move_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture - && bounds.drag_target_contains(&event.position, cx) - { - if let Some(view) = cx.active_drag().and_then(|view| view.downcast::().ok()) - { - (listener)( - &DragMoveEvent { - event: event.clone(), - drag: view, - }, - cx, - ); - } - } - }, - )); + self.interactivity().on_drag_move(listener); self } @@ -230,13 +428,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().scroll_wheel_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx); - } - }, - )); + self.interactivity().on_scroll_wheel(listener); self } @@ -245,29 +437,13 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&A, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().action_listeners.push(( - TypeId::of::(), - Box::new(move |action, phase, cx| { - let action = action.downcast_ref().unwrap(); - if phase == DispatchPhase::Capture { - (listener)(action, cx) - } - }), - )); + self.interactivity().capture_action(listener); self } /// Add a listener for the given action, fires during the bubble event phase fn on_action(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self { - self.interactivity().action_listeners.push(( - TypeId::of::(), - Box::new(move |action, phase, cx| { - let action = action.downcast_ref().unwrap(); - if phase == DispatchPhase::Bubble { - (listener)(action, cx) - } - }), - )); + self.interactivity().on_action(listener); self } @@ -276,15 +452,7 @@ pub trait InteractiveElement: Sized { action: &Box, listener: impl Fn(&Box, &mut WindowContext) + 'static, ) -> Self { - let action = action.boxed_clone(); - self.interactivity().action_listeners.push(( - (*action).type_id(), - Box::new(move |_, phase, cx| { - if phase == DispatchPhase::Bubble { - (listener)(&action, cx) - } - }), - )); + self.interactivity().on_boxed_action(action, listener); self } @@ -292,13 +460,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .key_down_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Bubble { - (listener)(event, cx) - } - })); + self.interactivity().on_key_down(listener); self } @@ -306,24 +468,12 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .key_down_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Capture { - listener(event, cx) - } - })); + self.interactivity().capture_key_down(listener); self } fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self { - self.interactivity() - .key_up_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Bubble { - listener(event, cx) - } - })); + self.interactivity().on_key_up(listener); self } @@ -331,13 +481,15 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .key_up_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Capture { - listener(event, cx) - } - })); + self.interactivity().capture_key_up(listener); + self + } + + fn on_drop( + mut self, + listener: impl Fn(&View, &mut WindowContext) + 'static, + ) -> Self { + self.interactivity().on_drop(listener); self } @@ -362,19 +514,6 @@ pub trait InteractiveElement: Sized { )); self } - - fn on_drop( - mut self, - listener: impl Fn(&View, &mut WindowContext) + 'static, - ) -> Self { - self.interactivity().drop_listeners.push(( - TypeId::of::(), - Box::new(move |dragged_view, cx| { - listener(&dragged_view.downcast().unwrap(), cx); - }), - )); - self - } } pub trait StatefulInteractiveElement: InteractiveElement { @@ -431,9 +570,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { where Self: Sized, { - self.interactivity() - .click_listeners - .push(Box::new(move |event, cx| listener(event, cx))); + self.interactivity().on_click(listener); self } @@ -442,14 +579,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { Self: Sized, W: 'static + Render, { - debug_assert!( - self.interactivity().drag_listener.is_none(), - "calling on_drag more than once on the same element is not supported" - ); - self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag { - view: constructor(cx).into(), - cursor_offset, - })); + self.interactivity().on_drag(constructor); self } @@ -457,11 +587,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { where Self: Sized, { - debug_assert!( - self.interactivity().hover_listener.is_none(), - "calling on_hover more than once on the same element is not supported" - ); - self.interactivity().hover_listener = Some(Box::new(listener)); + self.interactivity().on_hover(listener); self } @@ -469,11 +595,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { where Self: Sized, { - debug_assert!( - self.interactivity().tooltip_builder.is_none(), - "calling tooltip more than once on the same element is not supported" - ); - self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip)); + self.interactivity().tooltip(build_tooltip); self } } @@ -529,6 +651,7 @@ pub type ActionListener = Box Div { + #[allow(unused_mut)] let mut div = Div { interactivity: Interactivity::default(), children: SmallVec::default(), @@ -598,7 +721,7 @@ impl Element for Div { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, @@ -649,7 +772,7 @@ impl Element for Div { cx.with_text_style(style.text_style().cloned(), |cx| { cx.with_content_mask(style.overflow_mask(bounds), |cx| { cx.with_element_offset(scroll_offset, |cx| { - for child in self.children { + for child in &mut self.children { child.paint(cx); } }) @@ -769,7 +892,7 @@ impl Interactivity { } pub fn paint( - mut self, + &mut self, bounds: Bounds, content_size: Size, element_state: &mut InteractiveElementState, @@ -788,7 +911,7 @@ impl Interactivity { && bounds.contains(&cx.mouse_position()) { const FONT_SIZE: crate::Pixels = crate::Pixels(10.); - let element_id = format!("{:?}", self.element_id.unwrap()); + let element_id = format!("{:?}", self.element_id.as_ref().unwrap()); let str_len = element_id.len(); let render_debug_text = |cx: &mut WindowContext| { @@ -934,28 +1057,28 @@ impl Interactivity { }); } - for listener in self.mouse_down_listeners { + for listener in self.mouse_down_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { listener(event, &interactive_bounds, phase, cx); }) } - for listener in self.mouse_up_listeners { + for listener in self.mouse_up_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { listener(event, &interactive_bounds, phase, cx); }) } - for listener in self.mouse_move_listeners { + for listener in self.mouse_move_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { listener(event, &interactive_bounds, phase, cx); }) } - for listener in self.scroll_wheel_listeners { + for listener in self.scroll_wheel_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { listener(event, &interactive_bounds, phase, cx); @@ -1024,8 +1147,8 @@ impl Interactivity { } } - let click_listeners = self.click_listeners; - let drag_listener = self.drag_listener; + let click_listeners = mem::take(&mut self.click_listeners); + let drag_listener = mem::take(&mut self.drag_listener); if !click_listeners.is_empty() || drag_listener.is_some() { let pending_mouse_down = element_state @@ -1267,23 +1390,26 @@ impl Interactivity { .as_ref() .map(|scroll_offset| *scroll_offset.borrow()); + let key_down_listeners = mem::take(&mut self.key_down_listeners); + let key_up_listeners = mem::take(&mut self.key_up_listeners); + let action_listeners = mem::take(&mut self.action_listeners); cx.with_key_dispatch( self.key_context.clone(), element_state.focus_handle.clone(), |_, cx| { - for listener in self.key_down_listeners { + for listener in key_down_listeners { cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { listener(event, phase, cx); }) } - for listener in self.key_up_listeners { + for listener in key_up_listeners { cx.on_key_event(move |event: &KeyUpEvent, phase, cx| { listener(event, phase, cx); }) } - for (action_type, listener) in self.action_listeners { + for (action_type, listener) in action_listeners { cx.on_action(action_type, listener) } @@ -1522,7 +1648,7 @@ where self.element.layout(state, cx) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { self.element.paint(bounds, state, cx) } } @@ -1596,7 +1722,7 @@ where self.element.layout(state, cx) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { self.element.paint(bounds, state, cx) } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index f6aae2de66aebb7bc894bd63a8d9a85a6e74e089..4f81f604c8d3799923b9fb76742019cb1724c0b5 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -81,11 +81,12 @@ impl Element for Img { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, ) { + let source = self.source.clone(); self.interactivity.paint( bounds, bounds.size, @@ -94,7 +95,7 @@ impl Element for Img { |style, _scroll_offset, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); cx.with_z_index(1, |cx| { - match self.source { + match source { ImageSource::Uri(uri) => { let image_future = cx.image_cache.get(uri.clone()); if let Some(data) = image_future diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index ba479e1ea8f460ecf5915661f877a89f132f288d..6818c5c7a2e62baab27aad701dfa27cfa80dbce4 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -257,7 +257,7 @@ impl Element for List { } fn paint( - self, + &mut self, bounds: crate::Bounds, _state: &mut Self::State, cx: &mut crate::WindowContext, @@ -385,7 +385,7 @@ impl Element for List { // Paint the visible items let mut item_origin = bounds.origin; item_origin.y -= scroll_top.offset_in_item; - for mut item_element in item_elements { + for item_element in &mut item_elements { let item_height = item_element.measure(available_item_space, cx).height; item_element.draw(item_origin, available_item_space, cx); item_origin.y += item_height; diff --git a/crates/gpui2/src/elements/overlay.rs b/crates/gpui2/src/elements/overlay.rs index e925d03d275ea75d40633b1add4228b3c1ffce38..5b72019f177f899f886a375fe4468bcf403c336c 100644 --- a/crates/gpui2/src/elements/overlay.rs +++ b/crates/gpui2/src/elements/overlay.rs @@ -81,7 +81,7 @@ impl Element for Overlay { } fn paint( - self, + &mut self, bounds: crate::Bounds, element_state: &mut Self::State, cx: &mut WindowContext, @@ -149,7 +149,7 @@ impl Element for Overlay { cx.with_element_offset(desired.origin - bounds.origin, |cx| { cx.break_content_mask(|cx| { - for child in self.children { + for child in &mut self.children { child.paint(cx); } }) diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index aba31686f588fbfca2d9aabf2e04e52daa15d5bd..9ca9baf470e6c3bdeec2fafb1806d34430c44aea 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -36,8 +36,12 @@ impl Element for Svg { }) } - fn paint(self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext) - where + fn paint( + &mut self, + bounds: Bounds, + element_state: &mut Self::State, + cx: &mut WindowContext, + ) where Self: Sized, { self.interactivity diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index b8fe5e68666d7abe3e7f07b90feb4e7cdc7a5354..175a79c19a512832c7c70571780adb6268b91ab2 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -6,7 +6,7 @@ use crate::{ use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; use smallvec::SmallVec; -use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc}; +use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc}; use util::ResultExt; impl Element for &'static str { @@ -22,7 +22,7 @@ impl Element for &'static str { (layout_id, state) } - fn paint(self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { state.paint(bounds, self, cx) } } @@ -52,7 +52,7 @@ impl Element for SharedString { (layout_id, state) } - fn paint(self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { let text_str: &str = self.as_ref(); state.paint(bounds, text_str, cx) } @@ -128,7 +128,7 @@ impl Element for StyledText { (layout_id, state) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { state.paint(bounds, &self.text, cx) } } @@ -356,8 +356,8 @@ impl Element for InteractiveText { } } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - if let Some(click_listener) = self.click_listener { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + if let Some(click_listener) = self.click_listener.take() { if let Some(ix) = state .text_state .index_for_position(bounds, cx.mouse_position()) @@ -374,13 +374,14 @@ impl Element for InteractiveText { let text_state = state.text_state.clone(); let mouse_down = state.mouse_down_index.clone(); if let Some(mouse_down_index) = mouse_down.get() { + let clickable_ranges = mem::take(&mut self.clickable_ranges); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Bubble { if let Some(mouse_up_index) = text_state.index_for_position(bounds, event.position) { click_listener( - &self.clickable_ranges, + &clickable_ranges, InteractiveTextClickEvent { mouse_down_index, mouse_up_index, diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index debd365c87da2da4fbf402dc6bf4188267c191ee..9fedbad41c580b767d5c4425be1aa7a5c5f3b9b9 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -155,7 +155,7 @@ impl Element for UniformList { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, @@ -220,11 +220,11 @@ impl Element for UniformList { let visible_range = first_visible_element_ix ..cmp::min(last_visible_element_ix, self.item_count); - let items = (self.render_items)(visible_range.clone(), cx); + let mut items = (self.render_items)(visible_range.clone(), cx); cx.with_z_index(1, |cx| { let content_mask = ContentMask { bounds }; cx.with_content_mask(Some(content_mask), |cx| { - for (item, ix) in items.into_iter().zip(visible_range) { + for (item, ix) in items.iter_mut().zip(visible_range) { let item_origin = padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset.y); let available_space = size( diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index d3506e93fa1f4630d620258b77c8995c1c4cc0c3..7657ae25120c526d6581fb8be8709bdb2e2024bb 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -90,7 +90,7 @@ impl Element for View { (layout_id, Some(element)) } - fn paint(self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { element.take().unwrap().paint(cx); } } @@ -170,7 +170,7 @@ impl Eq for WeakView {} pub struct AnyView { model: AnyModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, AnyElement, &mut WindowContext), + paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), } impl AnyView { @@ -209,7 +209,7 @@ impl AnyView { ) { cx.with_absolute_element_offset(origin, |cx| { let start_time = std::time::Instant::now(); - let (layout_id, rendered_element) = (self.layout)(self, cx); + let (layout_id, mut rendered_element) = (self.layout)(self, cx); let duration = start_time.elapsed(); println!("request layout: {:?}", duration); @@ -219,7 +219,7 @@ impl AnyView { println!("compute layout: {:?}", duration); let start_time = std::time::Instant::now(); - (self.paint)(self, rendered_element, cx); + (self.paint)(self, &mut rendered_element, cx); let duration = start_time.elapsed(); println!("paint: {:?}", duration); }) @@ -248,12 +248,12 @@ impl Element for AnyView { (layout_id, Some(state)) } - fn paint(self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { debug_assert!( state.is_some(), "state is None. Did you include an AnyView twice in the tree?" ); - (self.paint)(&self, state.take().unwrap(), cx) + (self.paint)(&self, state.as_mut().unwrap(), cx) } } @@ -284,7 +284,7 @@ impl IntoElement for AnyView { pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, AnyElement, &mut WindowContext), + paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), } impl AnyWeakView { @@ -335,7 +335,7 @@ mod any_view { pub(crate) fn paint( _view: &AnyView, - element: AnyElement, + element: &mut AnyElement, cx: &mut WindowContext, ) { element.paint(cx); diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 7358f2e1d735bd9170ab2815035d4232c3e4f933..7f221129f03009115be75c2f405990cc228be7a3 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -2,8 +2,8 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ black, div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, FontStyle, - FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement, - LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, + FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, Interactivity, + IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext, }; @@ -145,11 +145,11 @@ pub struct TerminalElement { focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, - interactivity: gpui::Interactivity, + interactivity: Interactivity, } impl InteractiveElement for TerminalElement { - fn interactivity(&mut self) -> &mut gpui::Interactivity { + fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } @@ -605,141 +605,157 @@ impl TerminalElement { } fn register_mouse_listeners( - self, + &mut self, origin: Point, mode: TermMode, bounds: Bounds, cx: &mut WindowContext, - ) -> Self { + ) { let focus = self.focus.clone(); - let connection = self.terminal.clone(); - - let mut this = self - .on_mouse_down(MouseButton::Left, { - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - cx.focus(&focus); - //todo!(context menu) - // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); - connection.update(cx, |terminal, cx| { - terminal.mouse_down(&e, origin); + let terminal = self.terminal.clone(); + + self.interactivity.on_mouse_down(MouseButton::Left, { + let terminal = terminal.clone(); + let focus = focus.clone(); + move |e, cx| { + cx.focus(&focus); + //todo!(context menu) + // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); + terminal.update(cx, |terminal, cx| { + terminal.mouse_down(&e, origin); + cx.notify(); + }) + } + }); + self.interactivity.on_mouse_move({ + let terminal = terminal.clone(); + let focus = focus.clone(); + move |e, cx| { + if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { + terminal.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); cx.notify(); }) } - }) - .on_mouse_move({ - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { - connection.update(cx, |terminal, cx| { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - }) - } - } - }) - .on_mouse_up( - MouseButton::Left, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) - .on_click({ - let connection = connection.clone(); - move |e, cx| { - if e.down.button == MouseButton::Right { - let mouse_mode = connection.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }); - - if !mouse_mode { - //todo!(context menu) - // view.deploy_context_menu(e.position, cx); - } - } - } - }) - .on_mouse_move({ - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - if focus.is_focused(cx) { - connection.update(cx, |terminal, cx| { - terminal.mouse_move(&e, origin); - cx.notify(); - }) + } + }); + self.interactivity.on_mouse_up( + MouseButton::Left, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ); + self.interactivity.on_click({ + let terminal = terminal.clone(); + move |e, cx| { + if e.down.button == MouseButton::Right { + let mouse_mode = terminal.update(cx, |terminal, _cx| { + terminal.mouse_mode(e.down.modifiers.shift) + }); + + if !mouse_mode { + //todo!(context menu) + // view.deploy_context_menu(e.position, cx); } } - }) - .on_scroll_wheel({ - let connection = connection.clone(); - move |e, cx| { - connection.update(cx, |terminal, cx| { - terminal.scroll_wheel(e, origin); + } + }); + + self.interactivity.on_mouse_move({ + let terminal = terminal.clone(); + let focus = focus.clone(); + move |e, cx| { + if focus.is_focused(cx) { + terminal.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); cx.notify(); }) } - }); + } + }); + self.interactivity.on_scroll_wheel({ + let terminal = terminal.clone(); + move |e, cx| { + terminal.update(cx, |terminal, cx| { + terminal.scroll_wheel(e, origin); + cx.notify(); + }) + } + }); + + self.interactivity.on_drop::({ + let focus = focus.clone(); + let terminal = terminal.clone(); + move |external_paths, cx| { + cx.focus(&focus); + let mut new_text = external_paths + .read(cx) + .paths() + .iter() + .map(|path| format!(" {path:?}")) + .join(""); + new_text.push(' '); + terminal.update(cx, |terminal, _| { + // todo!() long paths are not displayed properly albeit the text is there + terminal.paste(&new_text); + }); + } + }); // Mouse mode handlers: // All mouse modes need the extra click handlers if mode.intersects(TermMode::MOUSE_MODE) { - this = this - .on_mouse_down( - MouseButton::Right, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_mouse_down( - MouseButton::Middle, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_mouse_up( - MouseButton::Right, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) - .on_mouse_up( - MouseButton::Middle, - TerminalElement::generic_button_handler( - connection, - origin, - focus, - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) + self.interactivity.on_mouse_down( + MouseButton::Right, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ); + self.interactivity.on_mouse_down( + MouseButton::Middle, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ); + self.interactivity.on_mouse_up( + MouseButton::Right, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ); + self.interactivity.on_mouse_up( + MouseButton::Middle, + TerminalElement::generic_button_handler( + terminal, + origin, + focus, + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ); } - - this } } @@ -764,7 +780,12 @@ impl Element for TerminalElement { (layout_id, interactive_state) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext<'_>) { + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::State, + cx: &mut WindowContext<'_>, + ) { let mut layout = self.compute_layout(bounds, cx); let theme = cx.theme(); @@ -783,33 +804,19 @@ impl Element for TerminalElement { let terminal_focus_handle = self.focus.clone(); let terminal_handle = self.terminal.clone(); - let mut this: TerminalElement = self - .register_mouse_listeners(origin, layout.mode, bounds, cx) - .drag_over::(|style| { - // todo!() why does not it work? z-index of elements? - style.bg(cx.theme().colors().ghost_element_hover) - }) - .on_drop::(move |external_paths, cx| { - cx.focus(&terminal_focus_handle); - let mut new_text = external_paths - .read(cx) - .paths() - .iter() - .map(|path| format!(" {path:?}")) - .join(""); - new_text.push(' '); - terminal_handle.update(cx, |terminal, _| { - // todo!() long paths are not displayed properly albeit the text is there - terminal.paste(&new_text); - }); - }); + self.register_mouse_listeners(origin, layout.mode, bounds, cx); - let interactivity = mem::take(&mut this.interactivity); + // todo!(change this to work in terms of on_drag_move or some such) + // .drag_over::(|style| { + // // todo!() why does not it work? z-index of elements? + // style.bg(cx.theme().colors().ghost_element_hover) + // }) + let mut interactivity = mem::take(&mut self.interactivity); interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { - cx.handle_input(&this.focus, terminal_input_handler); + cx.handle_input(&self.focus, terminal_input_handler); - this.register_key_listeners(cx); + self.register_key_listeners(cx); for rect in &layout.rects { rect.paint(origin, &layout, cx); @@ -840,7 +847,7 @@ impl Element for TerminalElement { } }); - if this.cursor_visible { + if self.cursor_visible { cx.with_z_index(3, |cx| { if let Some(cursor) = &layout.cursor { cursor.paint(origin, cx); @@ -848,7 +855,7 @@ impl Element for TerminalElement { }); } - if let Some(element) = layout.hyperlink_tooltip.take() { + if let Some(mut element) = layout.hyperlink_tooltip.take() { let width: AvailableSpace = bounds.size.width.into(); let height: AvailableSpace = bounds.size.height.into(); element.draw(origin, Size { width, height }, cx) diff --git a/crates/ui2/src/components/popover_menu.rs b/crates/ui2/src/components/popover_menu.rs index 4b5144e7c7de468e823cd7e60d062083ea065386..0f2fa6d23f147ba846695d1177ca5189f9130b3e 100644 --- a/crates/ui2/src/components/popover_menu.rs +++ b/crates/ui2/src/components/popover_menu.rs @@ -182,12 +182,12 @@ impl Element for PopoverMenu { } fn paint( - self, + &mut self, _: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, ) { - if let Some(child) = element_state.child_element.take() { + if let Some(mut child) = element_state.child_element.take() { child.paint(cx); } @@ -195,7 +195,7 @@ impl Element for PopoverMenu { element_state.child_bounds = Some(cx.layout_bounds(child_layout_id)); } - if let Some(menu) = element_state.menu_element.take() { + if let Some(mut menu) = element_state.menu_element.take() { menu.paint(cx); if let Some(child_bounds) = element_state.child_bounds { diff --git a/crates/ui2/src/components/right_click_menu.rs b/crates/ui2/src/components/right_click_menu.rs index 19031b2be70f7953e21a1305dd8957912ddfb63a..a3a454d652fa4dcbea48a6c8114c7c83e390c555 100644 --- a/crates/ui2/src/components/right_click_menu.rs +++ b/crates/ui2/src/components/right_click_menu.rs @@ -112,21 +112,21 @@ impl Element for RightClickMenu { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, ) { - if let Some(child) = element_state.child_element.take() { + if let Some(mut child) = element_state.child_element.take() { child.paint(cx); } - if let Some(menu) = element_state.menu_element.take() { + if let Some(mut menu) = element_state.menu_element.take() { menu.paint(cx); return; } - let Some(builder) = self.menu_builder else { + let Some(builder) = self.menu_builder.take() else { return; }; let menu = element_state.menu.clone(); diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 5f14df833d1dfd5df8b83edf52dd0f1fce8ad2eb..5d79109dee817d9b35558904bd8497fa5f4ea364 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -896,7 +896,7 @@ mod element { } fn paint( - self, + &mut self, bounds: gpui::Bounds, state: &mut Self::State, cx: &mut ui::prelude::WindowContext, @@ -912,7 +912,7 @@ mod element { let mut bounding_boxes = self.bounding_boxes.lock(); bounding_boxes.clear(); - for (ix, child) in self.children.into_iter().enumerate() { + for (ix, mut child) in self.children.iter_mut().enumerate() { //todo!(active_pane_magnification) // If usign active pane magnification, need to switch to using // 1 for all non-active panes, and then the magnification for the From 23d5f3f3e832ba68e0a191e7139c2e48a81445eb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 14:15:56 -0800 Subject: [PATCH 38/61] Enable all warnings in workspace, fix all warnings Bring back some workspace tests Co-authored-by: Conrad --- crates/gpui2/src/view.rs | 15 + crates/workspace2/src/dock.rs | 179 +----- crates/workspace2/src/item.rs | 6 +- crates/workspace2/src/modal_layer.rs | 6 +- crates/workspace2/src/notifications.rs | 43 +- crates/workspace2/src/pane.rs | 170 ++---- crates/workspace2/src/pane_group.rs | 416 +------------ crates/workspace2/src/searchable.rs | 4 +- crates/workspace2/src/status_bar.rs | 4 +- crates/workspace2/src/workspace2.rs | 779 ++++++++++++------------- 10 files changed, 499 insertions(+), 1123 deletions(-) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 280c52df2afad19af029a75e336222eae82aa74e..351eb0ad06fbe9ac66855870156afa1834a1a7c2 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -7,6 +7,7 @@ use crate::{ use anyhow::{Context, Result}; use std::{ any::TypeId, + fmt, hash::{Hash, Hasher}, }; @@ -297,6 +298,20 @@ impl From> for AnyWeakView { } } +impl PartialEq for AnyWeakView { + fn eq(&self, other: &Self) -> bool { + self.model == other.model + } +} + +impl std::fmt::Debug for AnyWeakView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AnyWeakView") + .field("entity_id", &self.model.entity_id) + .finish_non_exhaustive() + } +} + impl Render for T where T: 'static + FnMut(&mut WindowContext) -> E, diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 6a4740b6e270e5ef4e813ef6ca44081bfbc3f07c..54480f0b68748870530425f7b6d7910a065f2a0e 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -41,7 +41,7 @@ pub trait Panel: FocusableView + EventEmitter { } pub trait PanelHandle: Send + Sync { - fn entity_id(&self) -> EntityId; + fn panel_id(&self) -> EntityId; fn persistent_name(&self) -> &'static str; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; @@ -62,7 +62,7 @@ impl PanelHandle for View where T: Panel, { - fn entity_id(&self) -> EntityId { + fn panel_id(&self) -> EntityId { Entity::entity_id(self) } @@ -135,7 +135,7 @@ pub struct Dock { is_open: bool, active_panel_index: usize, focus_handle: FocusHandle, - focus_subscription: Subscription, + _focus_subscription: Subscription, } impl FocusableView for Dock { @@ -187,7 +187,6 @@ struct PanelEntry { pub struct PanelButtons { dock: View, - workspace: WeakView, } impl Dock { @@ -204,7 +203,7 @@ impl Dock { active_panel_index: 0, is_open: false, focus_handle, - focus_subscription, + _focus_subscription: focus_subscription, } } @@ -261,7 +260,7 @@ impl Dock { pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { for entry in &mut self.panel_entries { - if entry.panel.entity_id() == panel.entity_id() { + if entry.panel.panel_id() == panel.entity_id() { if zoomed != entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(zoomed, cx); } @@ -309,7 +308,7 @@ impl Dock { let was_visible = this.is_open() && this.visible_panel().map_or(false, |active_panel| { - active_panel.entity_id() == Entity::entity_id(&panel) + active_panel.panel_id() == Entity::entity_id(&panel) }); this.remove_panel(&panel, cx); @@ -351,7 +350,7 @@ impl Dock { if let Some(ix) = this .panel_entries .iter() - .position(|entry| entry.panel.entity_id() == Entity::entity_id(&panel)) + .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel)) { this.set_open(true, cx); this.activate_panel(ix, cx); @@ -361,7 +360,7 @@ impl Dock { PanelEvent::Close => { if this .visible_panel() - .map_or(false, |p| p.entity_id() == Entity::entity_id(&panel)) + .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel)) { this.set_open(false, cx); } @@ -389,7 +388,7 @@ impl Dock { if let Some(panel_ix) = self .panel_entries .iter() - .position(|entry| entry.panel.entity_id() == Entity::entity_id(panel)) + .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel)) { if panel_ix == self.active_panel_index { self.active_panel_index = 0; @@ -450,7 +449,7 @@ impl Dock { pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { self.panel_entries .iter() - .find(|entry| entry.panel.entity_id() == panel.entity_id()) + .find(|entry| entry.panel.panel_id() == panel.panel_id()) .map(|entry| entry.panel.size(cx)) } @@ -549,166 +548,12 @@ impl Render for Dock { } impl PanelButtons { - pub fn new( - dock: View, - workspace: WeakView, - cx: &mut ViewContext, - ) -> Self { + pub fn new(dock: View, cx: &mut ViewContext) -> Self { cx.observe(&dock, |_, _, cx| cx.notify()).detach(); - Self { dock, workspace } + Self { dock } } } -// impl Render for PanelButtons { -// type Element = (); - -// fn render(&mut self, cx: &mut ViewContext) -> Self::Element { -// todo!("") -// } - -// fn ui_name() -> &'static str { -// "PanelButtons" -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let theme = &settings::get::(cx).theme; -// let tooltip_style = theme.tooltip.clone(); -// let theme = &theme.workspace.status_bar.panel_buttons; -// let button_style = theme.button.clone(); -// let dock = self.dock.read(cx); -// let active_ix = dock.active_panel_index; -// let is_open = dock.is_open; -// let dock_position = dock.position; -// let group_style = match dock_position { -// DockPosition::Left => theme.group_left, -// DockPosition::Bottom => theme.group_bottom, -// DockPosition::Right => theme.group_right, -// }; -// let menu_corner = match dock_position { -// DockPosition::Left => AnchorCorner::BottomLeft, -// DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, -// }; - -// let panels = dock -// .panel_entries -// .iter() -// .map(|item| (item.panel.clone(), item.context_menu.clone())) -// .collect::>(); -// Flex::row() -// .with_children(panels.into_iter().enumerate().filter_map( -// |(panel_ix, (view, context_menu))| { -// let icon_path = view.icon_path(cx)?; -// let is_active = is_open && panel_ix == active_ix; -// let (tooltip, tooltip_action) = if is_active { -// ( -// format!("Close {} dock", dock_position.to_label()), -// Some(match dock_position { -// DockPosition::Left => crate::ToggleLeftDock.boxed_clone(), -// DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(), -// DockPosition::Right => crate::ToggleRightDock.boxed_clone(), -// }), -// ) -// } else { -// view.icon_tooltip(cx) -// }; -// Some( -// Stack::new() -// .with_child( -// MouseEventHandler::new::(panel_ix, cx, |state, cx| { -// let style = button_style.in_state(is_active); - -// let style = style.style_for(state); -// Flex::row() -// .with_child( -// Svg::new(icon_path) -// .with_color(style.icon_color) -// .constrained() -// .with_width(style.icon_size) -// .aligned(), -// ) -// .with_children(if let Some(label) = view.icon_label(cx) { -// Some( -// Label::new(label, style.label.text.clone()) -// .contained() -// .with_style(style.label.container) -// .aligned(), -// ) -// } else { -// None -// }) -// .constrained() -// .with_height(style.icon_size) -// .contained() -// .with_style(style.container) -// }) -// .with_cursor_style(CursorStyle::PointingHand) -// .on_click(MouseButton::Left, { -// let tooltip_action = -// tooltip_action.as_ref().map(|action| action.boxed_clone()); -// move |_, this, cx| { -// if let Some(tooltip_action) = &tooltip_action { -// let window = cx.window(); -// let view_id = this.workspace.id(); -// let tooltip_action = tooltip_action.boxed_clone(); -// cx.spawn(|_, mut cx| async move { -// window.dispatch_action( -// view_id, -// &*tooltip_action, -// &mut cx, -// ); -// }) -// .detach(); -// } -// } -// }) -// .on_click(MouseButton::Right, { -// let view = view.clone(); -// let menu = context_menu.clone(); -// move |_, _, cx| { -// const POSITIONS: [DockPosition; 3] = [ -// DockPosition::Left, -// DockPosition::Right, -// DockPosition::Bottom, -// ]; - -// menu.update(cx, |menu, cx| { -// let items = POSITIONS -// .into_iter() -// .filter(|position| { -// *position != dock_position -// && view.position_is_valid(*position, cx) -// }) -// .map(|position| { -// let view = view.clone(); -// ContextMenuItem::handler( -// format!("Dock {}", position.to_label()), -// move |cx| view.set_position(position, cx), -// ) -// }) -// .collect(); -// menu.show(Default::default(), menu_corner, items, cx); -// }) -// } -// }) -// .with_tooltip::( -// panel_ix, -// tooltip, -// tooltip_action, -// tooltip_style.clone(), -// cx, -// ), -// ) -// .with_child(ChildView::new(&context_menu, cx)), -// ) -// }, -// )) -// .contained() -// .with_style(group_style) -// .into_any() -// } -// } - -// here be kittens impl Render for PanelButtons { type Element = Div; diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index c0242ffa170aedfd00516a0ef031988bee16cdcd..43597f21a79a0cc2b178a429fe28b4736a895c5f 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -919,7 +919,7 @@ pub mod test { impl EventEmitter for TestItem {} impl FocusableView for TestItem { - fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { + fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle { self.focus_handle.clone() } } @@ -941,8 +941,8 @@ pub mod test { fn tab_content( &self, detail: Option, - selected: bool, - cx: &ui::prelude::WindowContext, + _selected: bool, + _cx: &ui::prelude::WindowContext, ) -> AnyElement { self.tab_detail.set(detail); gpui::div().into_any_element() diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index a428ba3e18352ec7750f13ecd19289b5682a75be..8c1af48667789e3cd072611778e83de601455468 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -5,7 +5,7 @@ use gpui::{ use ui::{h_stack, v_stack}; pub trait ModalView: ManagedView { - fn on_before_dismiss(&mut self, cx: &mut ViewContext) -> bool { + fn on_before_dismiss(&mut self, _: &mut ViewContext) -> bool { true } } @@ -27,7 +27,7 @@ impl ModalViewHandle for View { pub struct ActiveModal { modal: Box, - subscription: Subscription, + _subscription: Subscription, previous_focus_handle: Option, focus_handle: FocusHandle, } @@ -63,7 +63,7 @@ impl ModalLayer { { self.active_modal = Some(ActiveModal { modal: Box::new(new_modal.clone()), - subscription: cx.subscribe(&new_modal, |this, modal, _: &DismissEvent, cx| { + _subscription: cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| { this.hide_modal(cx); }), previous_focus_handle: cx.focused(), diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 63475c2aba64100155311136630f6e7d57537950..a692388a06afe1eb4f88eb063d37b1a4e4e333d1 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -104,12 +104,9 @@ impl Workspace { }) { let notification = build_notification(cx); - cx.subscribe( - ¬ification, - move |this, handle, event: &DismissEvent, cx| { - this.dismiss_notification_internal(type_id, id, cx); - }, - ) + cx.subscribe(¬ification, move |this, _, _: &DismissEvent, cx| { + this.dismiss_notification_internal(type_id, id, cx); + }) .detach(); self.notifications .push((type_id, id, Box::new(notification))); @@ -173,21 +170,15 @@ impl Workspace { pub mod simple_message_notification { use gpui::{ - div, AnyElement, AppContext, DismissEvent, Div, EventEmitter, InteractiveElement, - ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle, - ViewContext, + div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render, + SharedString, StatefulInteractiveElement, Styled, ViewContext, }; use std::sync::Arc; use ui::prelude::*; use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; - enum NotificationMessage { - Text(SharedString), - Element(fn(TextStyle, &AppContext) -> AnyElement), - } - pub struct MessageNotification { - message: NotificationMessage, + message: SharedString, on_click: Option)>>, click_message: Option, } @@ -200,23 +191,12 @@ pub mod simple_message_notification { S: Into, { Self { - message: NotificationMessage::Text(message.into()), + message: message.into(), on_click: None, click_message: None, } } - // not needed I think (only for the "new panel" toast, which is outdated now) - // pub fn new_element( - // message: fn(TextStyle, &AppContext) -> AnyElement, - // ) -> MessageNotification { - // Self { - // message: NotificationMessage::Element(message), - // on_click: None, - // click_message: None, - // } - // } - pub fn with_click_message(mut self, message: S) -> Self where S: Into, @@ -248,18 +228,13 @@ pub mod simple_message_notification { .child( h_stack() .justify_between() - .child(div().max_w_80().child(match &self.message { - NotificationMessage::Text(text) => Label::new(text.clone()), - NotificationMessage::Element(element) => { - todo!() - } - })) + .child(div().max_w_80().child(Label::new(self.message.clone()))) .child( div() .id("cancel") .child(IconElement::new(Icon::Close)) .cursor_pointer() - .on_click(cx.listener(|this, event, cx| this.dismiss(cx))), + .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), ), ) .children(self.click_message.iter().map(|message| { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 2f6dec5bc493dce504486044cb52424b841ca345..4c436daada47625f8db784f11978129b3b4082c6 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -7,7 +7,7 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext, + actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AppContext, AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable, FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render, ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, @@ -164,11 +164,6 @@ impl fmt::Debug for Event { } } -struct FocusedView { - view: AnyWeakView, - focus_handle: FocusHandle, -} - pub struct Pane { focus_handle: FocusHandle, items: Vec>, @@ -187,7 +182,7 @@ pub struct Pane { // can_drop: Rc, &WindowContext) -> bool>, can_split: bool, // render_tab_bar_buttons: Rc) -> AnyElement>, - subscriptions: Vec, + _subscriptions: Vec, tab_bar_scroll_handle: ScrollHandle, } @@ -432,14 +427,10 @@ impl Pane { // }) // .into_any() // }), - subscriptions, + _subscriptions: subscriptions, } } - pub(crate) fn workspace(&self) -> &WeakView { - &self.workspace - } - pub fn has_focus(&self, cx: &WindowContext) -> bool { // todo!(); // inline this manually self.focus_handle.contains_focused(cx) @@ -1468,21 +1459,6 @@ impl Pane { let label = item.tab_content(Some(detail), is_active, cx); let close_side = &ItemSettings::get_global(cx).close_position; - let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index { - false => ( - cx.theme().colors().text_muted, - cx.theme().colors().tab_inactive_background, - cx.theme().colors().ghost_element_hover, - cx.theme().colors().ghost_element_active, - ), - true => ( - cx.theme().colors().text, - cx.theme().colors().tab_active_background, - cx.theme().colors().element_hover, - cx.theme().colors().element_active, - ), - }; - let indicator = maybe!({ let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) { (true, _) => Color::Warning, @@ -1498,57 +1474,55 @@ impl Pane { let is_last_item = ix == self.items.len() - 1; let position_relative_to_active_item = ix.cmp(&self.active_item_index); - let tab = - Tab::new(ix) - .position(if is_first_item { - TabPosition::First - } else if is_last_item { - TabPosition::Last - } else { - TabPosition::Middle(position_relative_to_active_item) - }) - .close_side(match close_side { - ClosePosition::Left => ui::TabCloseSide::Start, - ClosePosition::Right => ui::TabCloseSide::End, - }) - .selected(is_active) - .on_click(cx.listener(move |pane: &mut Self, event, cx| { - pane.activate_item(ix, true, true, cx) - })) - .on_drag( - DraggedTab { - pane: cx.view().clone(), - detail, - item_id, - is_active, - ix, - }, - |tab, cx| cx.build_view(|cx| tab.clone()), - ) - .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) - .drag_over::(|tab| tab.bg(gpui::red())) - .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { - this.handle_tab_drop(dragged_tab, ix, cx) - })) - .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { - dbg!(entry_id); - this.handle_project_entry_drop(entry_id, ix, cx) - })) - .when_some(item.tab_tooltip_text(cx), |tab, text| { - tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) - }) - .start_slot::(indicator) - .end_slot( - IconButton::new("close tab", Icon::Close) - .icon_color(Color::Muted) - .size(ButtonSize::None) - .icon_size(IconSize::XSmall) - .on_click(cx.listener(move |pane, _, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, cx) - .detach_and_log_err(cx); - })), - ) - .child(label); + let tab = Tab::new(ix) + .position(if is_first_item { + TabPosition::First + } else if is_last_item { + TabPosition::Last + } else { + TabPosition::Middle(position_relative_to_active_item) + }) + .close_side(match close_side { + ClosePosition::Left => ui::TabCloseSide::Start, + ClosePosition::Right => ui::TabCloseSide::End, + }) + .selected(is_active) + .on_click( + cx.listener(move |pane: &mut Self, _, cx| pane.activate_item(ix, true, true, cx)), + ) + .on_drag( + DraggedTab { + pane: cx.view().clone(), + detail, + item_id, + is_active, + ix, + }, + |tab, cx| cx.build_view(|_| tab.clone()), + ) + .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) + .drag_over::(|tab| tab.bg(gpui::red())) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.handle_tab_drop(dragged_tab, ix, cx) + })) + .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.handle_project_entry_drop(entry_id, cx) + })) + .when_some(item.tab_tooltip_text(cx), |tab, text| { + tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) + }) + .start_slot::(indicator) + .end_slot( + IconButton::new("close tab", Icon::Close) + .icon_color(Color::Muted) + .size(ButtonSize::None) + .icon_size(IconSize::XSmall) + .on_click(cx.listener(move |pane, _, cx| { + pane.close_item_by_id(item_id, SaveIntent::Close, cx) + .detach_and_log_err(cx); + })), + ) + .child(label); let single_entry_to_resolve = { let item_entries = self.items[ix].project_entry_ids(cx); @@ -1618,12 +1592,12 @@ impl Pane { IconButton::new("plus", Icon::Plus) .icon_size(IconSize::Small) .on_click(cx.listener(|this, _, cx| { - let menu = ContextMenu::build(cx, |menu, cx| { + let menu = ContextMenu::build(cx, |menu, _| { menu.action("New File", NewFile.boxed_clone()) .action("New Terminal", NewCenterTerminal.boxed_clone()) .action("New Search", NewSearch.boxed_clone()) }); - cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| { + cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| { this.focus(cx); this.new_item_menu = None; }) @@ -1642,13 +1616,13 @@ impl Pane { IconButton::new("split", Icon::Split) .icon_size(IconSize::Small) .on_click(cx.listener(|this, _, cx| { - let menu = ContextMenu::build(cx, |menu, cx| { + let menu = ContextMenu::build(cx, |menu, _| { menu.action("Split Right", SplitRight.boxed_clone()) .action("Split Left", SplitLeft.boxed_clone()) .action("Split Up", SplitUp.boxed_clone()) .action("Split Down", SplitDown.boxed_clone()) }); - cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| { + cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| { this.focus(cx); this.split_item_menu = None; }) @@ -1684,7 +1658,7 @@ impl Pane { this.handle_tab_drop(dragged_tab, this.items.len(), cx) })) .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { - this.handle_project_entry_drop(entry_id, this.items.len(), cx) + this.handle_project_entry_drop(entry_id, cx) })), ) } @@ -1755,7 +1729,7 @@ impl Pane { let from_pane = dragged_tab.pane.clone(); let to_pane = cx.view().clone(); self.workspace - .update(cx, |workspace, cx| { + .update(cx, |_, cx| { cx.defer(move |workspace, cx| { workspace.move_item(from_pane, to_pane, item_id, ix, cx); }); @@ -1766,13 +1740,12 @@ impl Pane { fn handle_project_entry_drop( &mut self, project_entry_id: &ProjectEntryId, - ix: usize, cx: &mut ViewContext<'_, Pane>, ) { let to_pane = cx.view().downgrade(); let project_entry_id = *project_entry_id; self.workspace - .update(cx, |workspace, cx| { + .update(cx, |_, cx| { cx.defer(move |workspace, cx| { if let Some(path) = workspace .project() @@ -1798,19 +1771,10 @@ impl Pane { let from_pane = dragged_tab.pane.clone(); let to_pane = cx.view().clone(); self.workspace - .update(cx, |workspace, cx| { + .update(cx, |_, cx| { cx.defer(move |workspace, cx| { - let item = from_pane - .read(cx) - .items() - .find(|item| item.item_id() == item_id) - .map(|item| item.boxed_clone()); - if let Some(item) = item { - if let Some(item) = item.clone_on_split(workspace.database_id(), cx) { - let pane = workspace.split_pane(to_pane, split_direction, cx); - workspace.move_item(from_pane, pane, item_id, 0, cx); - } - } + let pane = workspace.split_pane(to_pane, split_direction, cx); + workspace.move_item(from_pane, pane, item_id, 0, cx); }); }) .log_err(); @@ -1825,7 +1789,7 @@ impl Pane { let project_entry_id = *project_entry_id; let current_pane = cx.view().clone(); self.workspace - .update(cx, |workspace, cx| { + .update(cx, |_, cx| { cx.defer(move |workspace, cx| { if let Some(path) = workspace .project() @@ -1853,8 +1817,6 @@ impl Render for Pane { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let this = cx.view().downgrade(); - v_stack() .key_context("Pane") .track_focus(&self.focus_handle) @@ -1953,11 +1915,7 @@ impl Render for Pane { this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) })) .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { - this.handle_project_entry_drop( - entry_id, - this.active_item_index(), - cx, - ) + this.handle_project_entry_drop(entry_id, cx) })), ) .children( diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 5f14df833d1dfd5df8b83edf52dd0f1fce8ad2eb..72466d9ce41ef96f838cdc50cd0d4813beef37d5 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -16,7 +16,7 @@ const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct PaneGroup { pub(crate) root: Member, } @@ -121,7 +121,7 @@ impl PaneGroup { } } -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub(crate) enum Member { Axis(PaneAxis), Pane(View), @@ -426,12 +426,6 @@ pub(crate) struct PaneAxis { pub bounding_boxes: Arc>>>>, } -impl PartialEq for PaneAxis { - fn eq(&self, other: &Self) -> bool { - todo!() - } -} - impl PaneAxis { pub fn new(axis: Axis, members: Vec) -> Self { let flexes = Arc::new(Mutex::new(vec![1.; members.len()])); @@ -816,7 +810,7 @@ mod element { proposed_current_pixel_change -= current_pixel_change; } - // todo!(reserialize workspace) + // todo!(schedule serialize) // workspace.schedule_serialize(cx); cx.notify(); } @@ -851,7 +845,7 @@ mod element { cx.on_mouse_event({ let dragged_handle = dragged_handle.clone(); - move |e: &MouseDownEvent, phase, cx| { + move |e: &MouseDownEvent, phase, _cx| { if phase.bubble() && handle_bounds.contains(&e.position) { dragged_handle.replace(Some(ix)); } @@ -859,7 +853,7 @@ mod element { }); cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| { let dragged_handle = dragged_handle.borrow(); - if *dragged_handle == Some(ix) { + if phase.bubble() && *dragged_handle == Some(ix) { Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx) } }); @@ -949,7 +943,7 @@ mod element { cx.with_z_index(1, |cx| { cx.on_mouse_event({ let state = state.clone(); - move |e: &MouseUpEvent, phase, cx| { + move |_: &MouseUpEvent, phase, _cx| { if phase.bubble() { state.replace(None); } @@ -968,402 +962,4 @@ mod element { fn flex_values_in_bounds(flexes: &[f32]) -> bool { (flexes.iter().copied().sum::() - flexes.len() as f32).abs() < 0.001 } - // // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; - - // // use gpui::{ - // // geometry::{ - // // rect::Bounds, - // // vector::{vec2f, Vector2F}, - // // }, - // // json::{self, ToJson}, - // // platform::{CursorStyle, MouseButton}, - // // scene::MouseDrag, - // // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, BoundsExt, - // // SizeConstraint, Vector2FExt, ViewContext, - // // }; - - // use crate::{ - // pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, - // Workspace, WorkspaceSettings, - // }; - - // pub struct PaneAxisElement { - // axis: Axis, - // basis: usize, - // active_pane_ix: Option, - // flexes: Rc>>, - // children: Vec>, - // bounding_boxes: Rc>>>>, - // } - - // impl PaneAxisElement { - // pub fn new( - // axis: Axis, - // basis: usize, - // flexes: Rc>>, - // bounding_boxes: Rc>>>>, - // ) -> Self { - // Self { - // axis, - // basis, - // flexes, - // bounding_boxes, - // active_pane_ix: None, - // children: Default::default(), - // } - // } - - // pub fn set_active_pane(&mut self, active_pane_ix: Option) { - // self.active_pane_ix = active_pane_ix; - // } - - // fn layout_children( - // &mut self, - // active_pane_magnification: f32, - // constraint: SizeConstraint, - // remaining_space: &mut f32, - // remaining_flex: &mut f32, - // cross_axis_max: &mut f32, - // view: &mut Workspace, - // cx: &mut ViewContext, - // ) { - // let flexes = self.flexes.borrow(); - // let cross_axis = self.axis.invert(); - // for (ix, child) in self.children.iter_mut().enumerate() { - // let flex = if active_pane_magnification != 1. { - // if let Some(active_pane_ix) = self.active_pane_ix { - // if ix == active_pane_ix { - // active_pane_magnification - // } else { - // 1. - // } - // } else { - // 1. - // } - // } else { - // flexes[ix] - // }; - - // let child_size = if *remaining_flex == 0.0 { - // *remaining_space - // } else { - // let space_per_flex = *remaining_space / *remaining_flex; - // space_per_flex * flex - // }; - - // let child_constraint = match self.axis { - // Axis::Horizontal => SizeConstraint::new( - // vec2f(child_size, constraint.min.y()), - // vec2f(child_size, constraint.max.y()), - // ), - // Axis::Vertical => SizeConstraint::new( - // vec2f(constraint.min.x(), child_size), - // vec2f(constraint.max.x(), child_size), - // ), - // }; - // let child_size = child.layout(child_constraint, view, cx); - // *remaining_space -= child_size.along(self.axis); - // *remaining_flex -= flex; - // *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); - // } - // } - - // fn handle_resize( - // flexes: Rc>>, - // axis: Axis, - // preceding_ix: usize, - // child_start: Vector2F, - // drag_bounds: Bounds, - // ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { - // let size = move |ix, flexes: &[f32]| { - // drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) - // }; - - // move |drag, workspace: &mut Workspace, cx| { - // if drag.end { - // // TODO: Clear cascading resize state - // return; - // } - // let min_size = match axis { - // Axis::Horizontal => HORIZONTAL_MIN_SIZE, - // Axis::Vertical => VERTICAL_MIN_SIZE, - // }; - // let mut flexes = flexes.borrow_mut(); - - // // Don't allow resizing to less than the minimum size, if elements are already too small - // if min_size - 1. > size(preceding_ix, flexes.as_slice()) { - // return; - // } - - // let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) - // - size(preceding_ix, flexes.as_slice()); - - // let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { - // let flex_change = pixel_dx / drag_bounds.length_along(axis); - // let current_target_flex = flexes[target_ix] + flex_change; - // let next_target_flex = - // flexes[(target_ix as isize + next) as usize] - flex_change; - // (current_target_flex, next_target_flex) - // }; - - // let mut successors = from_fn({ - // let forward = proposed_current_pixel_change > 0.; - // let mut ix_offset = 0; - // let len = flexes.len(); - // move || { - // let result = if forward { - // (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) - // } else { - // (preceding_ix as isize - ix_offset as isize >= 0) - // .then(|| preceding_ix - ix_offset) - // }; - - // ix_offset += 1; - - // result - // } - // }); - - // while proposed_current_pixel_change.abs() > 0. { - // let Some(current_ix) = successors.next() else { - // break; - // }; - - // let next_target_size = f32::max( - // size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, - // min_size, - // ); - - // let current_target_size = f32::max( - // size(current_ix, flexes.as_slice()) - // + size(current_ix + 1, flexes.as_slice()) - // - next_target_size, - // min_size, - // ); - - // let current_pixel_change = - // current_target_size - size(current_ix, flexes.as_slice()); - - // let (current_target_flex, next_target_flex) = - // flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); - - // flexes[current_ix] = current_target_flex; - // flexes[current_ix + 1] = next_target_flex; - - // proposed_current_pixel_change -= current_pixel_change; - // } - - // workspace.schedule_serialize(cx); - // cx.notify(); - // } - // } - // } - - // impl Extend> for PaneAxisElement { - // fn extend>>(&mut self, children: T) { - // self.children.extend(children); - // } - // } - - // impl Element for PaneAxisElement { - // type LayoutState = f32; - // type PaintState = (); - - // fn layout( - // &mut self, - // constraint: SizeConstraint, - // view: &mut Workspace, - // cx: &mut ViewContext, - // ) -> (Vector2F, Self::LayoutState) { - // debug_assert!(self.children.len() == self.flexes.borrow().len()); - - // let active_pane_magnification = - // settings::get::(cx).active_pane_magnification; - - // let mut remaining_flex = 0.; - - // if active_pane_magnification != 1. { - // let active_pane_flex = self - // .active_pane_ix - // .map(|_| active_pane_magnification) - // .unwrap_or(1.); - // remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; - // } else { - // for flex in self.flexes.borrow().iter() { - // remaining_flex += flex; - // } - // } - - // let mut cross_axis_max: f32 = 0.0; - // let mut remaining_space = constraint.max_along(self.axis); - - // if remaining_space.is_infinite() { - // panic!("flex contains flexible children but has an infinite constraint along the flex axis"); - // } - - // self.layout_children( - // active_pane_magnification, - // constraint, - // &mut remaining_space, - // &mut remaining_flex, - // &mut cross_axis_max, - // view, - // cx, - // ); - - // let mut size = match self.axis { - // Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), - // Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), - // }; - - // if constraint.min.x().is_finite() { - // size.set_x(size.x().max(constraint.min.x())); - // } - // if constraint.min.y().is_finite() { - // size.set_y(size.y().max(constraint.min.y())); - // } - - // if size.x() > constraint.max.x() { - // size.set_x(constraint.max.x()); - // } - // if size.y() > constraint.max.y() { - // size.set_y(constraint.max.y()); - // } - - // (size, remaining_space) - // } - - // fn paint( - // &mut self, - // bounds: Bounds, - // visible_bounds: Bounds, - // remaining_space: &mut Self::LayoutState, - // view: &mut Workspace, - // cx: &mut ViewContext, - // ) -> Self::PaintState { - // let can_resize = settings::get::(cx).active_pane_magnification == 1.; - // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - - // let overflowing = *remaining_space < 0.; - // if overflowing { - // cx.scene().push_layer(Some(visible_bounds)); - // } - - // let mut child_origin = bounds.origin(); - - // let mut bounding_boxes = self.bounding_boxes.borrow_mut(); - // bounding_boxes.clear(); - - // let mut children_iter = self.children.iter_mut().enumerate().peekable(); - // while let Some((ix, child)) = children_iter.next() { - // let child_start = child_origin.clone(); - // child.paint(child_origin, visible_bounds, view, cx); - - // bounding_boxes.push(Some(Bounds::new(child_origin, child.size()))); - - // match self.axis { - // Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), - // Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), - // } - - // if can_resize && children_iter.peek().is_some() { - // cx.scene().push_stacking_context(None, None); - - // let handle_origin = match self.axis { - // Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), - // Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), - // }; - - // let handle_bounds = match self.axis { - // Axis::Horizontal => Bounds::new( - // handle_origin, - // vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), - // ), - // Axis::Vertical => Bounds::new( - // handle_origin, - // vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), - // ), - // }; - - // let style = match self.axis { - // Axis::Horizontal => CursorStyle::ResizeLeftRight, - // Axis::Vertical => CursorStyle::ResizeUpDown, - // }; - - // cx.scene().push_cursor_region(CursorRegion { - // bounds: handle_bounds, - // style, - // }); - - // enum ResizeHandle {} - // let mut mouse_region = MouseRegion::new::( - // cx.view_id(), - // self.basis + ix, - // handle_bounds, - // ); - // mouse_region = mouse_region - // .on_drag( - // MouseButton::Left, - // Self::handle_resize( - // self.flexes.clone(), - // self.axis, - // ix, - // child_start, - // visible_bounds.clone(), - // ), - // ) - // .on_click(MouseButton::Left, { - // let flexes = self.flexes.clone(); - // move |e, v: &mut Workspace, cx| { - // if e.click_count >= 2 { - // let mut borrow = flexes.borrow_mut(); - // *borrow = vec![1.; borrow.len()]; - // v.schedule_serialize(cx); - // cx.notify(); - // } - // } - // }); - // cx.scene().push_mouse_region(mouse_region); - - // cx.scene().pop_stacking_context(); - // } - // } - - // if overflowing { - // cx.scene().pop_layer(); - // } - // } - - // fn rect_for_text_range( - // &self, - // range_utf16: Range, - // _: Bounds, - // _: Bounds, - // _: &Self::LayoutState, - // _: &Self::PaintState, - // view: &Workspace, - // cx: &ViewContext, - // ) -> Option> { - // self.children - // .iter() - // .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) - // } - - // fn debug( - // &self, - // bounds: Bounds, - // _: &Self::LayoutState, - // _: &Self::PaintState, - // view: &Workspace, - // cx: &ViewContext, - // ) -> json::Value { - // serde_json::json!({ - // "type": "PaneAxis", - // "bounds": bounds.to_json(), - // "axis": self.axis.to_json(), - // "flexes": *self.flexes.borrow(), - // "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() - // }) - // } - // } } diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index eadd602c8458d65434be39af241b699205b5e327..59202cbbaf53e5e4f62d77be693e3b13916b555b 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -193,7 +193,7 @@ impl SearchableItemHandle for View { cx: &mut WindowContext, ) -> Task>> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); - cx.spawn(|cx| async { + cx.spawn(|_| async { let matches = matches.await; matches .into_iter() @@ -253,7 +253,7 @@ pub trait WeakSearchableItemHandle: WeakItemHandle { } impl WeakSearchableItemHandle for WeakView { - fn upgrade(&self, cx: &AppContext) -> Option> { + fn upgrade(&self, _cx: &AppContext) -> Option> { Some(Box::new(self.upgrade()?)) } diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index c0cbd127acaf64df8716a562b167293ec630f5f5..ba571d6e0ad3c70bc64139e90e64f9c59314bfc7 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -51,14 +51,14 @@ impl Render for StatusBar { } impl StatusBar { - fn render_left_tools(&self, cx: &mut ViewContext) -> impl IntoElement { + fn render_left_tools(&self, _: &mut ViewContext) -> impl IntoElement { h_stack() .items_center() .gap_2() .children(self.left_items.iter().map(|item| item.to_any())) } - fn render_right_tools(&self, cx: &mut ViewContext) -> impl IntoElement { + fn render_right_tools(&self, _: &mut ViewContext) -> impl IntoElement { h_stack() .items_center() .gap_2() diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 87083b09292dfca85bd20264bf8148d81f815b6f..8318ba3f0fa064e50081bbd4131d23ac2d3f9d7a 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,15 +1,11 @@ -#![allow(unused_variables, dead_code, unused_mut)] -// todo!() this is to make transition easier. - pub mod dock; pub mod item; +mod modal_layer; pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; pub mod searchable; -// todo!() -mod modal_layer; pub mod shared_screen; mod status_bar; mod toolbar; @@ -236,14 +232,14 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.on_action({ let app_state = Arc::downgrade(&app_state); move |_: &Open, cx: &mut AppContext| { - let mut paths = cx.prompt_for_paths(PathPromptOptions { + let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: true, }); if let Some(app_state) = app_state.upgrade() { - cx.spawn(move |mut cx| async move { + cx.spawn(move |cx| async move { if let Some(paths) = paths.await.log_err().flatten() { cx.update(|cx| { open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx) @@ -458,7 +454,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - subscriptions: Vec, + _subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, _schedule_serialize: Option>, @@ -590,12 +586,9 @@ impl Workspace { let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx)); let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx)); let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx)); - let left_dock_buttons = - cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); - let bottom_dock_buttons = - cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); - let right_dock_buttons = - cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); + let left_dock_buttons = cx.build_view(|cx| PanelButtons::new(left_dock.clone(), cx)); + let bottom_dock_buttons = cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), cx)); + let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), cx)); let status_bar = cx.build_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); @@ -604,8 +597,7 @@ impl Workspace { status_bar }); - let workspace_handle = cx.view().downgrade(); - let modal_layer = cx.build_view(|cx| ModalLayer::new()); + let modal_layer = cx.build_view(|_| ModalLayer::new()); // todo!() // cx.update_default_global::, _, _>(|drag_and_drop, _| { @@ -703,7 +695,7 @@ impl Workspace { _apply_leader_updates, _schedule_serialize: None, leader_updates_tx, - subscriptions, + _subscriptions: subscriptions, pane_history_timestamp, workspace_actions: Default::default(), // This data will be incorrect, but it will be overwritten by the time it needs to be used. @@ -763,7 +755,7 @@ impl Workspace { }; let window = if let Some(window) = requesting_window { - cx.update_window(window.into(), |old_workspace, cx| { + cx.update_window(window.into(), |_, cx| { cx.replace_root_view(|cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); @@ -1185,8 +1177,7 @@ impl Workspace { } fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext) { - let save_all = self - .save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx) + self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx) .detach_and_log_err(cx); } @@ -1216,7 +1207,7 @@ impl Workspace { cx.spawn(|workspace, mut cx| async move { // Override save mode and display "Save all files" prompt if save_intent == SaveIntent::Close && dirty_items.len() > 1 { - let mut answer = workspace.update(&mut cx, |_, cx| { + let answer = workspace.update(&mut cx, |_, cx| { let prompt = Pane::file_names_for_prompt( &mut dirty_items.iter().map(|(_, handle)| handle), dirty_items.len(), @@ -1261,7 +1252,7 @@ impl Workspace { } pub fn open(&mut self, _: &Open, cx: &mut ViewContext) { - let mut paths = cx.prompt_for_paths(PathPromptOptions { + let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: true, @@ -1390,7 +1381,7 @@ impl Workspace { } fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { - let mut paths = cx.prompt_for_paths(PathPromptOptions { + let paths = cx.prompt_for_paths(PathPromptOptions { files: false, directories: true, multiple: true, @@ -1670,6 +1661,8 @@ impl Workspace { None } + // todo!("implement zoom") + #[allow(unused)] fn zoom_out(&mut self, cx: &mut ViewContext) { for pane in &self.panes { pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); @@ -2574,7 +2567,7 @@ impl Workspace { // } // } - fn render_notifications(&self, cx: &ViewContext) -> Option
{ + fn render_notifications(&self, _cx: &ViewContext) -> Option
{ if self.notifications.is_empty() { None } else { @@ -3005,6 +2998,7 @@ impl Workspace { cx.notify(); } + #[allow(unused)] fn schedule_serialize(&mut self, cx: &mut ViewContext) { self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move { cx.background_executor() @@ -3143,12 +3137,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task>>>> { cx.spawn(|workspace, mut cx| async move { - let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?; let mut center_group = None; let mut center_items = None; @@ -3293,7 +3282,7 @@ impl Workspace { .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| { workspace.swap_pane_in_direction(action.0, cx) })) - .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| { + .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| { this.toggle_dock(DockPosition::Left, cx); })) .on_action( @@ -3418,7 +3407,7 @@ impl Workspace { self } - fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext) -> Div { + fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext) -> Div { let mut div = div .on_action(cx.listener(Self::close_inactive_items_and_panes)) .on_action(cx.listener(Self::close_all_items_and_panes)) @@ -3578,8 +3567,6 @@ impl FocusableView for Workspace { } } -struct WorkspaceBounds(Bounds); - #[derive(Clone, Render)] struct DraggedDock(DockPosition); @@ -3628,7 +3615,7 @@ impl Render for Workspace { .border_b() .border_color(cx.theme().colors().border) .child( - canvas(cx.listener(|workspace, bounds, cx| { + canvas(cx.listener(|workspace, bounds, _| { workspace.bounds = *bounds; })) .absolute() @@ -4056,7 +4043,7 @@ async fn join_channel_internal( active_call: &Model, cx: &mut AsyncAppContext, ) -> Result { - let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { + let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return (false, None); }; @@ -4421,7 +4408,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) { } cx.spawn(|mut cx| async move { - if let Some(mut prompt) = prompt { + if let Some(prompt) = prompt { let answer = prompt.await?; if answer != 0 { return Ok(()); @@ -4460,6 +4447,8 @@ fn parse_pixel_size_env_var(value: &str) -> Option> { #[cfg(test)] mod tests { + use std::{cell::RefCell, rc::Rc}; + use super::*; use crate::item::{ test::{TestItem, TestProjectItem}, @@ -4470,7 +4459,6 @@ mod tests { use project::{Project, ProjectEntryId}; use serde_json::json; use settings::SettingsStore; - use std::{cell::RefCell, rc::Rc}; #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { @@ -4544,7 +4532,7 @@ mod tests { let project = Project::test(fs, ["root1".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); - let worktree_id = project.read_with(cx, |project, cx| { + let worktree_id = project.update(cx, |project, cx| { project.worktrees().next().unwrap().read(cx).id() }); @@ -4557,7 +4545,7 @@ mod tests { // Add an item to an empty pane workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx)); - project.read_with(cx, |project, cx| { + project.update(cx, |project, cx| { assert_eq!( project.active_entry(), project @@ -4570,7 +4558,7 @@ mod tests { // Add a second item to a non-empty pane workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1")); - project.read_with(cx, |project, cx| { + project.update(cx, |project, cx| { assert_eq!( project.active_entry(), project @@ -4586,7 +4574,7 @@ mod tests { .await .unwrap(); assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1")); - project.read_with(cx, |project, cx| { + project.update(cx, |project, cx| { assert_eq!( project.active_entry(), project @@ -4970,14 +4958,14 @@ mod tests { item.is_dirty = true; cx.blur(); }); - cx.executor().run_until_parked(); + cx.run_until_parked(); item.update(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. let _close_items = pane.update(cx, |pane, cx| { pane.close_items(cx, SaveIntent::Close, move |id| id == item_id) }); - cx.executor().run_until_parked(); + cx.run_until_parked(); assert!(cx.has_pending_prompt()); item.update(cx, |item, _| assert_eq!(item.save_count, 5)); } @@ -5036,363 +5024,362 @@ mod tests { }); } - // #[gpui::test] - // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); - - // let project = Project::test(fs, [], cx).await; - // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); - // let workspace = window.root(cx); - - // let panel = workspace.update(cx, |workspace, cx| { - // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right)); - // workspace.add_panel(panel.clone(), cx); - - // workspace - // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - - // panel - // }); - - // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); - // pane.update(cx, |pane, cx| { - // let item = cx.build_view(|_| TestItem::new(cx)); - // pane.add_item(Box::new(item), true, true, None, cx); - // }); - - // // Transfer focus from center to panel - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Transfer focus from panel to center - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.has_focus(cx)); - // }); - - // // Close the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.has_focus(cx)); - // }); - - // // Open the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Focus and zoom panel - // panel.update(cx, |panel, cx| { - // cx.focus_self(); - // panel.set_zoomed(true, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Transfer focus to the center closes the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(!panel.has_focus(cx)); - // }); - - // // Transferring focus back to the panel keeps it zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Close the dock while it is zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_none()); - // assert!(!panel.has_focus(cx)); - // }); - - // // Opening the dock, when it's zoomed, retains focus - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_some()); - // assert!(panel.has_focus(cx)); - // }); - - // // Unzoom and close the panel, zoom the active pane. - // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + // #[gpui::test] + // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { + // init_test(cx); + // let fs = FakeFs::new(cx.executor()); + + // let project = Project::test(fs, [], cx).await; + // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + // let panel = workspace.update(cx, |workspace, cx| { + // let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); + // workspace.add_panel(panel.clone(), cx); + + // workspace + // .right_dock() + // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + // panel + // }); + + // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + // pane.update(cx, |pane, cx| { + // let item = cx.build_view(|cx| TestItem::new(cx)); + // pane.add_item(Box::new(item), true, true, None, cx); + // }); + + // // Transfer focus from center to panel + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Transfer focus from panel to center + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Close the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(!workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Open the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Focus and zoom panel + // panel.update(cx, |panel, cx| { + // cx.focus_self(); + // panel.set_zoomed(true, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Transfer focus to the center closes the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(!workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Transferring focus back to the panel keeps it zoomed + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Close the dock while it is zoomed + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(!workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(workspace.zoomed.is_none()); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Opening the dock, when it's zoomed, retains focus + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(workspace.zoomed.is_some()); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Unzoom and close the panel, zoom the active pane. + // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + + // // Opening a dock unzooms the pane. + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + // workspace.update(cx, |workspace, cx| { + // let pane = pane.read(cx); + // assert!(!pane.is_zoomed()); + // assert!(!pane.focus_handle(cx).is_focused(cx)); + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(workspace.zoomed.is_none()); + // }); + // } - // // Opening a dock unzooms the pane. - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // workspace.update(cx, |workspace, cx| { - // let pane = pane.read(cx); - // assert!(!pane.is_zoomed()); - // assert!(!pane.has_focus()); - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(workspace.zoomed.is_none()); + // #[gpui::test] + // async fn test_panels(cx: &mut gpui::TestAppContext) { + // init_test(cx); + // let fs = FakeFs::new(cx.executor()); + + // let project = Project::test(fs, [], cx).await; + // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + // // Add panel_1 on the left, panel_2 on the right. + // let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx)); + // workspace.add_panel(panel_1.clone(), cx); + // workspace + // .left_dock() + // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + // let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); + // workspace.add_panel(panel_2.clone(), cx); + // workspace + // .right_dock() + // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + // let left_dock = workspace.left_dock(); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id() + // ); + // assert_eq!( + // left_dock.read(cx).active_panel_size(cx).unwrap(), + // panel_1.size(cx) + // ); + + // left_dock.update(cx, |left_dock, cx| { + // left_dock.resize_active_panel(Some(1337.), cx) // }); - // } - - // #[gpui::test] - // async fn test_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); - - // let project = Project::test(fs, [], cx).await; - // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); - // let workspace = window.root(cx); - - // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { - // // Add panel_1 on the left, panel_2 on the right. - // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left)); - // workspace.add_panel(panel_1.clone(), cx); + // assert_eq!( // workspace - // .left_dock() - // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right)); - // workspace.add_panel(panel_2.clone(), cx); + // .right_dock() + // .read(cx) + // .visible_panel() + // .unwrap() + // .panel_id(), + // panel_2.panel_id(), + // ); + + // (panel_1, panel_2) + // }); + + // // Move panel_1 to the right + // panel_1.update(cx, |panel_1, cx| { + // panel_1.set_position(DockPosition::Right, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // // Since it was the only panel on the left, the left dock should now be closed. + // assert!(!workspace.left_dock().read(cx).is_open()); + // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); + // let right_dock = workspace.right_dock(); + // assert_eq!( + // right_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id() + // ); + // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); + + // // Now we move panel_2 to the left + // panel_2.set_position(DockPosition::Left, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_2 was not visible on the right, we don't open the left dock. + // assert!(!workspace.left_dock().read(cx).is_open()); + // // And the right dock is unaffected in it's displaying of panel_1 + // assert!(workspace.right_dock().read(cx).is_open()); + // assert_eq!( // workspace // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - - // let left_dock = workspace.left_dock(); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert_eq!( - // left_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx) - // ); - - // left_dock.update(cx, |left_dock, cx| { - // left_dock.resize_active_panel(Some(1337.), cx) - // }); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .id(), - // panel_2.id() - // ); - - // (panel_1, panel_2) - // }); - - // // Move panel_1 to the right - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. - // // Since it was the only panel on the left, the left dock should now be closed. - // assert!(!workspace.left_dock().read(cx).is_open()); - // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); - // let right_dock = workspace.right_dock(); - // assert_eq!( - // right_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - - // // Now we move panel_2 to the left - // panel_2.set_position(DockPosition::Left, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_2 was not visible on the right, we don't open the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // And the right dock is unaffected in it's displaying of panel_1 - // assert!(workspace.right_dock().read(cx).is_open()); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .id(), - // panel_1.id() - // ); - // }); - - // // Move panel_1 back to the left - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Left, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - // // And right the dock should be closed as it no longer has any panels. - // assert!(!workspace.right_dock().read(cx).is_open()); - - // // Now we move panel_1 to the bottom - // panel_1.set_position(DockPosition::Bottom, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, we close the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // The bottom dock is sized based on the panel's default size, - // // since the panel orientation changed from vertical to horizontal. - // let bottom_dock = workspace.bottom_dock(); - // assert_eq!( - // bottom_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx), - // ); - // // Close bottom dock and move panel_1 back to the left. - // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); - // panel_1.set_position(DockPosition::Left, cx); - // }); - - // // Emit activated event on panel 1 - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); - - // // Now the left dock is open and panel_1 is active and focused. - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert!(panel_1.is_focused(cx)); - // }); - - // // Emit closed event on panel 2, which is not active - // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); - - // // Wo don't close the left dock, because panel_2 wasn't the active panel - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // }); - - // // Emitting a ZoomIn event shows the panel as zoomed. - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); - // }); - - // // Move panel to another dock while it is zoomed - // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // If focus is transferred to another view that's not a panel or another pane, we still show - // // the panel as zoomed. - // let focus_receiver = cx.build_view(|_| EmptyView); - // focus_receiver.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. - // workspace.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // If focus is transferred again to another view that's not a panel or a pane, we won't - // // show the panel as zoomed because it wasn't zoomed before. - // focus_receiver.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // When focus is transferred back to the panel, it is zoomed again. - // panel_1.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // Emitting a ZoomOut event unzooms the panel. - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // Emit closed event on panel 1, which is active - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); - - // // Now the left dock is closed, because panel_1 was the active panel - // workspace.update(cx, |workspace, cx| { - // let right_dock = workspace.right_dock(); - // assert!(!right_dock.read(cx).is_open()); - // }); - // } + // .read(cx) + // .visible_panel() + // .unwrap() + // .panel_id(), + // panel_1.panel_id(), + // ); + // }); + + // // Move panel_1 back to the left + // panel_1.update(cx, |panel_1, cx| { + // panel_1.set_position(DockPosition::Left, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + // let left_dock = workspace.left_dock(); + // assert!(left_dock.read(cx).is_open()); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id() + // ); + // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); + // // And right the dock should be closed as it no longer has any panels. + // assert!(!workspace.right_dock().read(cx).is_open()); + + // // Now we move panel_1 to the bottom + // panel_1.set_position(DockPosition::Bottom, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_1 was visible on the left, we close the left dock. + // assert!(!workspace.left_dock().read(cx).is_open()); + // // The bottom dock is sized based on the panel's default size, + // // since the panel orientation changed from vertical to horizontal. + // let bottom_dock = workspace.bottom_dock(); + // assert_eq!( + // bottom_dock.read(cx).active_panel_size(cx).unwrap(), + // panel_1.size(cx), + // ); + // // Close bottom dock and move panel_1 back to the left. + // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + // panel_1.set_position(DockPosition::Left, cx); + // }); + + // // Emit activated event on panel 1 + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); + + // // Now the left dock is open and panel_1 is active and focused. + // workspace.update(cx, |workspace, cx| { + // let left_dock = workspace.left_dock(); + // assert!(left_dock.read(cx).is_open()); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id(), + // ); + // assert!(panel_1.focus_handle(cx).is_focused(cx)); + // }); + + // // Emit closed event on panel 2, which is not active + // panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // // Wo don't close the left dock, because panel_2 wasn't the active panel + // workspace.update(cx, |workspace, cx| { + // let left_dock = workspace.left_dock(); + // assert!(left_dock.read(cx).is_open()); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id(), + // ); + // }); + + // // Emitting a ZoomIn event shows the panel as zoomed. + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); + // }); + + // // Move panel to another dock while it is zoomed + // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + // }); + + // // If focus is transferred to another view that's not a panel or another pane, we still show + // // the panel as zoomed. + // let other_focus_handle = cx.update(|cx| cx.focus_handle()); + // cx.update(|cx| cx.focus(&other_focus_handle)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + // }); + + // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + // workspace.update(cx, |_, cx| cx.focus_self()); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, None); + // assert_eq!(workspace.zoomed_position, None); + // }); + + // // If focus is transferred again to another view that's not a panel or a pane, we won't + // // show the panel as zoomed because it wasn't zoomed before. + // cx.update(|cx| cx.focus(&other_focus_handle)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, None); + // assert_eq!(workspace.zoomed_position, None); + // }); + + // // When focus is transferred back to the panel, it is zoomed again. + // panel_1.update(cx, |_, cx| cx.focus_self()); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + // }); + + // // Emitting a ZoomOut event unzooms the panel. + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, None); + // assert_eq!(workspace.zoomed_position, None); + // }); + + // // Emit closed event on panel 1, which is active + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // // Now the left dock is closed, because panel_1 was the active panel + // workspace.update(cx, |workspace, cx| { + // let right_dock = workspace.right_dock(); + // assert!(!right_dock.read(cx).is_open()); + // }); + // } pub fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { From 4e7005b4f76b32da998e8b781ec2df28d81864d4 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 14 Dec 2023 17:22:13 -0500 Subject: [PATCH 39/61] Use bitflags to hold error states --- Cargo.lock | 19 ++++++++------- crates/feedback2/Cargo.toml | 6 +++-- crates/feedback2/src/feedback_modal.rs | 33 ++++++++++++++------------ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68919dffbc943d855143e0f9c0688d19b5b9b694..7b7acd54f2a6bed1f22530b4a80ce7e2c0510634 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ dependencies = [ "alacritty_config", "alacritty_config_derive", "base64 0.13.1", - "bitflags 2.4.0", + "bitflags 2.4.1", "home", "libc", "log", @@ -1025,9 +1025,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -3280,6 +3280,7 @@ name = "feedback2" version = "0.1.0" dependencies = [ "anyhow", + "bitflags 2.4.1", "client2", "db2", "editor2", @@ -4026,7 +4027,7 @@ dependencies = [ "async-task", "backtrace", "bindgen 0.65.1", - "bitflags 2.4.0", + "bitflags 2.4.1", "block", "cbindgen", "cocoa", @@ -6195,7 +6196,7 @@ version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if 1.0.0", "foreign-types", "libc", @@ -7915,7 +7916,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -8031,7 +8032,7 @@ version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno 0.3.3", "libc", "linux-raw-sys 0.4.7", @@ -9134,7 +9135,7 @@ dependencies = [ "atoi", "base64 0.21.4", "bigdecimal", - "bitflags 2.4.0", + "bitflags 2.4.1", "byteorder", "bytes 1.5.0", "chrono", @@ -9181,7 +9182,7 @@ dependencies = [ "atoi", "base64 0.21.4", "bigdecimal", - "bitflags 2.4.0", + "bitflags 2.4.1", "byteorder", "chrono", "crc", diff --git a/crates/feedback2/Cargo.toml b/crates/feedback2/Cargo.toml index 0e34ee410b5aa26c1b0417b739c928c464447312..9fe125ec57e53f10209f75072c98ada88fe25947 100644 --- a/crates/feedback2/Cargo.toml +++ b/crates/feedback2/Cargo.toml @@ -18,7 +18,6 @@ gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } menu = { package = "menu2", path = "../menu2" } project = { package = "project2", path = "../project2" } -regex.workspace = true search = { package = "search2", path = "../search2" } settings = { package = "settings2", path = "../settings2" } theme = { package = "theme2", path = "../theme2" } @@ -26,13 +25,16 @@ ui = { package = "ui2", path = "../ui2" } util = { path = "../util" } workspace = { package = "workspace2", path = "../workspace2"} +bitflags = "2.4.1" +human_bytes = "0.4.1" + anyhow.workspace = true futures.workspace = true -human_bytes = "0.4.1" isahc.workspace = true lazy_static.workspace = true log.workspace = true postage.workspace = true +regex.workspace = true serde.workspace = true serde_derive.workspace = true smallvec.workspace = true diff --git a/crates/feedback2/src/feedback_modal.rs b/crates/feedback2/src/feedback_modal.rs index 3130c4bad6f3a31f0ec89312f6a08c3a990c692a..22904f3a0a2c14857e13bde40c2049e1a0982988 100644 --- a/crates/feedback2/src/feedback_modal.rs +++ b/crates/feedback2/src/feedback_modal.rs @@ -1,6 +1,7 @@ use std::{ops::RangeInclusive, sync::Arc, time::Duration}; use anyhow::{anyhow, bail}; +use bitflags::bitflags; use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorEvent}; @@ -48,15 +49,17 @@ struct FeedbackRequestBody<'a> { token: &'a str, } -#[derive(Debug, Clone, PartialEq)] -enum InvalidStateIssue { - EmailAddress, - CharacterCount, +bitflags! { + #[derive(Debug, Clone, PartialEq)] + struct InvalidStateFlags: u8 { + const EmailAddress = 0b00000001; + const CharacterCount = 0b00000010; + } } #[derive(Debug, Clone, PartialEq)] enum CannotSubmitReason { - InvalidState { issues: Vec }, + InvalidState { flags: InvalidStateFlags }, AwaitingSubmission, } @@ -322,7 +325,7 @@ impl FeedbackModal { return; } - let mut invalid_state_issues = Vec::new(); + let mut invalid_state_flags = InvalidStateFlags::empty(); let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) { Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address), @@ -330,37 +333,37 @@ impl FeedbackModal { }; if !valid_email_address { - invalid_state_issues.push(InvalidStateIssue::EmailAddress); + invalid_state_flags |= InvalidStateFlags::EmailAddress; } if !FEEDBACK_CHAR_LIMIT.contains(&self.character_count) { - invalid_state_issues.push(InvalidStateIssue::CharacterCount); + invalid_state_flags |= InvalidStateFlags::CharacterCount; } - if invalid_state_issues.is_empty() { + if invalid_state_flags.is_empty() { self.submission_state = Some(SubmissionState::CanSubmit); } else { self.submission_state = Some(SubmissionState::CannotSubmit { reason: CannotSubmitReason::InvalidState { - issues: invalid_state_issues, + flags: invalid_state_flags, }, }); } } fn valid_email_address(&self) -> bool { - !self.in_invalid_state(InvalidStateIssue::EmailAddress) + !self.in_invalid_state(InvalidStateFlags::EmailAddress) } fn valid_character_count(&self) -> bool { - !self.in_invalid_state(InvalidStateIssue::CharacterCount) + !self.in_invalid_state(InvalidStateFlags::CharacterCount) } - fn in_invalid_state(&self, a: InvalidStateIssue) -> bool { + fn in_invalid_state(&self, flag: InvalidStateFlags) -> bool { match self.submission_state { Some(SubmissionState::CannotSubmit { - reason: CannotSubmitReason::InvalidState { ref issues }, - }) => issues.contains(&a), + reason: CannotSubmitReason::InvalidState { ref flags }, + }) => flags.contains(flag), _ => false, } } From 6170895932b2f3ec12911dfc4373fbcb8b4cf9de Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 14:27:44 -0800 Subject: [PATCH 40/61] Fix bug in Workspace::activate_pane_in_direction Co-authored-by: Conrad --- crates/workspace2/src/workspace2.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 8318ba3f0fa064e50081bbd4131d23ac2d3f9d7a..6d0b05708387519a39e496d53ac61f9a858d3e04 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2036,20 +2036,20 @@ impl Workspace { _ => bounding_box.center(), }; - let distance_to_next = 1.; //todo(pane dividers styling) + let distance_to_next = 8.; //todo(pane dividers styling) let target = match direction { SplitDirection::Left => { - Point::new(bounding_box.origin.x - distance_to_next.into(), center.y) + Point::new(bounding_box.left() - distance_to_next.into(), center.y) } SplitDirection::Right => { Point::new(bounding_box.right() + distance_to_next.into(), center.y) } SplitDirection::Up => { - Point::new(center.x, bounding_box.origin.y - distance_to_next.into()) + Point::new(center.x, bounding_box.top() - distance_to_next.into()) } SplitDirection::Down => { - Point::new(center.x, bounding_box.top() + distance_to_next.into()) + Point::new(center.x, bounding_box.bottom() + distance_to_next.into()) } }; self.center.pane_at_pixel_position(target) From 8bac4e199d656fe0179fb300c2a435a63ea727d9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 17:28:45 -0500 Subject: [PATCH 41/61] Emit `editor_foreground` color from theme converter --- crates/theme2/src/themes/andromeda.rs | 2 ++ crates/theme2/src/themes/ayu.rs | 3 +++ crates/theme2/src/themes/dracula.rs | 1 + crates/theme2/src/themes/gruvbox.rs | 6 +++++ crates/theme2/src/themes/night_owl.rs | 2 ++ crates/theme2/src/themes/noctis.rs | 11 ++++++++ crates/theme2/src/themes/nord.rs | 1 + crates/theme2/src/themes/palenight.rs | 3 +++ crates/theme2/src/themes/rose_pine.rs | 3 +++ crates/theme2/src/themes/solarized.rs | 2 ++ crates/theme2/src/themes/synthwave_84.rs | 1 + crates/theme_importer/src/theme_printer.rs | 1 + crates/theme_importer/src/vscode/converter.rs | 26 ++++++++++++------- 13 files changed, 52 insertions(+), 10 deletions(-) diff --git a/crates/theme2/src/themes/andromeda.rs b/crates/theme2/src/themes/andromeda.rs index 33284cdbeee1bf548384ebc689861c9db0c69694..32bccf172a61ab7fcdaff19fcc4b9c973fc753bb 100644 --- a/crates/theme2/src/themes/andromeda.rs +++ b/crates/theme2/src/themes/andromeda.rs @@ -47,6 +47,7 @@ pub fn andromeda() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x3a3f4c77).into()), scrollbar_track_background: Some(rgba(0x23262eff).into()), scrollbar_track_border: Some(rgba(0x1b1d23ff).into()), + editor_foreground: Some(rgba(0xd5ced9ff).into()), editor_background: Some(rgba(0x23262eff).into()), editor_gutter_background: Some(rgba(0x23262eff).into()), editor_line_number: Some(rgba(0x746f77ff).into()), @@ -261,6 +262,7 @@ pub fn andromeda() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x3a3f4c77).into()), scrollbar_track_background: Some(rgba(0x262a33ff).into()), scrollbar_track_border: Some(rgba(0x1b1d23ff).into()), + editor_foreground: Some(rgba(0xd5ced9ff).into()), editor_background: Some(rgba(0x262a33ff).into()), editor_gutter_background: Some(rgba(0x262a33ff).into()), editor_line_number: Some(rgba(0x746f77ff).into()), diff --git a/crates/theme2/src/themes/ayu.rs b/crates/theme2/src/themes/ayu.rs index 0164e4786f88559bb4234d8acce95e463bdac57c..f7c8f8c12908f072661cd5eb65529e3dd29d71bf 100644 --- a/crates/theme2/src/themes/ayu.rs +++ b/crates/theme2/src/themes/ayu.rs @@ -46,6 +46,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x8a919966).into()), scrollbar_track_background: Some(rgba(0xf8f9faff).into()), scrollbar_track_border: Some(rgba(0x6b7d8f1f).into()), + editor_foreground: Some(rgba(0x8a9199ff).into()), editor_background: Some(rgba(0xf8f9faff).into()), editor_gutter_background: Some(rgba(0xf8f9faff).into()), editor_line_number: Some(rgba(0x8a919966).into()), @@ -352,6 +353,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x707a8c66).into()), scrollbar_track_background: Some(rgba(0x1f2430ff).into()), scrollbar_track_border: Some(rgba(0x171b24ff).into()), + editor_foreground: Some(rgba(0x707a8cff).into()), editor_background: Some(rgba(0x1f2430ff).into()), editor_gutter_background: Some(rgba(0x1f2430ff).into()), editor_line_number: Some(rgba(0x8a919966).into()), @@ -658,6 +660,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x565b6666).into()), scrollbar_track_background: Some(rgba(0x0b0e14ff).into()), scrollbar_track_border: Some(rgba(0x1e232bff).into()), + editor_foreground: Some(rgba(0x565b66ff).into()), editor_background: Some(rgba(0x0b0e14ff).into()), editor_gutter_background: Some(rgba(0x0b0e14ff).into()), editor_line_number: Some(rgba(0x6c738099).into()), diff --git a/crates/theme2/src/themes/dracula.rs b/crates/theme2/src/themes/dracula.rs index 5933ec46ffe60c0f5ff686b47312c8b283905faa..3fabb67ec2ce1a1a86d5a934731b2593ceb6426b 100644 --- a/crates/theme2/src/themes/dracula.rs +++ b/crates/theme2/src/themes/dracula.rs @@ -43,6 +43,7 @@ pub fn dracula() -> UserThemeFamily { tab_active_background: Some(rgba(0x282a36ff).into()), scrollbar_track_background: Some(rgba(0x282a36ff).into()), scrollbar_track_border: Some(rgba(0x191a21ff).into()), + editor_foreground: Some(rgba(0xf8f8f2ff).into()), editor_background: Some(rgba(0x282a36ff).into()), editor_gutter_background: Some(rgba(0x282a36ff).into()), editor_line_number: Some(rgba(0x6272a4ff).into()), diff --git a/crates/theme2/src/themes/gruvbox.rs b/crates/theme2/src/themes/gruvbox.rs index 4573f55ca1725532f1e5f5e03d52da7ef7670b41..2c9666553447d94a460fb122f4993a80dc5318a9 100644 --- a/crates/theme2/src/themes/gruvbox.rs +++ b/crates/theme2/src/themes/gruvbox.rs @@ -46,6 +46,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x50494599).into()), scrollbar_track_background: Some(rgba(0x1d2021ff).into()), scrollbar_track_border: Some(rgba(0x1d202100).into()), + editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x1d2021ff).into()), editor_gutter_background: Some(rgba(0x1d2021ff).into()), editor_line_number: Some(rgba(0x665c54ff).into()), @@ -338,6 +339,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x50494599).into()), scrollbar_track_background: Some(rgba(0x282828ff).into()), scrollbar_track_border: Some(rgba(0x28282800).into()), + editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x282828ff).into()), editor_gutter_background: Some(rgba(0x282828ff).into()), editor_line_number: Some(rgba(0x665c54ff).into()), @@ -630,6 +632,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x50494599).into()), scrollbar_track_background: Some(rgba(0x32302fff).into()), scrollbar_track_border: Some(rgba(0x32302f00).into()), + editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x32302fff).into()), editor_gutter_background: Some(rgba(0x32302fff).into()), editor_line_number: Some(rgba(0x665c54ff).into()), @@ -922,6 +925,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()), scrollbar_track_background: Some(rgba(0xf9f5d7ff).into()), scrollbar_track_border: Some(rgba(0xf9f5d700).into()), + editor_foreground: Some(rgba(0x3c3836ff).into()), editor_background: Some(rgba(0xf9f5d7ff).into()), editor_gutter_background: Some(rgba(0xf9f5d7ff).into()), editor_line_number: Some(rgba(0xbdae93ff).into()), @@ -1214,6 +1218,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()), scrollbar_track_background: Some(rgba(0xfbf1c7ff).into()), scrollbar_track_border: Some(rgba(0xfbf1c700).into()), + editor_foreground: Some(rgba(0x3c3836ff).into()), editor_background: Some(rgba(0xfbf1c7ff).into()), editor_gutter_background: Some(rgba(0xfbf1c7ff).into()), editor_line_number: Some(rgba(0xbdae93ff).into()), @@ -1506,6 +1511,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()), scrollbar_track_background: Some(rgba(0xf2e5bcff).into()), scrollbar_track_border: Some(rgba(0xf2e5bc00).into()), + editor_foreground: Some(rgba(0x3c3836ff).into()), editor_background: Some(rgba(0xf2e5bcff).into()), editor_gutter_background: Some(rgba(0xf2e5bcff).into()), editor_line_number: Some(rgba(0xbdae93ff).into()), diff --git a/crates/theme2/src/themes/night_owl.rs b/crates/theme2/src/themes/night_owl.rs index 71c980e6315314d60d3d761c2bc66524ae732242..778dfabeb8e7c5f1bb6c5afa43c0930205157102 100644 --- a/crates/theme2/src/themes/night_owl.rs +++ b/crates/theme2/src/themes/night_owl.rs @@ -46,6 +46,7 @@ pub fn night_owl() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x084d8180).into()), scrollbar_thumb_border: Some(rgba(0x084d8180).into()), scrollbar_track_background: Some(rgba(0x011627ff).into()), + editor_foreground: Some(rgba(0xd6deebff).into()), editor_background: Some(rgba(0x011627ff).into()), editor_gutter_background: Some(rgba(0x011627ff).into()), editor_line_number: Some(rgba(0x4b6479ff).into()), @@ -305,6 +306,7 @@ pub fn night_owl() -> UserThemeFamily { tab_inactive_background: Some(rgba(0xf0f0f0ff).into()), tab_active_background: Some(rgba(0xf6f6f6ff).into()), scrollbar_track_background: Some(rgba(0xfbfbfbff).into()), + editor_foreground: Some(rgba(0x403f53ff).into()), editor_background: Some(rgba(0xfbfbfbff).into()), editor_gutter_background: Some(rgba(0xfbfbfbff).into()), editor_line_number: Some(rgba(0x90a7b2ff).into()), diff --git a/crates/theme2/src/themes/noctis.rs b/crates/theme2/src/themes/noctis.rs index 51b442a57e46de757b735c5a881be30bd02cdc9c..614553593d5ffa031d385dfbd801af10d9892bfc 100644 --- a/crates/theme2/src/themes/noctis.rs +++ b/crates/theme2/src/themes/noctis.rs @@ -47,6 +47,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x008ee633).into()), scrollbar_track_background: Some(rgba(0x07273bff).into()), scrollbar_track_border: Some(rgba(0x07273bff).into()), + editor_foreground: Some(rgba(0xbecfdaff).into()), editor_background: Some(rgba(0x07273bff).into()), editor_gutter_background: Some(rgba(0x07273bff).into()), editor_line_number: Some(rgba(0x4d6c80ff).into()), @@ -324,6 +325,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xeb609133).into()), scrollbar_track_background: Some(rgba(0x322a2dff).into()), scrollbar_track_border: Some(rgba(0x322a2dff).into()), + editor_foreground: Some(rgba(0xcbbec2ff).into()), editor_background: Some(rgba(0x322a2dff).into()), editor_gutter_background: Some(rgba(0x322a2dff).into()), editor_line_number: Some(rgba(0x715b63ff).into()), @@ -601,6 +603,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0xf4f6f6ff).into()), scrollbar_track_border: Some(rgba(0xf4f6f6ff).into()), + editor_foreground: Some(rgba(0x005661ff).into()), editor_background: Some(rgba(0xf4f6f6ff).into()), editor_gutter_background: Some(rgba(0xf4f6f6ff).into()), editor_line_number: Some(rgba(0xa0abacff).into()), @@ -878,6 +881,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0xf2f1f8ff).into()), scrollbar_track_border: Some(rgba(0xf2f1f8ff).into()), + editor_foreground: Some(rgba(0x0c006bff).into()), editor_background: Some(rgba(0xf2f1f8ff).into()), editor_gutter_background: Some(rgba(0xf2f1f8ff).into()), editor_line_number: Some(rgba(0x9d9ab1ff).into()), @@ -1155,6 +1159,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0xfef8ecff).into()), scrollbar_track_border: Some(rgba(0xfef8ecff).into()), + editor_foreground: Some(rgba(0x005661ff).into()), editor_background: Some(rgba(0xfef8ecff).into()), editor_gutter_background: Some(rgba(0xfef8ecff).into()), editor_line_number: Some(rgba(0xa0abacff).into()), @@ -1432,6 +1437,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x3f7fa633).into()), scrollbar_track_background: Some(rgba(0x1b2932ff).into()), scrollbar_track_border: Some(rgba(0x1b2932ff).into()), + editor_foreground: Some(rgba(0xc5cdd3ff).into()), editor_background: Some(rgba(0x1b2932ff).into()), editor_gutter_background: Some(rgba(0x1b2932ff).into()), editor_line_number: Some(rgba(0x5d6e79ff).into()), @@ -1709,6 +1715,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0x052529ff).into()), scrollbar_track_border: Some(rgba(0x052529ff).into()), + editor_foreground: Some(rgba(0xb2cacdff).into()), editor_background: Some(rgba(0x052529ff).into()), editor_gutter_background: Some(rgba(0x052529ff).into()), editor_line_number: Some(rgba(0x4e6b6eff).into()), @@ -1986,6 +1993,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0x031417ff).into()), scrollbar_track_border: Some(rgba(0x031417ff).into()), + editor_foreground: Some(rgba(0xb2cacdff).into()), editor_background: Some(rgba(0x031417ff).into()), editor_gutter_background: Some(rgba(0x031417ff).into()), editor_line_number: Some(rgba(0x4e6b6eff).into()), @@ -2263,6 +2271,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0x031417ff).into()), scrollbar_track_border: Some(rgba(0x031417ff).into()), + editor_foreground: Some(rgba(0xb2cacdff).into()), editor_background: Some(rgba(0x031417ff).into()), editor_gutter_background: Some(rgba(0x031417ff).into()), editor_line_number: Some(rgba(0x4e6b6eff).into()), @@ -2540,6 +2549,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x7060eb33).into()), scrollbar_track_background: Some(rgba(0x292640ff).into()), scrollbar_track_border: Some(rgba(0x292640ff).into()), + editor_foreground: Some(rgba(0xc5c2d6ff).into()), editor_background: Some(rgba(0x292640ff).into()), editor_gutter_background: Some(rgba(0x292640ff).into()), editor_line_number: Some(rgba(0x5c5973ff).into()), @@ -2817,6 +2827,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xa660eb33).into()), scrollbar_track_background: Some(rgba(0x30243dff).into()), scrollbar_track_border: Some(rgba(0x30243dff).into()), + editor_foreground: Some(rgba(0xccbfd9ff).into()), editor_background: Some(rgba(0x30243dff).into()), editor_gutter_background: Some(rgba(0x30243dff).into()), editor_line_number: Some(rgba(0x665973ff).into()), diff --git a/crates/theme2/src/themes/nord.rs b/crates/theme2/src/themes/nord.rs index 949926c6857d0b0b0d183328c8e837f0007c7afc..f3d03b38892628aa06f21b90eca69c169fca9921 100644 --- a/crates/theme2/src/themes/nord.rs +++ b/crates/theme2/src/themes/nord.rs @@ -46,6 +46,7 @@ pub fn nord() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x434c5e99).into()), scrollbar_track_background: Some(rgba(0x2e3440ff).into()), scrollbar_track_border: Some(rgba(0x3b4252ff).into()), + editor_foreground: Some(rgba(0xd8dee9ff).into()), editor_background: Some(rgba(0x2e3440ff).into()), editor_gutter_background: Some(rgba(0x2e3440ff).into()), editor_line_number: Some(rgba(0x4c566aff).into()), diff --git a/crates/theme2/src/themes/palenight.rs b/crates/theme2/src/themes/palenight.rs index ab47cc104643bc63fba6a85a778474b2fb4925c0..028d326229dfe268a096838f76f2068cd3bf12e5 100644 --- a/crates/theme2/src/themes/palenight.rs +++ b/crates/theme2/src/themes/palenight.rs @@ -46,6 +46,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), + editor_foreground: Some(rgba(0xffffffff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), @@ -331,6 +332,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), + editor_foreground: Some(rgba(0xffffffff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), @@ -616,6 +618,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), + editor_foreground: Some(rgba(0xffffffff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), diff --git a/crates/theme2/src/themes/rose_pine.rs b/crates/theme2/src/themes/rose_pine.rs index d9091df347dc891d628d601ccea2b8a7c965cf56..a09364405f02e9adb4d74a6b16f82f9dc386db9e 100644 --- a/crates/theme2/src/themes/rose_pine.rs +++ b/crates/theme2/src/themes/rose_pine.rs @@ -47,6 +47,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6e6a8633).into()), scrollbar_track_background: Some(rgba(0x191724ff).into()), scrollbar_track_border: Some(rgba(0x6e6a8666).into()), + editor_foreground: Some(rgba(0xe0def4ff).into()), editor_background: Some(rgba(0x191724ff).into()), editor_gutter_background: Some(rgba(0x191724ff).into()), editor_line_number: Some(rgba(0x908caaff).into()), @@ -306,6 +307,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x817c9c26).into()), scrollbar_track_background: Some(rgba(0x232136ff).into()), scrollbar_track_border: Some(rgba(0x817c9c4d).into()), + editor_foreground: Some(rgba(0xe0def4ff).into()), editor_background: Some(rgba(0x232136ff).into()), editor_gutter_background: Some(rgba(0x232136ff).into()), editor_line_number: Some(rgba(0x908caaff).into()), @@ -565,6 +567,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6e6a8614).into()), scrollbar_track_background: Some(rgba(0xfaf4edff).into()), scrollbar_track_border: Some(rgba(0x6e6a8626).into()), + editor_foreground: Some(rgba(0x575279ff).into()), editor_background: Some(rgba(0xfaf4edff).into()), editor_gutter_background: Some(rgba(0xfaf4edff).into()), editor_line_number: Some(rgba(0x797593ff).into()), diff --git a/crates/theme2/src/themes/solarized.rs b/crates/theme2/src/themes/solarized.rs index 25799bea280e0c4f00becefdfcda76b0b4b49ff3..9807a6cd3de000d79bd9b30fc21ffad5e9562906 100644 --- a/crates/theme2/src/themes/solarized.rs +++ b/crates/theme2/src/themes/solarized.rs @@ -42,6 +42,7 @@ pub fn solarized() -> UserThemeFamily { tab_inactive_background: Some(rgba(0x004052ff).into()), tab_active_background: Some(rgba(0x002b37ff).into()), scrollbar_track_background: Some(rgba(0x002b36ff).into()), + editor_foreground: Some(rgba(0xbbbbbbff).into()), editor_background: Some(rgba(0x002b36ff).into()), editor_gutter_background: Some(rgba(0x002b36ff).into()), editor_line_number: Some(rgba(0x566c74ff).into()), @@ -307,6 +308,7 @@ pub fn solarized() -> UserThemeFamily { tab_inactive_background: Some(rgba(0xd3cbb7ff).into()), tab_active_background: Some(rgba(0xfdf6e3ff).into()), scrollbar_track_background: Some(rgba(0xfdf6e3ff).into()), + editor_foreground: Some(rgba(0x333333ff).into()), editor_background: Some(rgba(0xfdf6e3ff).into()), editor_gutter_background: Some(rgba(0xfdf6e3ff).into()), editor_line_number: Some(rgba(0x9ca8a6ff).into()), diff --git a/crates/theme2/src/themes/synthwave_84.rs b/crates/theme2/src/themes/synthwave_84.rs index 4f62732d7f139e720d357a6fac6192c267f4e776..defc73ae567c788cf221162d549a9254157817c0 100644 --- a/crates/theme2/src/themes/synthwave_84.rs +++ b/crates/theme2/src/themes/synthwave_84.rs @@ -39,6 +39,7 @@ pub fn synthwave_84() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x9d8bca30).into()), scrollbar_track_background: Some(rgba(0x262335ff).into()), scrollbar_track_border: Some(rgba(0x34294fb3).into()), + editor_foreground: Some(rgba(0xffffffff).into()), editor_background: Some(rgba(0x262335ff).into()), editor_gutter_background: Some(rgba(0x262335ff).into()), editor_line_number: Some(rgba(0xffffff73).into()), diff --git a/crates/theme_importer/src/theme_printer.rs b/crates/theme_importer/src/theme_printer.rs index 78c8719105576f9efb7b6bca26dfcda3f07f5873..4726c90c6d1077da8f8f1c947bff49bac9bc9d57 100644 --- a/crates/theme_importer/src/theme_printer.rs +++ b/crates/theme_importer/src/theme_printer.rs @@ -201,6 +201,7 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> { self.0.scrollbar_track_background, ), ("scrollbar_track_border", self.0.scrollbar_track_border), + ("editor_foreground", self.0.editor_foreground), ("editor_background", self.0.editor_background), ("editor_gutter_background", self.0.editor_gutter_background), ( diff --git a/crates/theme_importer/src/vscode/converter.rs b/crates/theme_importer/src/vscode/converter.rs index c9a07a1f7f2a78cb09da7e5c09d21e5fab02cc8a..e4c95d6cf234637a2f509dc2b3a457647d1fc386 100644 --- a/crates/theme_importer/src/vscode/converter.rs +++ b/crates/theme_importer/src/vscode/converter.rs @@ -142,6 +142,16 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))?; + let vscode_token_colors_foreground = self + .theme + .token_colors + .iter() + .find(|token_color| token_color.scope.is_none()) + .and_then(|token_color| token_color.settings.foreground.as_ref()) + .traverse(|color| try_parse_color(&color)) + .ok() + .flatten(); + Ok(ThemeColorsRefinement { border: vscode_panel_border, border_variant: vscode_panel_border, @@ -197,16 +207,7 @@ impl VsCodeThemeConverter { .foreground .as_ref() .traverse(|color| try_parse_color(&color))? - .or_else(|| { - self.theme - .token_colors - .iter() - .find(|token_color| token_color.scope.is_none()) - .and_then(|token_color| token_color.settings.foreground.as_ref()) - .traverse(|color| try_parse_color(&color)) - .ok() - .flatten() - }), + .or(vscode_token_colors_foreground), text_muted: vscode_colors .tab_inactive_foreground .as_ref() @@ -226,6 +227,11 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))? .or(vscode_editor_background), + editor_foreground: vscode_colors + .foreground + .as_ref() + .traverse(|color| try_parse_color(&color))? + .or(vscode_token_colors_foreground), editor_background: vscode_editor_background, editor_gutter_background: vscode_editor_background, editor_line_number: vscode_colors From 292fac37bbe9109e4297d1e288c92c8784b9001d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 17:30:37 -0500 Subject: [PATCH 42/61] Use `editor.foreground` from VS Code for the editor foreground --- crates/theme2/src/themes/ayu.rs | 6 +++--- crates/theme2/src/themes/palenight.rs | 6 +++--- crates/theme2/src/themes/synthwave_84.rs | 1 - crates/theme_importer/src/vscode/converter.rs | 11 ++++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/theme2/src/themes/ayu.rs b/crates/theme2/src/themes/ayu.rs index f7c8f8c12908f072661cd5eb65529e3dd29d71bf..4c5d95123000183ace35e98f4a291762dbd3129e 100644 --- a/crates/theme2/src/themes/ayu.rs +++ b/crates/theme2/src/themes/ayu.rs @@ -46,7 +46,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x8a919966).into()), scrollbar_track_background: Some(rgba(0xf8f9faff).into()), scrollbar_track_border: Some(rgba(0x6b7d8f1f).into()), - editor_foreground: Some(rgba(0x8a9199ff).into()), + editor_foreground: Some(rgba(0x5c6166ff).into()), editor_background: Some(rgba(0xf8f9faff).into()), editor_gutter_background: Some(rgba(0xf8f9faff).into()), editor_line_number: Some(rgba(0x8a919966).into()), @@ -353,7 +353,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x707a8c66).into()), scrollbar_track_background: Some(rgba(0x1f2430ff).into()), scrollbar_track_border: Some(rgba(0x171b24ff).into()), - editor_foreground: Some(rgba(0x707a8cff).into()), + editor_foreground: Some(rgba(0xcccac2ff).into()), editor_background: Some(rgba(0x1f2430ff).into()), editor_gutter_background: Some(rgba(0x1f2430ff).into()), editor_line_number: Some(rgba(0x8a919966).into()), @@ -660,7 +660,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x565b6666).into()), scrollbar_track_background: Some(rgba(0x0b0e14ff).into()), scrollbar_track_border: Some(rgba(0x1e232bff).into()), - editor_foreground: Some(rgba(0x565b66ff).into()), + editor_foreground: Some(rgba(0xbfbdb6ff).into()), editor_background: Some(rgba(0x0b0e14ff).into()), editor_gutter_background: Some(rgba(0x0b0e14ff).into()), editor_line_number: Some(rgba(0x6c738099).into()), diff --git a/crates/theme2/src/themes/palenight.rs b/crates/theme2/src/themes/palenight.rs index 028d326229dfe268a096838f76f2068cd3bf12e5..501a678577d69a7318b1fd880c81c7790f0b83e5 100644 --- a/crates/theme2/src/themes/palenight.rs +++ b/crates/theme2/src/themes/palenight.rs @@ -46,7 +46,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), - editor_foreground: Some(rgba(0xffffffff).into()), + editor_foreground: Some(rgba(0xbfc7d5ff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), @@ -332,7 +332,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), - editor_foreground: Some(rgba(0xffffffff).into()), + editor_foreground: Some(rgba(0xbfc7d5ff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), @@ -618,7 +618,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), - editor_foreground: Some(rgba(0xffffffff).into()), + editor_foreground: Some(rgba(0xbfc7d5ff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), diff --git a/crates/theme2/src/themes/synthwave_84.rs b/crates/theme2/src/themes/synthwave_84.rs index defc73ae567c788cf221162d549a9254157817c0..4f62732d7f139e720d357a6fac6192c267f4e776 100644 --- a/crates/theme2/src/themes/synthwave_84.rs +++ b/crates/theme2/src/themes/synthwave_84.rs @@ -39,7 +39,6 @@ pub fn synthwave_84() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x9d8bca30).into()), scrollbar_track_background: Some(rgba(0x262335ff).into()), scrollbar_track_border: Some(rgba(0x34294fb3).into()), - editor_foreground: Some(rgba(0xffffffff).into()), editor_background: Some(rgba(0x262335ff).into()), editor_gutter_background: Some(rgba(0x262335ff).into()), editor_line_number: Some(rgba(0xffffff73).into()), diff --git a/crates/theme_importer/src/vscode/converter.rs b/crates/theme_importer/src/vscode/converter.rs index e4c95d6cf234637a2f509dc2b3a457647d1fc386..46d9c3d08608b0f3c060c86ae89cfe464bb8bb07 100644 --- a/crates/theme_importer/src/vscode/converter.rs +++ b/crates/theme_importer/src/vscode/converter.rs @@ -132,6 +132,11 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))?; + let vscode_editor_foreground = vscode_colors + .editor_foreground + .as_ref() + .traverse(|color| try_parse_color(&color))?; + let vscode_editor_background = vscode_colors .editor_background .as_ref() @@ -227,11 +232,7 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))? .or(vscode_editor_background), - editor_foreground: vscode_colors - .foreground - .as_ref() - .traverse(|color| try_parse_color(&color))? - .or(vscode_token_colors_foreground), + editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground), editor_background: vscode_editor_background, editor_gutter_background: vscode_editor_background, editor_line_number: vscode_colors From 2b278e69f73589c9bb277ab5c63f5d86453452c7 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 17:34:07 -0500 Subject: [PATCH 43/61] Use `editor_foreground` color in editor --- crates/editor2/src/editor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 89b5fd2efb91ff43f4683b0923f5658888ecc72a..e58aa1000d475caa009b045b89e36145c69b3894 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -9297,7 +9297,7 @@ impl Render for Editor { let settings = ThemeSettings::get_global(cx); let text_style = match self.mode { EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle { - color: cx.theme().colors().text, + color: cx.theme().colors().editor_foreground, font_family: settings.ui_font.family.clone(), font_features: settings.ui_font.features, font_size: rems(0.875).into(), @@ -9310,7 +9310,7 @@ impl Render for Editor { }, EditorMode::Full => TextStyle { - color: cx.theme().colors().text, + color: cx.theme().colors().editor_foreground, font_family: settings.buffer_font.family.clone(), font_features: settings.buffer_font.features, font_size: settings.buffer_font_size(cx).into(), From 4c63c74f920d11cff2ecefabd9e09fb001d076d1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 14 Dec 2023 15:23:24 -0800 Subject: [PATCH 44/61] Fix bug in drag move dispatch co-authored-by: conrad --- crates/gpui2/src/elements/div.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 395afdcc8846b8ee5a256bf59b3fb0ee2639acf3..1019e5f5e6379d9f28e330de153ccb3496c1bc14 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -213,7 +213,7 @@ pub trait InteractiveElement: Sized { listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, ) -> Self where - T: Render, + T: 'static, { self.interactivity().mouse_move_listeners.push(Box::new( move |event, bounds, phase, cx| { @@ -223,7 +223,7 @@ pub trait InteractiveElement: Sized { if cx .active_drag .as_ref() - .is_some_and(|drag| drag.value.type_id() == TypeId::of::()) + .is_some_and(|drag| (*drag.value).type_id() == TypeId::of::()) { (listener)( &DragMoveEvent { From bc3e6649f87ab468e0a564857dbeb23915cf0ef4 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 14 Dec 2023 18:52:14 -0500 Subject: [PATCH 45/61] Fix warning in release mode (#3662) This PR fixes a warning that was present in release mode, which was preventing the nightly builds from running: ``` error: variable does not need to be mutable --> crates/gpui2/src/elements/div.rs:547:9 | 547 | let mut div = Div { | ----^^^ | | | help: remove this `mut` | = note: `-D unused-mut` implied by `-D warnings` ``` Release Notes: - N/A --- crates/gpui2/src/elements/div.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 395afdcc8846b8ee5a256bf59b3fb0ee2639acf3..257ba64b8c325e634fbb6d52799b8ddae8e19e4e 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -544,17 +544,20 @@ pub type ActionListener = Box Div { - let mut div = Div { - interactivity: Interactivity::default(), - children: SmallVec::default(), + #[cfg(debug_assertions)] + let interactivity = { + let mut interactivity = Interactivity::default(); + interactivity.location = Some(*core::panic::Location::caller()); + interactivity }; - #[cfg(debug_assertions)] - { - div.interactivity.location = Some(*core::panic::Location::caller()); - } + #[cfg(not(debug_assertions))] + let interactivity = Interactivity::default(); - div + Div { + interactivity, + children: SmallVec::default(), + } } pub struct Div { From 8b4cf383798fd288f2e0c189b0f3af69b50775e3 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 14 Dec 2023 15:53:06 -0800 Subject: [PATCH 46/61] Fix dock resize handles co-authored-by: conrad --- crates/gpui2/src/elements/canvas.rs | 8 +++--- crates/workspace2/src/dock.rs | 39 ++++++++++++++++++----------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/crates/gpui2/src/elements/canvas.rs b/crates/gpui2/src/elements/canvas.rs index b3afd335d41d4544267a9453e8ffedea5c990b18..6a7d68e831bbc6809a96ccd367ceb85c67bcbf7d 100644 --- a/crates/gpui2/src/elements/canvas.rs +++ b/crates/gpui2/src/elements/canvas.rs @@ -27,7 +27,7 @@ impl IntoElement for Canvas { } impl Element for Canvas { - type State = (); + type State = Style; fn layout( &mut self, @@ -37,11 +37,11 @@ impl Element for Canvas { let mut style = Style::default(); style.refine(&self.style); let layout_id = cx.request_layout(&style, []); - (layout_id, ()) + (layout_id, style) } - fn paint(self, bounds: Bounds, _: &mut (), cx: &mut WindowContext) { - (self.paint_callback)(&bounds, cx) + fn paint(self, bounds: Bounds, style: &mut Style, cx: &mut WindowContext) { + style.paint(bounds, cx, |cx| (self.paint_callback)(&bounds, cx)); } } diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 54480f0b68748870530425f7b6d7910a065f2a0e..f76bb82177084f2e638e31e0e88bad8a3552a182 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -486,12 +486,9 @@ impl Render for Dock { if let Some(entry) = self.visible_entry() { let size = entry.panel.size(cx); - let mut pre_resize_handle = None; - let mut post_resize_handle = None; let position = self.position; - let handler = div() + let mut handle = div() .id("resize-handle") - .bg(cx.theme().colors().border) .on_drag(DraggedDock(position), |dock, cx| { cx.build_view(|_| dock.clone()) }) @@ -506,16 +503,31 @@ impl Render for Dock { match self.position() { DockPosition::Left => { - post_resize_handle = - Some(handler.min_w(HANDLE_SIZE).h_full().cursor_col_resize()) + handle = handle + .absolute() + .right(px(0.)) + .top(px(0.)) + .h_full() + .w(HANDLE_SIZE) + .cursor_col_resize(); } DockPosition::Bottom => { - pre_resize_handle = - Some(handler.w_full().min_h(HANDLE_SIZE).cursor_row_resize()) + handle = handle + .absolute() + .top(px(0.)) + .left(px(0.)) + .w_full() + .h(HANDLE_SIZE) + .cursor_row_resize(); } DockPosition::Right => { - pre_resize_handle = - Some(handler.min_w(HANDLE_SIZE).h_full().cursor_col_resize()) + handle = handle + .absolute() + .top(px(0.)) + .left(px(0.)) + .w_full() + .h(HANDLE_SIZE) + .cursor_col_resize(); } } @@ -531,16 +543,15 @@ impl Render for Dock { DockPosition::Right => this.border_l(), DockPosition::Bottom => this.border_t(), }) - .children(pre_resize_handle) .child( div() .map(|this| match self.position().axis() { - Axis::Horizontal => this.min_w(px(size) - HANDLE_SIZE).h_full(), - Axis::Vertical => this.min_h(px(size) - HANDLE_SIZE).w_full(), + Axis::Horizontal => this.min_w(px(size)).h_full(), + Axis::Vertical => this.min_h(px(size)).w_full(), }) .child(entry.panel.to_any()), ) - .children(post_resize_handle) + .child(handle) } else { div() } From ad8165ae797d48db42689424429bc5f2e195edf7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Dec 2023 17:20:27 -0700 Subject: [PATCH 47/61] Rename draw2 -> draw_and_update_state --- crates/editor2/src/element.rs | 2 +- crates/gpui2/src/element.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 831b6cd35ae8c80282ba312bc965fb8577e99bd3..0f1b565b9d818504e905ca2a668578ffafc04069 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -933,7 +933,7 @@ impl EditorElement { cx.stop_propagation(); }, )) - .draw2( + .draw_and_update_state( fold_bounds.origin, fold_bounds.size, cx, diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index f4bcb17f3c8180be4bef821092d6ef7ea1b0f88d..b446c2fe86eb4bd4ce12ddc183d55f58262aeaa9 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -23,7 +23,7 @@ pub trait IntoElement: Sized { self.into_element().into_any() } - fn draw2( + fn draw_and_update_state( self, origin: Point, available_space: Size, From d6383ab0c629e09bc5f8177e0db0fcc6e7dcfd06 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 16:20:19 -0800 Subject: [PATCH 48/61] Fix stickiness of main pane drag target Reimplement the pane drag targets using on_drag_move --- crates/gpui2/src/elements/div.rs | 6 +- crates/workspace2/src/pane.rs | 196 ++++++++++++++----------------- 2 files changed, 90 insertions(+), 112 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index aa77480944b77ebfa1a3d95b45f3e188ea289dea..fb730a0700dc870b7b5a74953e595848e1396291 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -33,6 +33,7 @@ pub struct GroupStyle { pub struct DragMoveEvent { pub event: MouseMoveEvent, + pub bounds: Bounds, drag: PhantomData, } @@ -208,7 +209,7 @@ pub trait InteractiveElement: Sized { self } - fn on_drag_move( + fn on_drag_move( mut self, listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, ) -> Self @@ -223,11 +224,12 @@ pub trait InteractiveElement: Sized { if cx .active_drag .as_ref() - .is_some_and(|drag| (*drag.value).type_id() == TypeId::of::()) + .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::()) { (listener)( &DragMoveEvent { event: event.clone(), + bounds: bounds.bounds, drag: PhantomData, }, cx, diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 4c436daada47625f8db784f11978129b3b4082c6..712e447905b4d3062ed686a695ddc01529557213 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -8,9 +8,10 @@ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AppContext, - AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable, - FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render, - ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, FocusHandle, + Focusable, FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, + Render, ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath}; @@ -28,8 +29,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, - Indicator, Label, Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, + Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -179,6 +180,7 @@ pub struct Pane { // tab_context_menu: ViewHandle, workspace: WeakView, project: Model, + drag_split_direction: Option, // can_drop: Rc, &WindowContext) -> bool>, can_split: bool, // render_tab_bar_buttons: Rc) -> AnyElement>, @@ -361,6 +363,7 @@ impl Pane { new_item_menu: None, split_item_menu: None, tab_bar_scroll_handle: ScrollHandle::new(), + drag_split_direction: None, // tab_bar_context_menu: TabBarContextMenu { // kind: TabBarContextMenuKind::New, // handle: context_menu, @@ -1503,9 +1506,11 @@ impl Pane { .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) .drag_over::(|tab| tab.bg(gpui::red())) .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, ix, cx) })) .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.drag_split_direction = None; this.handle_project_entry_drop(entry_id, cx) })) .when_some(item.tab_tooltip_text(cx), |tab, text| { @@ -1655,9 +1660,11 @@ impl Pane { }) .drag_over::(|bar| bar.bg(gpui::red())) .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, this.items.len(), cx) })) .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.drag_split_direction = None; this.handle_project_entry_drop(entry_id, cx) })), ) @@ -1719,18 +1726,42 @@ impl Pane { self.zoomed } + fn handle_drag_move(&mut self, event: &DragMoveEvent, cx: &mut ViewContext) { + let edge_width = cx.rem_size() * 8; + let cursor = event.event.position; + let direction = if cursor.x < event.bounds.left() + edge_width { + Some(SplitDirection::Left) + } else if cursor.x > event.bounds.right() - edge_width { + Some(SplitDirection::Right) + } else if cursor.y < event.bounds.top() + edge_width { + Some(SplitDirection::Up) + } else if cursor.y > event.bounds.bottom() - edge_width { + Some(SplitDirection::Down) + } else { + None + }; + if direction != self.drag_split_direction { + self.drag_split_direction = direction; + cx.notify(); + } + } + fn handle_tab_drop( &mut self, dragged_tab: &DraggedTab, ix: usize, cx: &mut ViewContext<'_, Pane>, ) { + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; let item_id = dragged_tab.item_id; let from_pane = dragged_tab.pane.clone(); - let to_pane = cx.view().clone(); self.workspace .update(cx, |_, cx| { cx.defer(move |workspace, cx| { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } workspace.move_item(from_pane, to_pane, item_id, ix, cx); }); }) @@ -1742,52 +1773,9 @@ impl Pane { project_entry_id: &ProjectEntryId, cx: &mut ViewContext<'_, Pane>, ) { - let to_pane = cx.view().downgrade(); - let project_entry_id = *project_entry_id; - self.workspace - .update(cx, |_, cx| { - cx.defer(move |workspace, cx| { - if let Some(path) = workspace - .project() - .read(cx) - .path_for_entry(project_entry_id, cx) - { - workspace - .open_path(path, Some(to_pane), true, cx) - .detach_and_log_err(cx); - } - }); - }) - .log_err(); - } - - fn handle_split_tab_drop( - &mut self, - dragged_tab: &DraggedTab, - split_direction: SplitDirection, - cx: &mut ViewContext<'_, Pane>, - ) { - let item_id = dragged_tab.item_id; - let from_pane = dragged_tab.pane.clone(); - let to_pane = cx.view().clone(); - self.workspace - .update(cx, |_, cx| { - cx.defer(move |workspace, cx| { - let pane = workspace.split_pane(to_pane, split_direction, cx); - workspace.move_item(from_pane, pane, item_id, 0, cx); - }); - }) - .log_err(); - } - - fn handle_split_project_entry_drop( - &mut self, - project_entry_id: &ProjectEntryId, - split_direction: SplitDirection, - cx: &mut ViewContext<'_, Pane>, - ) { + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; let project_entry_id = *project_entry_id; - let current_pane = cx.view().clone(); self.workspace .update(cx, |_, cx| { cx.defer(move |workspace, cx| { @@ -1796,9 +1784,11 @@ impl Pane { .read(cx) .path_for_entry(project_entry_id, cx) { - let pane = workspace.split_pane(current_pane, split_direction, cx); + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } workspace - .open_path(path, Some(pane.downgrade()), true, cx) + .open_path(path, Some(to_pane.downgrade()), true, cx) .detach_and_log_err(cx); } }); @@ -1817,6 +1807,9 @@ impl Render for Pane { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let mut drag_target_color = cx.theme().colors().text; + drag_target_color.a = 0.5; + v_stack() .key_context("Pane") .track_focus(&self.focus_handle) @@ -1894,71 +1887,54 @@ impl Render for Pane { }), ) .child(self.render_tab_bar(cx)) - .child(self.toolbar.clone()) - .child(if let Some(item) = self.active_item() { - let mut drag_target_color = cx.theme().colors().text; - drag_target_color.a = 0.5; - - div() - .flex() + .child( + // main content + v_stack() .flex_1() .relative() - .child(item.to_any()) + .group("") + .on_drag_move::(cx.listener(Self::handle_drag_move)) + .on_drag_move::(cx.listener(Self::handle_drag_move)) + .child(self.toolbar.clone()) + .map(|div| { + if let Some(item) = self.active_item() { + div.child(item.to_any()) + } else { + div.items_center().size_full().justify_center().child( + Label::new("Open a file or project to get started.") + .color(Color::Muted), + ) + } + }) .child( + // drag target div() + .invisible() .absolute() - .full() - .z_index(1) - .drag_over::(|style| style.bg(drag_target_color)) - .drag_over::(|style| style.bg(gpui::red())) - .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + .bg(drag_target_color) + .group_drag_over::("", |style| style.visible()) + .group_drag_over::("", |style| style.visible()) + .on_drop(cx.listener(move |this, dragged_tab, cx| { this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) })) - .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + .on_drop(cx.listener(move |this, entry_id, cx| { this.handle_project_entry_drop(entry_id, cx) - })), - ) - .children( - [ - (SplitDirection::Up, 2), - (SplitDirection::Down, 2), - (SplitDirection::Left, 3), - (SplitDirection::Right, 3), - ] - .into_iter() - .map(|(direction, z_index)| { - let div = div() - .absolute() - .z_index(z_index) - .invisible() - .bg(drag_target_color) - .drag_over::(|style| style.visible()) - .drag_over::(|style| style.visible()) - .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { - this.handle_split_tab_drop(dragged_tab, direction, cx) - })) - .on_drop(cx.listener( - move |this, entry_id: &ProjectEntryId, cx| { - this.handle_split_project_entry_drop( - entry_id, direction, cx, - ) - }, - )); - match direction { - SplitDirection::Up => div.top_0().left_0().right_0().h_32(), - SplitDirection::Down => div.left_0().bottom_0().right_0().h_32(), - SplitDirection::Left => div.top_0().left_0().bottom_0().w_32(), - SplitDirection::Right => div.top_0().bottom_0().right_0().w_32(), - } - }), - ) - } else { - h_stack() - .items_center() - .size_full() - .justify_center() - .child(Label::new("Open a file or project to get started.").color(Color::Muted)) - }) + })) + .map(|div| match self.drag_split_direction { + None => div.full(), + Some(SplitDirection::Up) => div.top_0().left_0().right_0().h_32(), + Some(SplitDirection::Down) => { + div.left_0().bottom_0().right_0().h_32() + } + Some(SplitDirection::Left) => { + div.top_0().left_0().bottom_0().w_32() + } + Some(SplitDirection::Right) => { + div.top_0().bottom_0().right_0().w_32() + } + }), + ), + ) .on_mouse_down( MouseButton::Navigate(NavigationDirection::Back), cx.listener(|pane, _, cx| { From 842f15c65b76d73701a10e4404ee51b4dabb2563 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 16:35:58 -0800 Subject: [PATCH 49/61] Fix centering of empty pane text --- crates/workspace2/src/pane.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 712e447905b4d3062ed686a695ddc01529557213..6b5a131ea0387fb9a0db9444a93122ce75388c6d 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1889,21 +1889,27 @@ impl Render for Pane { .child(self.render_tab_bar(cx)) .child( // main content - v_stack() + div() .flex_1() .relative() .group("") .on_drag_move::(cx.listener(Self::handle_drag_move)) .on_drag_move::(cx.listener(Self::handle_drag_move)) - .child(self.toolbar.clone()) .map(|div| { if let Some(item) = self.active_item() { - div.child(item.to_any()) + div.flex_col() + .child(self.toolbar.clone()) + .child(item.to_any()) } else { - div.items_center().size_full().justify_center().child( - Label::new("Open a file or project to get started.") - .color(Color::Muted), - ) + div.flex() + .flex_row() + .items_center() + .size_full() + .justify_center() + .child( + Label::new("Open a file or project to get started.") + .color(Color::Muted), + ) } }) .child( From 52b9fc303b6393712ec06f5113ce2c1c59e5dad3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 16:52:03 -0800 Subject: [PATCH 50/61] Fix overlay rendering when dragging onto the center of a pane --- crates/workspace2/src/pane.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 6b5a131ea0387fb9a0db9444a93122ce75388c6d..11588212ef76e3891c57fc5a14778fd8e48a9e37 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1927,7 +1927,7 @@ impl Render for Pane { this.handle_project_entry_drop(entry_id, cx) })) .map(|div| match self.drag_split_direction { - None => div.full(), + None => div.top_0().left_0().right_0().bottom_0(), Some(SplitDirection::Up) => div.top_0().left_0().right_0().h_32(), Some(SplitDirection::Down) => { div.left_0().bottom_0().right_0().h_32() From ead52067412636f9ad09663313a894d3f5635ad4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 14 Dec 2023 17:00:07 -0800 Subject: [PATCH 51/61] Fix todos in auto-update --- crates/auto_update2/src/auto_update.rs | 19 ++++++------------- .../auto_update2/src/update_notification.rs | 6 ++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/auto_update2/src/auto_update.rs b/crates/auto_update2/src/auto_update.rs index 31e474242ac7fd9cf918a785b35620ae7764df0e..4bf2bca0633ef1d9199750c38ae2c2d4ed0b2651 100644 --- a/crates/auto_update2/src/auto_update.rs +++ b/crates/auto_update2/src/auto_update.rs @@ -9,12 +9,14 @@ use gpui::{ ViewContext, VisualContext, }; use isahc::AsyncBody; + use serde::Deserialize; use serde_derive::Serialize; use smol::io::AsyncReadExt; use settings::{Settings, SettingsStore}; use smol::{fs::File, process::Command}; + use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; use util::channel::{AppCommitSha, ReleaseChannel}; @@ -24,16 +26,7 @@ use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); -//todo!(remove CheckThatAutoUpdaterWorks) -actions!( - auto_update, - [ - Check, - DismissErrorMessage, - ViewReleaseNotes, - CheckThatAutoUpdaterWorks - ] -); +actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]); #[derive(Serialize)] struct UpdateRequestBody { @@ -90,7 +83,10 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo cx.observe_new_views(|workspace: &mut Workspace, _cx| { workspace.register_action(|_, action: &Check, cx| check(action, cx)); + workspace.register_action(|_, action, cx| view_release_notes(action, cx)); + // @nate - code to trigger update notification on launch + // todo!("remove this when Nate is done") // workspace.show_notification(0, _cx, |cx| { // cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap())) // }); @@ -119,9 +115,6 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo updater }); cx.set_global(Some(auto_updater)); - //todo!(action) - // cx.add_global_action(view_release_notes); - // cx.add_action(UpdateNotification::dismiss); } } diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index 4a2efcf8076bb882a67c1f25b6300dd4cd48c1d1..8bb08912b01a13712afe2dbc14e34a9abd49bc24 100644 --- a/crates/auto_update2/src/update_notification.rs +++ b/crates/auto_update2/src/update_notification.rs @@ -2,6 +2,7 @@ use gpui::{ div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render, SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, }; +use menu::Cancel; use util::channel::ReleaseChannel; use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; @@ -18,6 +19,7 @@ impl Render for UpdateNotification { let app_name = cx.global::().display_name(); v_stack() + .on_action(cx.listener(UpdateNotification::dismiss)) .elevation_3(cx) .p_4() .child( @@ -32,7 +34,7 @@ impl Render for UpdateNotification { .id("cancel") .child(IconElement::new(Icon::Close)) .cursor_pointer() - .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), + .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))), ), ) .child( @@ -50,7 +52,7 @@ impl UpdateNotification { Self { version } } - pub fn dismiss(&mut self, cx: &mut ViewContext) { + pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext) { cx.emit(DismissEvent); } } From 6973b1b592cb77954c7324bf1b1216084cf6e8b5 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 14 Dec 2023 17:48:49 -0800 Subject: [PATCH 52/61] Add several red outlines --- crates/collab_ui2/src/collab_panel.rs | 23 +++- .../src/collab_panel/contact_finder.rs | 39 +------ crates/collab_ui2/src/collab_titlebar_item.rs | 106 ++++++++++-------- 3 files changed, 79 insertions(+), 89 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 65a994e6d94326e19245cb68120c1fdb35ea335b..4edf5ef35ba4495d5f3ef4922414d0f79cd46d7c 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2142,7 +2142,7 @@ impl CollabPanel { } fn render_signed_out(&mut self, cx: &mut ViewContext) -> Div { - v_stack().child( + v_stack().border_1().border_color(gpui::red()).child( Button::new("sign_in", "Sign in to collaborate").on_click(cx.listener( |this, _, cx| { let client = this.client.clone(); @@ -2301,9 +2301,14 @@ impl CollabPanel { .into_any_element() }), Section::Contacts => Some( - IconButton::new("add-contact", Icon::Plus) - .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) - .tooltip(|cx| Tooltip::text("Search for new contact", cx)) + div() + .border_1() + .border_color(gpui::red()) + .child( + IconButton::new("add-contact", Icon::Plus) + .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) + .tooltip(|cx| Tooltip::text("Search for new contact", cx)), + ) .into_any_element(), ), Section::Channels => Some( @@ -2323,7 +2328,7 @@ impl CollabPanel { | Section::Offline => true, }; - h_stack() + let mut row = h_stack() .w_full() .group("section-header") .child( @@ -2350,7 +2355,13 @@ impl CollabPanel { .detach_and_log_err(cx) }, )) - }) + }); + + if section == Section::Offline { + row = div().border_1().border_color(gpui::red()).child(row); + } + + row } fn render_contact( diff --git a/crates/collab_ui2/src/collab_panel/contact_finder.rs b/crates/collab_ui2/src/collab_panel/contact_finder.rs index bd0c5d4b074beef1e8a433c03eec71df4a2d2c7c..3087e6812f2afe93e2ba11a4a9bd836af8ec899a 100644 --- a/crates/collab_ui2/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui2/src/collab_panel/contact_finder.rs @@ -11,14 +11,8 @@ use ui::{prelude::*, Avatar}; use util::{ResultExt as _, TryFutureExt}; use workspace::ModalView; -pub fn init(cx: &mut AppContext) { - //Picker::::init(cx); - //cx.add_action(ContactFinder::dismiss) -} - pub struct ContactFinder { picker: View>, - has_focus: bool, } impl ContactFinder { @@ -31,16 +25,12 @@ impl ContactFinder { }; let picker = cx.build_view(|cx| Picker::new(delegate, cx)); - Self { - picker, - has_focus: false, - } + Self { picker } } pub fn set_query(&mut self, query: String, cx: &mut ViewContext) { self.picker.update(cx, |picker, cx| { - // todo!() - // picker.set_query(query, cx); + picker.set_query(query, cx); }); } } @@ -62,32 +52,9 @@ impl Render for ContactFinder { .w(rems(34.)) } - // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - // self.has_focus = true; - // if cx.is_self_focused() { - // cx.focus(&self.picker) - // } - // } - - // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { - // self.has_focus = false; - // } - type Element = Div; } -// impl Modal for ContactFinder { -// fn has_focus(&self) -> bool { -// self.has_focus -// } - -// fn dismiss_on_event(event: &Self::Event) -> bool { -// match event { -// PickerEvent::Dismiss => true, -// } -// } -// } - pub struct ContactFinderDelegate { parent: WeakView, potential_contacts: Arc<[Arc]>, @@ -161,7 +128,6 @@ impl PickerDelegate for ContactFinderDelegate { } fn dismissed(&mut self, cx: &mut ViewContext>) { - //cx.emit(PickerEvent::Dismiss); self.parent .update(cx, |_, cx| cx.emit(DismissEvent)) .log_err(); @@ -191,6 +157,7 @@ impl PickerDelegate for ContactFinderDelegate { .child(Label::new(user.github_login.clone())) .children(icon_path.map(|icon_path| svg().path(icon_path))), ) + // todo!() // Flex::row() // .with_children(user.avatar.clone().map(|avatar| { // Image::from_data(avatar) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 5b3d4c0942f30b33332f3be7913244eec8cd1dd9..2c48a66a1d7be20e155c08df1179eb203cddd614 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -233,56 +233,68 @@ impl Render for CollabTitlebarItem { }), ) }) - .child(h_stack().px_1p5().map(|this| { - if let Some(user) = current_user { - // TODO: Finish implementing user menu popover - // - this.child( - popover_menu("user-menu") - .menu(|cx| { - ContextMenu::build(cx, |menu, _| menu.header("ADADA")) - }) - .trigger( - ButtonLike::new("user-menu") - .child( - h_stack() - .gap_0p5() - .child(Avatar::new(user.avatar_uri.clone())) + .child( + h_stack() + .border_color(gpui::red()) + .border_1() + .px_1p5() + .map(|this| { + if let Some(user) = current_user { + // TODO: Finish implementing user menu popover + // + this.child( + popover_menu("user-menu") + .menu(|cx| { + ContextMenu::build(cx, |menu, _| { + menu.header("ADADA") + }) + }) + .trigger( + ButtonLike::new("user-menu") .child( - IconElement::new(Icon::ChevronDown) - .color(Color::Muted), - ), + h_stack() + .gap_0p5() + .child(Avatar::new( + user.avatar_uri.clone(), + )) + .child( + IconElement::new(Icon::ChevronDown) + .color(Color::Muted), + ), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| { + Tooltip::text("Toggle User Menu", cx) + }), ) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| { - Tooltip::text("Toggle User Menu", cx) - }), + .anchor(gpui::AnchorCorner::TopRight), ) - .anchor(gpui::AnchorCorner::TopRight), - ) - // this.child( - // ButtonLike::new("user-menu") - // .child( - // h_stack().gap_0p5().child(Avatar::data(avatar)).child( - // IconElement::new(Icon::ChevronDown).color(Color::Muted), - // ), - // ) - // .style(ButtonStyle::Subtle) - // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), - // ) - } else { - this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| { - let client = client.clone(); - cx.spawn(move |mut cx| async move { - client - .authenticate_and_connect(true, &cx) - .await - .notify_async_err(&mut cx); - }) - .detach(); - })) - } - })), + // this.child( + // ButtonLike::new("user-menu") + // .child( + // h_stack().gap_0p5().child(Avatar::data(avatar)).child( + // IconElement::new(Icon::ChevronDown).color(Color::Muted), + // ), + // ) + // .style(ButtonStyle::Subtle) + // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + // ) + } else { + this.child(Button::new("sign_in", "Sign in").on_click( + move |_, cx| { + let client = client.clone(); + cx.spawn(move |mut cx| async move { + client + .authenticate_and_connect(true, &cx) + .await + .notify_async_err(&mut cx); + }) + .detach(); + }, + )) + } + }), + ), ) } } From fbcaf96ab5bbdb1b47f301206ad91257dcdd2aed Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Dec 2023 20:58:59 -0500 Subject: [PATCH 53/61] Track focus on TestItem --- crates/project_panel2/src/project_panel.rs | 2 +- crates/workspace2/src/item.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index f7789c1a62af282840bc53acdcf5758a6257dd8c..51d04e046848ac60709834b3d36b2dd942616d6b 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1671,7 +1671,7 @@ mod tests { path::{Path, PathBuf}, sync::atomic::{self, AtomicUsize}, }; - use workspace::{dock::PanelHandle, AppState}; + use workspace::AppState; #[gpui::test] async fn test_visible_list(cx: &mut gpui::TestAppContext) { diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index c0242ffa170aedfd00516a0ef031988bee16cdcd..30410550fcabd10cd6104b8e2f1b9e6e4e228350 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -760,8 +760,9 @@ pub mod test { use super::{Item, ItemEvent}; use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ - AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView, - IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView, + AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, Focusable, + FocusableView, InteractiveElement, IntoElement, Model, Render, SharedString, Task, View, + ViewContext, VisualContext, WeakView, }; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; use std::{any::Any, cell::Cell, path::Path}; @@ -909,10 +910,10 @@ pub mod test { } impl Render for TestItem { - type Element = Div; + type Element = Focusable
; fn render(&mut self, _: &mut ViewContext) -> Self::Element { - gpui::div() + gpui::div().track_focus(&self.focus_handle) } } From ff4da878f9d0026f4cfc78c39b2cfdf8433394fa Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Dec 2023 21:33:11 -0500 Subject: [PATCH 54/61] Render panel in these tests --- crates/project_panel2/src/project_panel.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 51d04e046848ac60709834b3d36b2dd942616d6b..21989dd3ffb4ca6827955e53bf7a296f11391822 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1927,7 +1927,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "root1", cx); @@ -2556,7 +2561,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "src/", cx); From d4e09230cc103b8366ba58e3c493b1f34857016b Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Dec 2023 22:29:33 -0500 Subject: [PATCH 55/61] Give result-less project search a focus handle --- crates/search2/src/project_search.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index f1b0c16d5708241cd4421e9c7b88d762383ff532..25a44c4da2a4eb023075b06b1b4b2d030a4049ae 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -273,13 +273,15 @@ pub enum ViewEvent { impl EventEmitter for ProjectSearchView {} impl Render for ProjectSearchView { - type Element = Div; + type Element = AnyElement; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { if self.has_matches() { div() .flex_1() .size_full() .child(self.results_editor.clone()) + .into_any() } else { let model = self.model.read(cx); let has_no_results = model.no_results.unwrap_or(false); @@ -352,14 +354,20 @@ impl Render for ProjectSearchView { .max_w_96() .child(Label::new(text).size(LabelSize::Small)) }); - v_stack().flex_1().size_full().justify_center().child( - h_stack() - .size_full() - .justify_center() - .child(h_stack().flex_1()) - .child(v_stack().child(major_text).children(minor_text)) - .child(h_stack().flex_1()), - ) + v_stack() + .track_focus(&self.query_editor.focus_handle(cx)) + .flex_1() + .size_full() + .justify_center() + .child( + h_stack() + .size_full() + .justify_center() + .child(h_stack().flex_1()) + .child(v_stack().child(major_text).children(minor_text)) + .child(h_stack().flex_1()), + ) + .into_any() } } } From de523c2d8063610034e94ed803317107154a8977 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Dec 2023 23:49:44 -0500 Subject: [PATCH 56/61] Give correct focus handle when project search has no matches --- crates/gpui2/src/window.rs | 1 - crates/search2/src/project_search.rs | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e2f53f7347233241d7363a97ad495dcbcc38fcbe..1c0849785b69b3553bed6fff451c47ecec7c2570 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -489,7 +489,6 @@ impl<'a> WindowContext<'a> { #[cfg(any(test, feature = "test-support"))] { - println!("invalidating focus"); self.window.focus_invalidated = true; } diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index da48625b429b28d39be4a2f52336ac8e7c8357e2..167c6fece282f9d7619780497d8c357f7af2eebf 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -378,7 +378,11 @@ impl Render for ProjectSearchView { impl FocusableView for ProjectSearchView { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.results_editor.focus_handle(cx) + if self.has_matches() { + self.results_editor.focus_handle(cx) + } else { + self.query_editor.focus_handle(cx) + } } } From a6403aad1a246a5ca1013cac8c5c37407eada436 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Dec 2023 11:28:48 +0200 Subject: [PATCH 57/61] Remove extra nits, do not panic on clicking the buffer separator --- crates/editor2/src/element.rs | 4 ++-- crates/gpui2/src/platform/mac/metal_renderer.rs | 5 ----- crates/gpui2/src/view.rs | 11 ----------- crates/gpui2/src/window.rs | 2 -- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 0f1b565b9d818504e905ca2a668578ffafc04069..a6d4bc20b157d4f80a816d5dc78eb7980277cdc2 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2284,8 +2284,8 @@ impl EditorElement { .cursor_pointer() .hover(|style| style.bg(cx.theme().colors().element_hover)) .on_click(cx.listener(|_editor, _event, _cx| { - // TODO: Implement collapsing path headers - todo!("Clicking path header") + // todo!() Implement collapsing path headers + // todo!("Clicking path header") })) .child( h_stack() diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index 3210a53c634e81e5410e732c2971c86a553c808f..68768521ee474cd25c2e98c8d38c7ea669fba1e4 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -187,8 +187,6 @@ impl MetalRenderer { } pub fn draw(&mut self, scene: &Scene) { - let start = std::time::Instant::now(); - let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size = size( @@ -306,9 +304,6 @@ impl MetalRenderer { command_buffer.commit(); self.sprite_atlas.clear_textures(AtlasTextureKind::Path); - let duration_since_start = start.elapsed(); - println!("renderer draw: {:?}", duration_since_start); - command_buffer.wait_until_completed(); drawable.present(); } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index fb61190731bddf4859e3fcd5ebbc374976a4b9ca..1b4c2b6346fb56320737ab74a8057da589aba354 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -209,20 +209,9 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { - let start_time = std::time::Instant::now(); let (layout_id, mut rendered_element) = (self.layout)(self, cx); - let duration = start_time.elapsed(); - println!("request layout: {:?}", duration); - - let start_time = std::time::Instant::now(); cx.compute_layout(layout_id, available_space); - let duration = start_time.elapsed(); - println!("compute layout: {:?}", duration); - - let start_time = std::time::Instant::now(); (self.paint)(self, &mut rendered_element, cx); - let duration = start_time.elapsed(); - println!("paint: {:?}", duration); }) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 1c0849785b69b3553bed6fff451c47ecec7c2570..74c7204048d781a2dd9d01ae95cef28c85b78ab6 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1255,7 +1255,6 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { - let t0 = std::time::Instant::now(); self.window.dirty = false; self.window.drawing = true; @@ -1347,7 +1346,6 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; - eprintln!("window draw: {:?}", t0.elapsed()); scene } From 31ff7d40ed67ac55d19f1d4b051d27003765e86f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Dec 2023 11:34:00 +0200 Subject: [PATCH 58/61] Adjust copy/paste buffer only on the copy error action trigger --- crates/editor2/src/editor.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index e58aa1000d475caa009b045b89e36145c69b3894..664d1d7380634426aa4a8264c4ed66aef008c265 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -9739,12 +9739,8 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend }; highlighted_lines.push(line); } - let message = diagnostic.message; Arc::new(move |cx: &mut BlockContext| { - let message = message.clone(); let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into(); - let write_to_clipboard = cx.write_to_clipboard(ClipboardItem::new(message.clone())); - // TODO: Nate: We should tint the background of the block with the severity color // We need to extend the theme before we can do this v_stack() @@ -9754,7 +9750,6 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .bg(gpui::red()) .children(highlighted_lines.iter().map(|(line, highlights)| { let group_id = cx.block_id.to_string(); - h_stack() .group(group_id.clone()) .gap_2() @@ -9769,7 +9764,12 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) .visible_on_hover(group_id) - .on_click(cx.listener(move |_, _, cx| write_to_clipboard)) + .on_click(cx.listener({ + let message = diagnostic.message.clone(); + move |_, _, cx| { + cx.write_to_clipboard(ClipboardItem::new(message.clone())) + } + })) .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)), ), ) From 4bfe46f53a6eaacb02a748e8c33ed6ed24e4929c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Dec 2023 12:15:20 +0200 Subject: [PATCH 59/61] Restore zed1 behavior for buffer search deploy --- crates/search2/src/buffer_search.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index 45495e502700ee65c3015a9f6fd78bc22fe5053c..5db7aff73670161719188a8cff5051c4a4a46604 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -338,7 +338,9 @@ impl BufferSearchBar { pane.update(cx, |this, cx| { this.toolbar().update(cx, |this, cx| { if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, |this, cx| this.toggle(deploy, cx)); + search_bar.update(cx, |this, cx| { + this.deploy(deploy, cx); + }); return; } let view = cx.build_view(|cx| BufferSearchBar::new(cx)); @@ -1483,9 +1485,9 @@ mod tests { search_bar.select_all_matches(&SelectAllMatches, cx); }); assert!( - editor.update(cx, |this, cx| !this.is_focused(cx.window_context())), - "Should not switch focus to editor if SelectAllMatches does not find any matches" - ); + editor.update(cx, |this, cx| !this.is_focused(cx.window_context())), + "Should not switch focus to editor if SelectAllMatches does not find any matches" + ); search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); @@ -1651,6 +1653,7 @@ mod tests { assert_eq!(search_bar.search_options, SearchOptions::NONE); }); } + #[gpui::test] async fn test_replace_simple(cx: &mut TestAppContext) { let (editor, search_bar, cx) = init_test(cx); From 2b3d9deabe66e601e8b984a07188ade678e2689d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:07:25 +0100 Subject: [PATCH 60/61] Dismiss Recent Projects & VCS modals on ESC (#3671) Release Notes: - N/A --- crates/recent_projects2/src/recent_projects.rs | 5 ++++- crates/vcs_menu2/src/lib.rs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index dff6aa12ccc30f43766451d244619159c2a7c8bb..3ecf1180af535ea23190bd41ad2c8dd0e329810d 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -76,7 +76,10 @@ impl RecentProjects { let delegate = RecentProjectsDelegate::new(weak_workspace, workspace_locations, true); - RecentProjects::new(delegate, cx) + let modal = RecentProjects::new(delegate, cx); + cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) + .detach(); + modal }); } else { workspace.show_notification(0, cx, |cx| { diff --git a/crates/vcs_menu2/src/lib.rs b/crates/vcs_menu2/src/lib.rs index e867e04dcdb96229eb7513f891d60cc31ce1ec26..ca3b685aa60ad93f63cc2ba203fa52a184ff564b 100644 --- a/crates/vcs_menu2/src/lib.rs +++ b/crates/vcs_menu2/src/lib.rs @@ -65,8 +65,13 @@ impl ModalBranchList { ) -> Result<()> { // Modal branch picker has a longer trailoff than a popover one. let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?; - workspace.toggle_modal(cx, |cx| ModalBranchList { - picker: cx.build_view(|cx| Picker::new(delegate, cx)), + workspace.toggle_modal(cx, |cx| { + let modal = ModalBranchList { + picker: cx.build_view(|cx| Picker::new(delegate, cx)), + }; + cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) + .detach(); + modal }); Ok(()) From ff3f4f3027d7c4d03c8c163bac1158829e1af976 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:20:54 +0100 Subject: [PATCH 61/61] search: Reintroduce whole word switch (#3672) It seems to have been lost in the recent styling pass. Release Notes: - N/A --- crates/search2/src/project_search.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 167c6fece282f9d7619780497d8c357f7af2eebf..7bd60891ced8a78902d856a674c502c8c1546f4a 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -1536,13 +1536,30 @@ impl Render for ProjectSearchBar { cx, ) }) - .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) + .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) .on_click(cx.listener( |this, _, cx| { - this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + this.toggle_search_option( + SearchOptions::CASE_SENSITIVE, + cx, + ); }, )), ) + .child( + IconButton::new("project-search-whole-word", Icon::WholeWord) + .tooltip(|cx| { + Tooltip::for_action( + "Toggle whole word", + &ToggleWholeWord, + cx, + ) + }) + .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) + .on_click(cx.listener(|this, _, cx| { + this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + })), + ) }), ), );