From 5904bcf1c20638d63b244a1b2b038ec9a664ba1c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 Jan 2024 13:51:38 +0100 Subject: [PATCH 01/98] Use taffy to retrieve the parent for a given layout node --- Cargo.lock | 7 +++--- crates/gpui/Cargo.toml | 2 +- crates/gpui/src/taffy.rs | 46 ++++++++++++++++------------------------ 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54e2f483d8a655f3d5e5c6e200b918275f968a34..ca55567cbb2a78661cb3c91b3e31c89b281bb9b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3091,9 +3091,9 @@ dependencies = [ [[package]] name = "grid" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df00eed8d1f0db937f6be10e46e8072b0671accb504cf0f959c5c52c679f5b9" +checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" [[package]] name = "h2" @@ -7665,11 +7665,12 @@ dependencies = [ [[package]] name = "taffy" version = "0.3.11" -source = "git+https://github.com/DioxusLabs/taffy?rev=1876f72bee5e376023eaa518aa7b8a34c769bd1b#1876f72bee5e376023eaa518aa7b8a34c769bd1b" +source = "git+https://github.com/zed-industries/taffy?rev=5e6c2d23e70e9f2156911d11050cb686362ba277#5e6c2d23e70e9f2156911d11050cb686362ba277" dependencies = [ "arrayvec 0.7.4", "grid", "num-traits", + "serde", "slotmap", ] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 6ea3524fcc4eb2a60ae2411f7eed6a2ef05a17a8..118753aafea5981e0dd2119f9b4203c70b85695d 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -46,7 +46,7 @@ serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true smol.workspace = true -taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" } +taffy = { git = "https://github.com/zed-industries/taffy", rev = "5e6c2d23e70e9f2156911d11050cb686362ba277" } thiserror.workspace = true time.workspace = true tiny-skia = "0.5" diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 0ebd394217ae06c6a2256281f389ab506bfca934..a77127e80176f6f74ca5f789a2570d0a630f9a33 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -6,15 +6,12 @@ use collections::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ - geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, - style::AvailableSpace as TaffyAvailableSpace, - tree::NodeId, - Taffy, + AvailableSpace as TaffyAvailableSpace, NodeId, Point as TaffyPoint, Rect as TaffyRect, + Size as TaffySize, TaffyTree, TraversePartialTree, }; pub struct TaffyLayoutEngine { - taffy: Taffy, - children_to_parents: FxHashMap, + tree: TaffyTree, absolute_layout_bounds: FxHashMap>, computed_layouts: FxHashSet, nodes_to_measure: FxHashMap< @@ -34,8 +31,7 @@ static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by constructi impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { - taffy: Taffy::new(), - children_to_parents: FxHashMap::default(), + tree: TaffyTree::new(), absolute_layout_bounds: FxHashMap::default(), computed_layouts: FxHashSet::default(), nodes_to_measure: FxHashMap::default(), @@ -43,8 +39,7 @@ impl TaffyLayoutEngine { } pub fn clear(&mut self) { - self.taffy.clear(); - self.children_to_parents.clear(); + self.tree.clear(); self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); self.nodes_to_measure.clear(); @@ -58,18 +53,13 @@ impl TaffyLayoutEngine { ) -> LayoutId { let style = style.to_taffy(rem_size); if children.is_empty() { - self.taffy.new_leaf(style).expect(EXPECT_MESSAGE).into() + self.tree.new_leaf(style).expect(EXPECT_MESSAGE).into() } else { - let parent_id = self - .taffy + self.tree // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. .new_with_children(style, unsafe { std::mem::transmute(children) }) .expect(EXPECT_MESSAGE) - .into(); - for child_id in children { - self.children_to_parents.insert(*child_id, parent_id); - } - parent_id + .into() } } @@ -83,7 +73,7 @@ impl TaffyLayoutEngine { let style = style.to_taffy(rem_size); let layout_id = self - .taffy + .tree .new_leaf_with_context(style, ()) .expect(EXPECT_MESSAGE) .into(); @@ -96,7 +86,7 @@ impl TaffyLayoutEngine { fn count_all_children(&self, parent: LayoutId) -> anyhow::Result { let mut count = 0; - for child in self.taffy.children(parent.0)? { + for child in self.tree.children(parent.0)? { // Count this child. count += 1; @@ -112,12 +102,12 @@ impl TaffyLayoutEngine { fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result { println!( "{parent:?} at depth {depth} has {} children", - self.taffy.child_count(parent.0)? + self.tree.child_count(parent.0) ); let mut max_child_depth = 0; - for child in self.taffy.children(parent.0)? { + for child in self.tree.children(parent.0)? { max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?); } @@ -129,7 +119,7 @@ impl TaffyLayoutEngine { fn get_edges(&self, parent: LayoutId) -> anyhow::Result> { let mut edges = Vec::new(); - for child in self.taffy.children(parent.0)? { + for child in self.tree.children(parent.0)? { edges.push((parent, LayoutId(child))); edges.extend(self.get_edges(LayoutId(child))?); @@ -162,7 +152,7 @@ impl TaffyLayoutEngine { while let Some(id) = stack.pop() { self.absolute_layout_bounds.remove(&id); stack.extend( - self.taffy + self.tree .children(id.into()) .expect(EXPECT_MESSAGE) .into_iter() @@ -172,7 +162,7 @@ impl TaffyLayoutEngine { } // let started_at = std::time::Instant::now(); - self.taffy + self.tree .compute_layout_with_measure( id.into(), available_space.into(), @@ -199,14 +189,14 @@ impl TaffyLayoutEngine { return layout; } - let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE); + let layout = self.tree.layout(id.into()).expect(EXPECT_MESSAGE); let mut bounds = Bounds { origin: layout.location.into(), size: layout.size.into(), }; - if let Some(parent_id) = self.children_to_parents.get(&id).copied() { - let parent_bounds = self.layout_bounds(parent_id); + if let Some(parent_id) = self.tree.parent(id.0) { + let parent_bounds = self.layout_bounds(parent_id.into()); bounds.origin += parent_bounds.origin; } self.absolute_layout_bounds.insert(id, bounds); From 84c36066bcc20e3ef854d1527f01595e1e93fdf7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 Jan 2024 19:07:20 +0100 Subject: [PATCH 02/98] Start on caching views Co-Authored-By: Nathan Sobo --- crates/gpui/src/geometry.rs | 6 +- crates/gpui/src/style.rs | 2 +- crates/gpui/src/taffy.rs | 28 +++++-- crates/gpui/src/view.rs | 122 +++++++++++++++++++---------- crates/gpui/src/window.rs | 9 +++ crates/workspace/src/dock.rs | 2 +- crates/workspace/src/pane_group.rs | 6 +- 7 files changed, 117 insertions(+), 58 deletions(-) diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index a50de8c344247c705d24c8d9a0e94c7c799278bb..89e47994a34b76d4310e3d8afe0c15be411608be 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -2272,7 +2272,7 @@ impl From for GlobalPixels { /// For example, if the root element's font-size is `16px`, then `1rem` equals `16px`. A length of `2rems` would then be `32px`. /// /// [set_rem_size]: crate::WindowContext::set_rem_size -#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)] +#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg, PartialEq)] pub struct Rems(pub f32); impl Mul for Rems { @@ -2295,7 +2295,7 @@ impl Debug for Rems { /// affected by the current font size, or a number of rems, which is relative to the font size of /// the root element. It is used for specifying dimensions that are either independent of or /// related to the typographic scale. -#[derive(Clone, Copy, Debug, Neg)] +#[derive(Clone, Copy, Debug, Neg, PartialEq)] pub enum AbsoluteLength { /// A length in pixels. Pixels(Pixels), @@ -2366,7 +2366,7 @@ impl Default for AbsoluteLength { /// This enum represents lengths that have a specific value, as opposed to lengths that are automatically /// determined by the context. It includes absolute lengths in pixels or rems, and relative lengths as a /// fraction of the parent's size. -#[derive(Clone, Copy, Neg)] +#[derive(Clone, Copy, Neg, PartialEq)] pub enum DefiniteLength { /// An absolute length specified in pixels or rems. Absolute(AbsoluteLength), diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 244ccebf2498fb9ff275d0818215a9ba658ffc02..67b9b855d26ac159566daf748c4a8136b83f5cc0 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -146,7 +146,7 @@ pub enum WhiteSpace { Nowrap, } -#[derive(Refineable, Clone, Debug)] +#[derive(Refineable, Clone, Debug, PartialEq)] #[refineable(Debug)] pub struct TextStyle { pub color: Hsla, diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index a77127e80176f6f74ca5f789a2570d0a630f9a33..430d928cbf5bdc55f27928a9d5c65bb6aa2645d7 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -12,6 +12,7 @@ use taffy::{ pub struct TaffyLayoutEngine { tree: TaffyTree, + styles: FxHashMap, absolute_layout_bounds: FxHashMap>, computed_layouts: FxHashSet, nodes_to_measure: FxHashMap< @@ -32,6 +33,7 @@ impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { tree: TaffyTree::new(), + styles: FxHashMap::default(), absolute_layout_bounds: FxHashMap::default(), computed_layouts: FxHashSet::default(), nodes_to_measure: FxHashMap::default(), @@ -43,6 +45,11 @@ impl TaffyLayoutEngine { self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); self.nodes_to_measure.clear(); + self.styles.clear(); + } + + pub fn requested_style(&self, layout_id: LayoutId) -> Option<&Style> { + self.styles.get(&layout_id) } pub fn request_layout( @@ -51,16 +58,21 @@ impl TaffyLayoutEngine { rem_size: Pixels, children: &[LayoutId], ) -> LayoutId { - let style = style.to_taffy(rem_size); - if children.is_empty() { - self.tree.new_leaf(style).expect(EXPECT_MESSAGE).into() + let taffy_style = style.to_taffy(rem_size); + let layout_id = if children.is_empty() { + self.tree + .new_leaf(taffy_style) + .expect(EXPECT_MESSAGE) + .into() } else { self.tree // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. - .new_with_children(style, unsafe { std::mem::transmute(children) }) + .new_with_children(taffy_style, unsafe { std::mem::transmute(children) }) .expect(EXPECT_MESSAGE) .into() - } + }; + self.styles.insert(layout_id, style.clone()); + layout_id } pub fn request_measured_layout( @@ -70,14 +82,16 @@ impl TaffyLayoutEngine { measure: impl FnMut(Size>, Size, &mut WindowContext) -> Size + 'static, ) -> LayoutId { - let style = style.to_taffy(rem_size); + let style = style.clone(); + let taffy_style = style.to_taffy(rem_size); let layout_id = self .tree - .new_leaf_with_context(style, ()) + .new_leaf_with_context(taffy_style, ()) .expect(EXPECT_MESSAGE) .into(); self.nodes_to_measure.insert(layout_id, Box::new(measure)); + self.styles.insert(layout_id, style.clone()); layout_id } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 4472da02e71fda1bb17d4353056b67ad58639813..7d9b8092ee10531f6c80d1d874016bb8ebd00108 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,8 +1,8 @@ use crate::{ seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, - Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, - LayoutId, Model, Pixels, Point, Render, Size, ViewContext, VisualContext, WeakModel, - WindowContext, + Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, + IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, TextStyle, + ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; use std::{ @@ -17,6 +17,19 @@ pub struct View { impl Sealed for View {} +pub struct AnyViewState { + root_style: Style, + cache_key: Option, + element: Option, +} + +struct ViewCacheKey { + bounds: Bounds, + stacking_order: StackingOrder, + content_mask: ContentMask, + text_style: TextStyle, +} + impl Entity for View { type Weak = WeakView; @@ -60,16 +73,6 @@ impl View { self.model.read(cx) } - // pub fn render_with(&self, component: E) -> RenderViewWith - // where - // E: 'static + Element, - // { - // RenderViewWith { - // view: self.clone(), - // element: Some(component), - // } - // } - pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle where V: FocusableView, @@ -183,16 +186,20 @@ impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, - layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), + request_layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), + cache: bool, } impl AnyView { + pub fn cached(mut self) -> Self { + self.cache = true; + self + } + pub fn downgrade(&self) -> AnyWeakView { AnyWeakView { model: self.model.downgrade(), - layout: self.layout, - paint: self.paint, + layout: self.request_layout, } } @@ -201,8 +208,8 @@ impl AnyView { Ok(model) => Ok(View { model }), Err(model) => Err(Self { model, - layout: self.layout, - paint: self.paint, + request_layout: self.request_layout, + cache: self.cache, }), } } @@ -222,9 +229,9 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, mut rendered_element) = (self.layout)(self, cx); + let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); cx.compute_layout(layout_id, available_space); - (self.paint)(self, &mut rendered_element, cx); + rendered_element.paint(cx); }) } } @@ -233,30 +240,65 @@ impl From> for AnyView { fn from(value: View) -> Self { AnyView { model: value.model.into_any(), - layout: any_view::layout::, - paint: any_view::paint, + request_layout: any_view::request_layout::, + cache: false, } } } impl Element for AnyView { - type State = Option; + type State = AnyViewState; fn request_layout( &mut self, - _state: Option, + state: Option, cx: &mut WindowContext, ) -> (LayoutId, Self::State) { - let (layout_id, state) = (self.layout)(self, cx); - (layout_id, Some(state)) + if self.cache { + if let Some(state) = state { + let layout_id = cx.request_layout(&state.root_style, None); + return (layout_id, state); + } + } + + let (layout_id, element) = (self.request_layout)(self, cx); + let root_style = cx.layout_style(layout_id).unwrap().clone(); + let state = AnyViewState { + root_style, + cache_key: None, + element: Some(element), + }; + (layout_id, state) } - 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.as_mut().unwrap(), cx) + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + if !self.cache { + state.element.take().unwrap().paint(cx); + return; + } + + if let Some(cache_key) = state.cache_key.as_mut() { + if cache_key.bounds == bounds + && cache_key.content_mask == cx.content_mask() + && cache_key.stacking_order == *cx.stacking_order() + && cache_key.text_style == cx.text_style() + { + println!("could reuse geometry for view {}", self.entity_id()); + } + } + + let mut element = state + .element + .take() + .unwrap_or_else(|| (self.request_layout)(self, cx).1); + element.draw(bounds.origin, bounds.size.into(), cx); + + state.cache_key = Some(ViewCacheKey { + bounds, + stacking_order: cx.stacking_order().clone(), + content_mask: cx.content_mask(), + text_style: cx.text_style(), + }); } } @@ -287,7 +329,6 @@ impl IntoElement for AnyView { pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), } impl AnyWeakView { @@ -295,8 +336,8 @@ impl AnyWeakView { let model = self.model.upgrade()?; Some(AnyView { model, - layout: self.layout, - paint: self.paint, + request_layout: self.layout, + cache: false, }) } } @@ -305,8 +346,7 @@ impl From> for AnyWeakView { fn from(view: WeakView) -> Self { Self { model: view.model.into(), - layout: any_view::layout::, - paint: any_view::paint, + layout: any_view::request_layout::, } } } @@ -328,7 +368,7 @@ impl std::fmt::Debug for AnyWeakView { mod any_view { use crate::{AnyElement, AnyView, IntoElement, LayoutId, Render, WindowContext}; - pub(crate) fn layout( + pub(crate) fn request_layout( view: &AnyView, cx: &mut WindowContext, ) -> (LayoutId, AnyElement) { @@ -337,8 +377,4 @@ mod any_view { let layout_id = element.request_layout(cx); (layout_id, element) } - - pub(crate) fn paint(_view: &AnyView, element: &mut AnyElement, cx: &mut WindowContext) { - element.paint(cx); - } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e4c5f93f95e6ea770d404a63a3e6795d9a4be7d..edd98e8385a324ca91b2a8d04b8ab7107096ecf8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -754,6 +754,14 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + pub fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> { + self.window + .layout_engine + .as_ref() + .unwrap() + .requested_style(layout_id) + } + pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { let mut layout_engine = self.window.layout_engine.take().unwrap(); layout_engine.compute_layout(layout_id, available_space, self); @@ -1313,6 +1321,7 @@ 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 { + println!("====================="); self.window.dirty = false; self.window.drawing = true; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c13a00b11c897b46ef5e2ac69ae10848c573ebf2..8a16ce5ab6ce7ebf026cbef03f6dbaf883f0f18d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -601,7 +601,7 @@ impl Render for Dock { Axis::Horizontal => this.min_w(size).h_full(), Axis::Vertical => this.min_h(size).w_full(), }) - .child(entry.panel.to_any()), + .child(entry.panel.to_any().cached()), ) .child(handle) } else { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a7368f61360ce6639dffa26ffd31f2114cd2216b..236daf60f8a747f4d43f4bc0a73109cd16e2ad3e 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -3,8 +3,8 @@ use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use collections::HashMap; use gpui::{ - point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View, - ViewContext, + point, size, AnyView, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, + Point, View, ViewContext, }; use parking_lot::Mutex; use project::Project; @@ -244,7 +244,7 @@ impl Member { .relative() .flex_1() .size_full() - .child(pane.clone()) + .child(AnyView::from(pane.clone()).cached()) .when_some(leader_border, |this, color| { this.child( div() From c9193b586b7a53ddd29308bcc19f660b7b8e05a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 Jan 2024 19:31:50 +0100 Subject: [PATCH 03/98] WIP --- crates/gpui/src/view.rs | 53 +++++++++++++++++++++------------------ crates/gpui/src/window.rs | 33 +++++++++++++++++++++++- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 7d9b8092ee10531f6c80d1d874016bb8ebd00108..9d504627ab185212df7df14399dfc54bcf1d6b46 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -95,7 +95,7 @@ impl Element for View { } fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { - element.take().unwrap().paint(cx); + cx.with_view_id(self.entity_id(), |cx| element.take().unwrap().paint(cx)); } } @@ -272,33 +272,36 @@ impl Element for AnyView { } fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - if !self.cache { - state.element.take().unwrap().paint(cx); - return; - } + cx.with_view_id(self.entity_id(), |cx| { + if !self.cache { + state.element.take().unwrap().paint(cx); + return; + } - if let Some(cache_key) = state.cache_key.as_mut() { - if cache_key.bounds == bounds - && cache_key.content_mask == cx.content_mask() - && cache_key.stacking_order == *cx.stacking_order() - && cache_key.text_style == cx.text_style() - { - println!("could reuse geometry for view {}", self.entity_id()); + if let Some(cache_key) = state.cache_key.as_mut() { + if cache_key.bounds == bounds + && cache_key.content_mask == cx.content_mask() + && cache_key.stacking_order == *cx.stacking_order() + && cache_key.text_style == cx.text_style() + && !cx.window.dirty_views.contains(&self.entity_id()) + { + println!("could reuse geometry for view {}", self.entity_id()); + } } - } - let mut element = state - .element - .take() - .unwrap_or_else(|| (self.request_layout)(self, cx).1); - element.draw(bounds.origin, bounds.size.into(), cx); - - state.cache_key = Some(ViewCacheKey { - bounds, - stacking_order: cx.stacking_order().clone(), - content_mask: cx.content_mask(), - text_style: cx.text_style(), - }); + let mut element = state + .element + .take() + .unwrap_or_else(|| (self.request_layout)(self, cx).1); + element.draw(bounds.origin, bounds.size.into(), cx); + + state.cache_key = Some(ViewCacheKey { + bounds, + stacking_order: cx.stacking_order().clone(), + content_mask: cx.content_mask(), + text_style: cx.text_style(), + }); + }) } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index edd98e8385a324ca91b2a8d04b8ab7107096ecf8..98c81d3883122066edef4254d90b8adb6ba0ad3c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -12,7 +12,7 @@ use crate::{ VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::FxHashMap; +use collections::{FxHashMap, FxHashSet}; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -256,6 +256,7 @@ pub struct Window { pub(crate) element_id_stack: GlobalElementId, pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, + pub(crate) dirty_views: FxHashSet, frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, @@ -295,6 +296,8 @@ pub(crate) struct Frame { pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, + pub(crate) view_parents: FxHashMap, + pub(crate) view_stack: Vec, } impl Frame { @@ -310,6 +313,8 @@ impl Frame { depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), + view_parents: FxHashMap::default(), + view_stack: Vec::new(), } } @@ -319,6 +324,8 @@ impl Frame { self.dispatch_tree.clear(); self.depth_map.clear(); self.next_stacking_order_id = 0; + self.view_parents.clear(); + debug_assert!(self.view_stack.is_empty()); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -404,6 +411,7 @@ impl Window { element_id_stack: GlobalElementId::default(), rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), + dirty_views: FxHashSet::default(), frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), @@ -1423,6 +1431,7 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; + self.window.dirty_views.clear(); ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); scene @@ -2119,6 +2128,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { result } + fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + self.window_mut().next_frame.view_stack.push(view_id); + let result = f(self); + self.window_mut().next_frame.view_stack.pop(); + result + } + /// Update the global element offset relative to the current offset. This is used to implement /// scrolling. fn with_element_offset( @@ -2476,6 +2492,21 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn notify(&mut self) { + let mut dirty_view_id = Some(self.view.entity_id()); + while let Some(view_id) = dirty_view_id { + if self.window_cx.window.dirty_views.insert(view_id) { + dirty_view_id = self + .window_cx + .window + .rendered_frame + .view_parents + .get(&view_id) + .copied(); + } else { + break; + } + } + if !self.window.drawing { self.window_cx.notify(); self.window_cx.app.push_effect(Effect::Notify { From f55870f378f41e8f9d093a4a2f683590cd9b75d1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 12:37:24 +0100 Subject: [PATCH 04/98] Reuse mouse and keyboard listeners when reusing geometry for a view --- crates/gpui/src/key_dispatch.rs | 81 ++++++++++++++++++++--- crates/gpui/src/view.rs | 3 +- crates/gpui/src/window.rs | 113 ++++++++++++++++---------------- 3 files changed, 131 insertions(+), 66 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 22c4dffc03a78df8fde5530a3059887e91a2b876..9019670b04f4c66ea76761026a73a8fd6a0931dc 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -1,12 +1,13 @@ use crate::{ - arena::ArenaRef, Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, - KeyMatch, Keymap, Keystroke, KeystrokeMatcher, WindowContext, + Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, KeyMatch, + Keymap, Keystroke, KeystrokeMatcher, WindowContext, }; -use collections::HashMap; +use collections::FxHashMap; use parking_lot::Mutex; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + mem, rc::Rc, sync::Arc, }; @@ -18,8 +19,9 @@ pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, nodes: Vec, - focusable_node_ids: HashMap, - keystroke_matchers: HashMap, KeystrokeMatcher>, + focusable_node_ids: FxHashMap, + view_node_ids: FxHashMap, + keystroke_matchers: FxHashMap, KeystrokeMatcher>, keymap: Arc>, action_registry: Rc, } @@ -30,15 +32,16 @@ pub(crate) struct DispatchNode { pub action_listeners: Vec, pub context: Option, focus_id: Option, + view_id: Option, parent: Option, } -type KeyListener = ArenaRef; +type KeyListener = Rc; #[derive(Clone)] pub(crate) struct DispatchActionListener { pub(crate) action_type: TypeId, - pub(crate) listener: ArenaRef, + pub(crate) listener: Rc, } impl DispatchTree { @@ -47,8 +50,9 @@ impl DispatchTree { node_stack: Vec::new(), context_stack: Vec::new(), nodes: Vec::new(), - focusable_node_ids: HashMap::default(), - keystroke_matchers: HashMap::default(), + focusable_node_ids: FxHashMap::default(), + view_node_ids: FxHashMap::default(), + keystroke_matchers: FxHashMap::default(), keymap, action_registry, } @@ -59,6 +63,7 @@ impl DispatchTree { self.nodes.clear(); self.context_stack.clear(); self.focusable_node_ids.clear(); + self.view_node_ids.clear(); self.keystroke_matchers.clear(); } @@ -83,6 +88,56 @@ impl DispatchTree { } } + fn move_node(&mut self, source_node: &mut DispatchNode) { + self.push_node(source_node.context.take()); + if let Some(focus_id) = source_node.focus_id { + self.make_focusable(focus_id); + } + if let Some(view_id) = source_node.view_id { + self.associate_view(view_id); + } + + let target_node = self.active_node(); + target_node.key_listeners = mem::take(&mut source_node.key_listeners); + target_node.action_listeners = mem::take(&mut source_node.action_listeners); + } + + pub fn graft(&mut self, view_id: EntityId, source: &mut Self) { + let view_source_node_id = source + .view_node_ids + .get(&view_id) + .expect("view should exist in previous dispatch tree"); + let view_source_node = &mut source.nodes[view_source_node_id.0]; + self.move_node(view_source_node); + + let mut source_stack = vec![*view_source_node_id]; + for (source_node_id, source_node) in source + .nodes + .iter_mut() + .enumerate() + .skip(view_source_node_id.0 + 1) + { + let source_node_id = DispatchNodeId(source_node_id); + while let Some(source_ancestor) = source_stack.last() { + if source_node.parent != Some(*source_ancestor) { + source_stack.pop(); + self.pop_node(); + } + } + + if source_stack.is_empty() { + break; + } else { + source_stack.push(source_node_id); + self.move_node(source_node); + } + } + + while !source_stack.is_empty() { + self.pop_node(); + } + } + pub fn clear_pending_keystrokes(&mut self) { self.keystroke_matchers.clear(); } @@ -117,7 +172,7 @@ impl DispatchTree { pub fn on_action( &mut self, action_type: TypeId, - listener: ArenaRef, + listener: Rc, ) { self.active_node() .action_listeners @@ -133,6 +188,12 @@ impl DispatchTree { self.focusable_node_ids.insert(focus_id, node_id); } + pub fn associate_view(&mut self, view_id: EntityId) { + let node_id = self.active_node_id(); + self.active_node().view_id = Some(view_id); + self.view_node_ids.insert(view_id, node_id); + } + pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { if parent == child { return true; diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 9d504627ab185212df7df14399dfc54bcf1d6b46..24bc00ce39796890158bc6890e0fb779801a73b3 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -285,7 +285,8 @@ impl Element for AnyView { && cache_key.text_style == cx.text_style() && !cx.window.dirty_views.contains(&self.entity_id()) { - println!("could reuse geometry for view {}", self.entity_id()); + cx.reuse_geometry(); + return; } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 98c81d3883122066edef4254d90b8adb6ba0ad3c..e5f9195b692409ec176d40d6014a22b5ceaaa6f8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,8 +1,8 @@ use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, - AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, - DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, - Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, + AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, + DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, + EntityId, 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, @@ -95,7 +95,7 @@ impl DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyMouseListener = ArenaBox; +type AnyMouseListener = Box; type AnyWindowFocusListener = Box bool + 'static>; struct FocusEvent { @@ -257,7 +257,6 @@ pub struct Window { pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, pub(crate) dirty_views: FxHashSet, - frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, blur_listeners: SubscriberSet<(), AnyObserver>, @@ -288,7 +287,7 @@ pub(crate) struct ElementStateBox { pub(crate) struct Frame { focus: Option, pub(crate) element_states: FxHashMap, - mouse_listeners: FxHashMap>, + mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene_builder: SceneBuilder, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, @@ -298,6 +297,7 @@ pub(crate) struct Frame { element_offset_stack: Vec>, pub(crate) view_parents: FxHashMap, pub(crate) view_stack: Vec, + pub(crate) reused_views: FxHashSet, } impl Frame { @@ -315,6 +315,7 @@ impl Frame { element_offset_stack: Vec::new(), view_parents: FxHashMap::default(), view_stack: Vec::new(), + reused_views: FxHashSet::default(), } } @@ -326,6 +327,7 @@ impl Frame { self.next_stacking_order_id = 0; self.view_parents.clear(); debug_assert!(self.view_stack.is_empty()); + self.reused_views.clear(); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -412,7 +414,6 @@ impl Window { rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), dirty_views: FxHashSet::default(), - frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), blur_listeners: SubscriberSet::new(), @@ -886,21 +887,21 @@ impl<'a> WindowContext<'a> { mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { let order = self.window.next_frame.z_index_stack.clone(); - let handler = self - .window - .frame_arena - .alloc(|| { - move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { - handler(event.downcast_ref().unwrap(), phase, cx) - } - }) - .map(|handler| handler as _); + let view_id = *self.window.next_frame.view_stack.last().unwrap(); self.window .next_frame .mouse_listeners .entry(TypeId::of::()) .or_default() - .push((order, handler)) + .push(( + order, + view_id, + Box::new( + move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { + handler(event.downcast_ref().unwrap(), phase, cx) + }, + ), + )) } /// Register a key event listener on the window for the next frame. The type of event @@ -913,21 +914,13 @@ impl<'a> WindowContext<'a> { &mut self, listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let listener = self - .window - .frame_arena - .alloc(|| { - move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| { - if let Some(event) = event.downcast_ref::() { - listener(event, phase, cx) - } + self.window.next_frame.dispatch_tree.on_key_event(Rc::new( + move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| { + if let Some(event) = event.downcast_ref::() { + listener(event, phase, cx) } - }) - .map(|handler| handler as _); - self.window - .next_frame - .dispatch_tree - .on_key_event(ArenaRef::from(listener)); + }, + )); } /// Register an action listener on the window for the next frame. The type of action @@ -941,15 +934,10 @@ impl<'a> WindowContext<'a> { action_type: TypeId, listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, ) { - let listener = self - .window - .frame_arena - .alloc(|| listener) - .map(|handler| handler as _); self.window .next_frame .dispatch_tree - .on_action(action_type, ArenaRef::from(listener)); + .on_action(action_type, Rc::new(listener)); } pub fn is_action_available(&self, action: &dyn Action) -> bool { @@ -1327,6 +1315,16 @@ impl<'a> WindowContext<'a> { ); } + pub(crate) fn reuse_geometry(&mut self) { + let window = &mut self.window; + let view_id = *window.next_frame.view_stack.last().unwrap(); + assert!(window.next_frame.reused_views.insert(view_id)); + window + .next_frame + .dispatch_tree + .graft(view_id, &mut window.rendered_frame.dispatch_tree) + } + /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { println!("====================="); @@ -1342,26 +1340,18 @@ impl<'a> WindowContext<'a> { self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); self.window.next_frame.clear(); - self.window.frame_arena.clear(); let root_view = self.window.root_view.take().unwrap(); self.with_z_index(0, |cx| { cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { for (action_type, action_listeners) in &cx.app.global_action_listeners { for action_listener in action_listeners.iter().cloned() { - let listener = cx - .window - .frame_arena - .alloc(|| { - move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { - action_listener(action, phase, cx) - } - }) - .map(|listener| listener as _); - cx.window - .next_frame - .dispatch_tree - .on_action(*action_type, ArenaRef::from(listener)) + cx.window.next_frame.dispatch_tree.on_action( + *action_type, + Rc::new(move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { + action_listener(action, phase, cx) + }), + ) } } @@ -1395,6 +1385,19 @@ impl<'a> WindowContext<'a> { ); self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { + let next_listeners = self + .window + .next_frame + .mouse_listeners + .entry(*type_id) + .or_default(); + for (order, view_id, listener) in listeners.drain(..) { + if self.window.next_frame.reused_views.contains(&view_id) { + next_listeners.push((order, view_id, listener)); + } + } + } let previous_focus_path = self.window.rendered_frame.focus_path(); mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); @@ -1540,11 +1543,11 @@ impl<'a> WindowContext<'a> { .remove(&event.type_id()) { // Because handlers may add other handlers, we sort every time. - handlers.sort_by(|(a, _), (b, _)| a.cmp(b)); + handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); // Capture phase, events bubble from back to front. Handlers for this phase are used for // special purposes, such as detecting events outside of a given Bounds. - for (_, handler) in &mut handlers { + for (_, _, handler) in &mut handlers { handler(event, DispatchPhase::Capture, self); if !self.app.propagate_event { break; @@ -1553,7 +1556,7 @@ impl<'a> WindowContext<'a> { // Bubble phase, where most normal handlers do their work. if self.app.propagate_event { - for (_, handler) in handlers.iter_mut().rev() { + for (_, _, handler) in handlers.iter_mut().rev() { handler(event, DispatchPhase::Bubble, self); if !self.app.propagate_event { break; From 84b05d6c0564948369383a953a65666597b082eb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 15:12:23 +0100 Subject: [PATCH 05/98] Maintain view stack as part of `DispatchTree` --- crates/gpui/src/key_dispatch.rs | 42 +++++++++++++++-- crates/gpui/src/scene.rs | 79 +++++++++++++++++-------------- crates/gpui/src/window.rs | 83 +++++++++++++++++++++++---------- 3 files changed, 139 insertions(+), 65 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 9019670b04f4c66ea76761026a73a8fd6a0931dc..af836b7166635f85002a00b11b61a8643ab6b31c 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -4,7 +4,7 @@ use crate::{ }; use collections::FxHashMap; use parking_lot::Mutex; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use std::{ any::{Any, TypeId}, mem, @@ -18,6 +18,7 @@ pub struct DispatchNodeId(usize); pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, + view_stack: Vec, nodes: Vec, focusable_node_ids: FxHashMap, view_node_ids: FxHashMap, @@ -49,6 +50,7 @@ impl DispatchTree { Self { node_stack: Vec::new(), context_stack: Vec::new(), + view_stack: Vec::new(), nodes: Vec::new(), focusable_node_ids: FxHashMap::default(), view_node_ids: FxHashMap::default(), @@ -60,8 +62,9 @@ impl DispatchTree { pub fn clear(&mut self) { self.node_stack.clear(); - self.nodes.clear(); self.context_stack.clear(); + self.view_stack.clear(); + self.nodes.clear(); self.focusable_node_ids.clear(); self.view_node_ids.clear(); self.keystroke_matchers.clear(); @@ -82,10 +85,14 @@ impl DispatchTree { } pub fn pop_node(&mut self) { - let node_id = self.node_stack.pop().unwrap(); - if self.nodes[node_id.0].context.is_some() { + let node = &self.nodes[self.active_node_id().0]; + if node.context.is_some() { self.context_stack.pop(); } + if node.view_id.is_some() { + self.view_stack.pop(); + } + self.node_stack.pop(); } fn move_node(&mut self, source_node: &mut DispatchNode) { @@ -102,7 +109,7 @@ impl DispatchTree { target_node.action_listeners = mem::take(&mut source_node.action_listeners); } - pub fn graft(&mut self, view_id: EntityId, source: &mut Self) { + pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { let view_source_node_id = source .view_node_ids .get(&view_id) @@ -110,6 +117,7 @@ impl DispatchTree { let view_source_node = &mut source.nodes[view_source_node_id.0]; self.move_node(view_source_node); + let mut grafted_view_ids = smallvec![view_id]; let mut source_stack = vec![*view_source_node_id]; for (source_node_id, source_node) in source .nodes @@ -130,12 +138,17 @@ impl DispatchTree { } else { source_stack.push(source_node_id); self.move_node(source_node); + if let Some(view_id) = source_node.view_id { + grafted_view_ids.push(view_id); + } } } while !source_stack.is_empty() { self.pop_node(); } + + grafted_view_ids } pub fn clear_pending_keystrokes(&mut self) { @@ -192,6 +205,7 @@ impl DispatchTree { let node_id = self.active_node_id(); self.active_node().view_id = Some(view_id); self.view_node_ids.insert(view_id, node_id); + self.view_stack.push(view_id); } pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { @@ -322,6 +336,24 @@ impl DispatchTree { focus_path } + pub fn view_path(&self, view_id: EntityId) -> SmallVec<[EntityId; 8]> { + let mut view_path: SmallVec<[EntityId; 8]> = SmallVec::new(); + let mut current_node_id = self.view_node_ids.get(&view_id).copied(); + while let Some(node_id) = current_node_id { + let node = self.node(node_id); + if let Some(view_id) = node.view_id { + view_path.push(view_id); + } + current_node_id = node.parent; + } + view_path.reverse(); // Reverse the path so it goes from the root to the view node. + view_path + } + + pub fn active_view_id(&self) -> Option { + self.view_stack.last().copied() + } + pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index e922c11f533680e102e67bfa7bd5d51b741678a3..42183100b7c63ae2c5959a2b885afb3ba626d219 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -11,13 +11,12 @@ pub(crate) type PointF = Point; pub(crate) type PathVertex_ScaledPixels = PathVertex; pub type LayerId = u32; - pub type DrawOrder = u32; #[derive(Default)] pub(crate) struct SceneBuilder { - last_order: Option<(StackingOrder, LayerId)>, layers_by_order: BTreeMap, + orders_by_layer: BTreeMap, shadows: Vec, quads: Vec, paths: Vec>, @@ -34,40 +33,39 @@ impl SceneBuilder { orders[*layer_id as usize] = ix as u32; } self.layers_by_order.clear(); - self.last_order = None; for shadow in &mut self.shadows { - shadow.order = orders[shadow.order as usize]; + shadow.order = orders[shadow.layer_id as usize]; } self.shadows.sort_by_key(|shadow| shadow.order); for quad in &mut self.quads { - quad.order = orders[quad.order as usize]; + quad.order = orders[quad.layer_id as usize]; } self.quads.sort_by_key(|quad| quad.order); for path in &mut self.paths { - path.order = orders[path.order as usize]; + path.order = orders[path.layer_id as usize]; } self.paths.sort_by_key(|path| path.order); for underline in &mut self.underlines { - underline.order = orders[underline.order as usize]; + underline.order = orders[underline.layer_id as usize]; } self.underlines.sort_by_key(|underline| underline.order); for monochrome_sprite in &mut self.monochrome_sprites { - monochrome_sprite.order = orders[monochrome_sprite.order as usize]; + monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; } self.monochrome_sprites.sort_by_key(|sprite| sprite.order); for polychrome_sprite in &mut self.polychrome_sprites { - polychrome_sprite.order = orders[polychrome_sprite.order as usize]; + polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; } self.polychrome_sprites.sort_by_key(|sprite| sprite.order); for surface in &mut self.surfaces { - surface.order = orders[surface.order as usize]; + surface.order = orders[surface.layer_id as usize]; } self.surfaces.sort_by_key(|surface| surface.order); @@ -96,53 +94,46 @@ impl SceneBuilder { let layer_id = self.layer_id_for_order(order); match primitive { Primitive::Shadow(mut shadow) => { - shadow.order = layer_id; + shadow.layer_id = layer_id; self.shadows.push(shadow); } Primitive::Quad(mut quad) => { - quad.order = layer_id; + quad.layer_id = layer_id; self.quads.push(quad); } Primitive::Path(mut path) => { - path.order = layer_id; + path.layer_id = layer_id; path.id = PathId(self.paths.len()); self.paths.push(path); } Primitive::Underline(mut underline) => { - underline.order = layer_id; + underline.layer_id = layer_id; self.underlines.push(underline); } Primitive::MonochromeSprite(mut sprite) => { - sprite.order = layer_id; + sprite.layer_id = layer_id; self.monochrome_sprites.push(sprite); } Primitive::PolychromeSprite(mut sprite) => { - sprite.order = layer_id; + sprite.layer_id = layer_id; self.polychrome_sprites.push(sprite); } Primitive::Surface(mut surface) => { - surface.order = layer_id; + surface.layer_id = layer_id; self.surfaces.push(surface); } } } - fn layer_id_for_order(&mut self, order: &StackingOrder) -> u32 { - if let Some((last_order, last_layer_id)) = self.last_order.as_ref() { - if last_order == order { - return *last_layer_id; - } - }; - - let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) { + fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId { + if let Some(layer_id) = self.layers_by_order.get(order) { *layer_id } else { let next_id = self.layers_by_order.len() as LayerId; self.layers_by_order.insert(order.clone(), next_id); + self.orders_by_layer.insert(next_id, order.clone()); next_id - }; - self.last_order = Some((order.clone(), layer_id)); - layer_id + } } } @@ -439,7 +430,9 @@ pub(crate) enum PrimitiveBatch<'a> { #[derive(Default, Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Quad { - pub order: u32, // Initially a LayerId, then a DrawOrder. + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub background: Hsla, @@ -469,7 +462,9 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Underline { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub thickness: ScaledPixels, @@ -498,7 +493,9 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Shadow { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub corner_radii: Corners, pub content_mask: ContentMask, @@ -527,7 +524,9 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct MonochromeSprite { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub color: Hsla, @@ -558,7 +557,9 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct PolychromeSprite { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub corner_radii: Corners, @@ -589,7 +590,9 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Surface { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub image_buffer: media::core_video::CVImageBuffer, @@ -619,7 +622,9 @@ pub(crate) struct PathId(pub(crate) usize); #[derive(Debug)] pub struct Path { pub(crate) id: PathId, - order: u32, + pub(crate) view_id: u32, + layer_id: LayerId, + order: DrawOrder, pub(crate) bounds: Bounds

, pub(crate) content_mask: ContentMask

, pub(crate) vertices: Vec>, @@ -633,6 +638,8 @@ impl Path { pub fn new(start: Point) -> Self { Self { id: PathId(0), + view_id: 0, + layer_id: 0, order: 0, vertices: Vec::new(), start, @@ -650,6 +657,8 @@ impl Path { pub fn scale(&self, factor: f32) -> Path { Path { id: self.id, + view_id: self.view_id, + layer_id: self.layer_id, order: self.order, bounds: self.bounds.scale(factor), content_mask: self.content_mask.scale(factor), diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index e5f9195b692409ec176d40d6014a22b5ceaaa6f8..f21b8b3b80d691f2707f4e2c18f2213809c40d36 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -295,8 +295,6 @@ pub(crate) struct Frame { pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, - pub(crate) view_parents: FxHashMap, - pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -313,8 +311,6 @@ impl Frame { depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), - view_parents: FxHashMap::default(), - view_stack: Vec::new(), reused_views: FxHashSet::default(), } } @@ -325,8 +321,6 @@ impl Frame { self.dispatch_tree.clear(); self.depth_map.clear(); self.next_stacking_order_id = 0; - self.view_parents.clear(); - debug_assert!(self.view_stack.is_empty()); self.reused_views.clear(); } @@ -886,8 +880,8 @@ impl<'a> WindowContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { + let view_id = self.active_view_id(); let order = self.window.next_frame.z_index_stack.clone(); - let view_id = *self.window.next_frame.view_stack.last().unwrap(); self.window .next_frame .mouse_listeners @@ -1029,6 +1023,7 @@ impl<'a> WindowContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); + let view_id = self.active_view_id(); let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; @@ -1037,6 +1032,8 @@ impl<'a> WindowContext<'a> { window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Shadow { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds: shadow_bounds.scale(scale_factor), content_mask: content_mask.scale(scale_factor), @@ -1054,11 +1051,14 @@ impl<'a> WindowContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Quad { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds: quad.bounds.scale(scale_factor), content_mask: content_mask.scale(scale_factor), @@ -1074,8 +1074,11 @@ impl<'a> WindowContext<'a> { pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); + let view_id = self.active_view_id(); + path.content_mask = content_mask; path.color = color.into(); + path.view_id = view_id.as_u64() as u32; let window = &mut *self.window; window .next_frame @@ -1101,10 +1104,14 @@ impl<'a> WindowContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); + let view_id = self.active_view_id(); + let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Underline { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds: bounds.scale(scale_factor), content_mask: content_mask.scale(scale_factor), @@ -1154,10 +1161,13 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, MonochromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1204,11 +1214,14 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, PolychromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, corner_radii: Default::default(), @@ -1246,11 +1259,14 @@ impl<'a> WindowContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, MonochromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1282,11 +1298,14 @@ impl<'a> WindowContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, PolychromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1303,10 +1322,13 @@ impl<'a> WindowContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Surface { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1316,13 +1338,23 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_geometry(&mut self) { + let view_id = self.active_view_id(); let window = &mut self.window; - let view_id = *window.next_frame.view_stack.last().unwrap(); - assert!(window.next_frame.reused_views.insert(view_id)); - window + let grafted_view_ids = window .next_frame .dispatch_tree - .graft(view_id, &mut window.rendered_frame.dispatch_tree) + .graft(view_id, &mut window.rendered_frame.dispatch_tree); + for view_id in grafted_view_ids { + assert!(window.next_frame.reused_views.insert(view_id)); + } + } + + fn active_view_id(&self) -> EntityId { + self.window + .next_frame + .dispatch_tree + .active_view_id() + .expect("a view should always be active") } /// Draw pixels to the display for this window based on the contents of its scene. @@ -1375,6 +1407,7 @@ impl<'a> WindowContext<'a> { .draw(active_tooltip.cursor_offset, available_space, cx); }); } + self.window.dirty_views.clear(); self.window .next_frame @@ -1385,6 +1418,7 @@ impl<'a> WindowContext<'a> { ); self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { let next_listeners = self .window @@ -1434,7 +1468,6 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; - self.window.dirty_views.clear(); ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); scene @@ -2132,9 +2165,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.window_mut().next_frame.view_stack.push(view_id); + self.window_mut().next_frame.dispatch_tree.push_node(None); + self.window_mut() + .next_frame + .dispatch_tree + .associate_view(view_id); let result = f(self); - self.window_mut().next_frame.view_stack.pop(); + self.window_mut().next_frame.dispatch_tree.pop_node(); result } @@ -2495,17 +2532,13 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn notify(&mut self) { - let mut dirty_view_id = Some(self.view.entity_id()); - while let Some(view_id) = dirty_view_id { - if self.window_cx.window.dirty_views.insert(view_id) { - dirty_view_id = self - .window_cx - .window - .rendered_frame - .view_parents - .get(&view_id) - .copied(); - } else { + for view_id in self + .window + .rendered_frame + .dispatch_tree + .view_path(self.view.entity_id()) + { + if !self.window.dirty_views.insert(view_id) { break; } } From 3bb29acd26f9d406cbbe6660fb361485792909ec Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 17:34:57 +0100 Subject: [PATCH 06/98] :lipstick: --- crates/gpui/src/key_dispatch.rs | 45 +++++++++++++++++---------------- crates/gpui/src/window.rs | 16 +++++------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index af836b7166635f85002a00b11b61a8643ab6b31c..c6abc101eabd398ce3beafe7f7c0da85702174e2 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -70,18 +70,35 @@ impl DispatchTree { self.keystroke_matchers.clear(); } - pub fn push_node(&mut self, context: Option) { + pub fn push_node( + &mut self, + context: Option, + focus_id: Option, + view_id: Option, + ) { let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { parent, + focus_id, + view_id, ..Default::default() }); self.node_stack.push(node_id); + if let Some(context) = context { self.active_node().context = Some(context.clone()); self.context_stack.push(context); } + + if let Some(focus_id) = focus_id { + self.focusable_node_ids.insert(focus_id, node_id); + } + + if let Some(view_id) = view_id { + self.view_stack.push(view_id); + self.view_node_ids.insert(view_id, node_id); + } } pub fn pop_node(&mut self) { @@ -96,14 +113,11 @@ impl DispatchTree { } fn move_node(&mut self, source_node: &mut DispatchNode) { - self.push_node(source_node.context.take()); - if let Some(focus_id) = source_node.focus_id { - self.make_focusable(focus_id); - } - if let Some(view_id) = source_node.view_id { - self.associate_view(view_id); - } - + self.push_node( + source_node.context.take(), + source_node.focus_id, + source_node.view_id, + ); let target_node = self.active_node(); target_node.key_listeners = mem::take(&mut source_node.key_listeners); target_node.action_listeners = mem::take(&mut source_node.action_listeners); @@ -195,19 +209,6 @@ impl DispatchTree { }); } - pub fn make_focusable(&mut self, focus_id: FocusId) { - 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 associate_view(&mut self, view_id: EntityId) { - let node_id = self.active_node_id(); - self.active_node().view_id = Some(view_id); - self.view_node_ids.insert(view_id, node_id); - self.view_stack.push(view_id); - } - pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { if parent == child { return true; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f21b8b3b80d691f2707f4e2c18f2213809c40d36..2748ebed8489a35025a89ac35ab22311522e8c28 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1866,13 +1866,12 @@ impl<'a> WindowContext<'a> { f: impl FnOnce(Option, &mut Self) -> R, ) -> R { let window = &mut self.window; - window.next_frame.dispatch_tree.push_node(context.clone()); - if let Some(focus_handle) = focus_handle.as_ref() { - window - .next_frame - .dispatch_tree - .make_focusable(focus_handle.id); - } + let focus_id = focus_handle.as_ref().map(|handle| handle.id); + window + .next_frame + .dispatch_tree + .push_node(context.clone(), focus_id, None); + let result = f(focus_handle, self); self.window.next_frame.dispatch_tree.pop_node(); @@ -2165,11 +2164,10 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.window_mut().next_frame.dispatch_tree.push_node(None); self.window_mut() .next_frame .dispatch_tree - .associate_view(view_id); + .push_node(None, None, Some(view_id)); let result = f(self); self.window_mut().next_frame.dispatch_tree.pop_node(); result From 0c6d107740cded0b78b291db25346867c8bf27af Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 18:00:48 +0100 Subject: [PATCH 07/98] Introduce `on_request_frame` --- crates/gpui/src/platform.rs | 5 ++-- crates/gpui/src/platform/mac/platform.rs | 11 ++------ crates/gpui/src/platform/mac/window.rs | 32 +++++++++++++---------- crates/gpui/src/platform/test/platform.rs | 4 +-- crates/gpui/src/platform/test/window.rs | 8 +++--- crates/gpui/src/window.rs | 19 +++++++------- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 0ef345d98d1de34bbb7c6b567b4716608e2e6dbf..c41bf998f698443d5b6ca93dee2966206596d533 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -44,8 +44,6 @@ pub(crate) fn current_platform() -> Rc { Rc::new(MacPlatform::new()) } -pub type DrawWindow = Box Result>; - pub(crate) trait Platform: 'static { fn background_executor(&self) -> BackgroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor; @@ -66,7 +64,6 @@ pub(crate) trait Platform: 'static { &self, handle: AnyWindowHandle, options: WindowOptions, - draw: DrawWindow, ) -> Box; fn set_display_link_output_callback( @@ -157,6 +154,7 @@ pub trait PlatformWindow { fn minimize(&self); fn zoom(&self); fn toggle_full_screen(&self); + fn on_request_frame(&self, callback: Box); fn on_input(&self, callback: Box bool>); fn on_active_status_change(&self, callback: Box); fn on_resize(&self, callback: Box, f32)>); @@ -167,6 +165,7 @@ pub trait PlatformWindow { fn on_appearance_changed(&self, callback: Box); fn is_topmost_for_position(&self, position: Point) -> bool; fn invalidate(&self); + fn draw(&self, scene: &Scene); fn sprite_atlas(&self) -> Arc; diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8370e2a4953c1280a59d4a9cb74a93ae97214db2..78c2a0738127e43f36d48f60553a1f854b8ec761 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -3,8 +3,7 @@ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, - PlatformTextSystem, PlatformWindow, Result, Scene, SemanticVersion, VideoTimestamp, - WindowOptions, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -498,14 +497,8 @@ impl Platform for MacPlatform { &self, handle: AnyWindowHandle, options: WindowOptions, - draw: Box Result>, ) -> Box { - Box::new(MacWindow::open( - handle, - options, - draw, - self.foreground_executor(), - )) + Box::new(MacWindow::open(handle, options, self.foreground_executor())) } fn set_display_link_output_callback( diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2beac528c18f53cfa9a39b008dbebf3825502b30..6f15a7d1acddad42b3427ddf2129d26b4519dd92 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1,6 +1,6 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use crate::{ - display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DrawWindow, ExternalPaths, + display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, @@ -46,7 +46,6 @@ use std::{ sync::{Arc, Weak}, time::Duration, }; -use util::ResultExt; const WINDOW_STATE_IVAR: &str = "windowState"; @@ -317,8 +316,8 @@ struct MacWindowState { executor: ForegroundExecutor, native_window: id, renderer: MetalRenderer, - draw: Option, kind: WindowKind, + request_frame_callback: Option>, event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option, f32)>>, @@ -453,7 +452,6 @@ impl MacWindow { pub fn open( handle: AnyWindowHandle, options: WindowOptions, - draw: DrawWindow, executor: ForegroundExecutor, ) -> Self { unsafe { @@ -545,8 +543,8 @@ impl MacWindow { executor, native_window, renderer: MetalRenderer::new(true), - draw: Some(draw), kind: options.kind, + request_frame_callback: None, event_callback: None, activate_callback: None, resize_callback: None, @@ -926,6 +924,10 @@ impl PlatformWindow for MacWindow { .detach(); } + fn on_request_frame(&self, callback: Box) { + self.0.as_ref().lock().request_frame_callback = Some(callback); + } + fn on_input(&self, callback: Box bool>) { self.0.as_ref().lock().event_callback = Some(callback); } @@ -990,6 +992,11 @@ impl PlatformWindow for MacWindow { } } + fn draw(&self, scene: &crate::Scene) { + let mut this = self.0.lock(); + this.renderer.draw(scene); + } + fn sprite_atlas(&self) -> Arc { self.0.lock().renderer.sprite_atlas().clone() } @@ -1462,15 +1469,12 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) { } extern "C" fn display_layer(this: &Object, _: Sel, _: id) { - unsafe { - let window_state = get_window_state(this); - let mut draw = window_state.lock().draw.take().unwrap(); - let scene = draw().log_err(); - let mut window_state = window_state.lock(); - window_state.draw = Some(draw); - if let Some(scene) = scene { - window_state.renderer.draw(&scene); - } + let window_state = unsafe { get_window_state(this) }; + let mut lock = window_state.lock(); + if let Some(mut callback) = lock.request_frame_callback.take() { + drop(lock); + callback(); + window_state.lock().request_frame_callback = Some(callback); } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 695323e9c46b8e2a8f4260a682d8e214f58c43f4..5067f6a4b3417853a9fe36b59681ddfc5984437f 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,7 +1,6 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, - Keymap, Platform, PlatformDisplay, PlatformTextSystem, Scene, TestDisplay, TestWindow, - WindowOptions, + Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -162,7 +161,6 @@ impl Platform for TestPlatform { &self, handle: AnyWindowHandle, options: WindowOptions, - _draw: Box Result>, ) -> Box { let window = TestWindow::new( options, diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 91f965c10ac2987f4f6c8c15cb40a7cd94171370..029fd7300998f55f089696d6e01199b9c687ddfd 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -218,6 +218,8 @@ impl PlatformWindow for TestWindow { unimplemented!() } + fn on_request_frame(&self, _callback: Box) {} + fn on_input(&self, callback: Box bool>) { self.0.lock().input_callback = Some(callback) } @@ -254,9 +256,9 @@ impl PlatformWindow for TestWindow { unimplemented!() } - fn invalidate(&self) { - // (self.draw.lock())().unwrap(); - } + fn invalidate(&self) {} + + fn draw(&self, _scene: &crate::Scene) {} fn sprite_atlas(&self) -> sync::Arc { self.0.lock().sprite_atlas.clone() diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2748ebed8489a35025a89ac35ab22311522e8c28..8f9a82672da6a3c006d788a3e10782403f7c0a17 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -337,14 +337,7 @@ impl Window { options: WindowOptions, cx: &mut AppContext, ) -> Self { - let platform_window = cx.platform.open_window( - handle, - options, - Box::new({ - let mut cx = cx.to_async(); - move || handle.update(&mut cx, |_, cx| cx.draw()) - }), - ); + let platform_window = cx.platform.open_window(handle, options); let display_id = platform_window.display().id(); let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); @@ -353,6 +346,12 @@ impl Window { let scale_factor = platform_window.scale_factor(); let bounds = platform_window.bounds(); + platform_window.on_request_frame(Box::new({ + let mut cx = cx.to_async(); + move || { + handle.update(&mut cx, |_, cx| cx.draw()).log_err(); + } + })); platform_window.on_resize(Box::new({ let mut cx = cx.to_async(); move |_, _| { @@ -1358,7 +1357,7 @@ 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 { + pub(crate) fn draw(&mut self) { println!("====================="); self.window.dirty = false; self.window.drawing = true; @@ -1470,7 +1469,7 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - scene + self.window.platform_window.draw(&scene); } /// Dispatch a mouse or keyboard event on the window. From 881c532256d4263edc55251e05b163f38de915f6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 10:50:16 +0100 Subject: [PATCH 08/98] Insert primitives associated with views from a previous scene --- crates/gpui/src/app/entity_map.rs | 8 +- crates/gpui/src/key_dispatch.rs | 3 + crates/gpui/src/scene.rs | 218 ++++++++++++++++++------------ crates/gpui/src/view.rs | 2 +- crates/gpui/src/window.rs | 41 +++--- 5 files changed, 164 insertions(+), 108 deletions(-) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 17f6e47ddfec85de1c82b1fc0b8cd6ffc285aa32..482a003fe76fdbab46b8447efff2610ffaec52e3 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -2,7 +2,7 @@ use crate::{seal::Sealed, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; -use slotmap::{SecondaryMap, SlotMap}; +use slotmap::{KeyData, SecondaryMap, SlotMap}; use std::{ any::{type_name, Any, TypeId}, fmt::{self, Display}, @@ -21,6 +21,12 @@ use collections::HashMap; slotmap::new_key_type! { pub struct EntityId; } +impl From for EntityId { + fn from(value: u64) -> Self { + Self(KeyData::from_ffi(value)) + } +} + impl EntityId { pub fn as_u64(self) -> u64 { self.0.as_ffi() diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c6abc101eabd398ce3beafe7f7c0da85702174e2..168b1da9fe7e54d9a36982deaed8972dd8b5c482 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -144,6 +144,8 @@ impl DispatchTree { if source_node.parent != Some(*source_ancestor) { source_stack.pop(); self.pop_node(); + } else { + break; } } @@ -159,6 +161,7 @@ impl DispatchTree { } while !source_stack.is_empty() { + source_stack.pop(); self.pop_node(); } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 42183100b7c63ae2c5959a2b885afb3ba626d219..9c63f803254b8f099be1cb7c369b41458a253df2 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,9 +1,9 @@ use crate::{ - point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point, - ScaledPixels, StackingOrder, + point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, + Point, ScaledPixels, StackingOrder, }; -use collections::BTreeMap; -use std::{fmt::Debug, iter::Peekable, mem, slice}; +use collections::{BTreeMap, FxHashSet}; +use std::{fmt::Debug, iter::Peekable, slice}; // Exported to metal pub(crate) type PointF = Point; @@ -14,7 +14,7 @@ pub type LayerId = u32; pub type DrawOrder = u32; #[derive(Default)] -pub(crate) struct SceneBuilder { +pub struct Scene { layers_by_order: BTreeMap, orders_by_layer: BTreeMap, shadows: Vec, @@ -26,57 +26,46 @@ pub(crate) struct SceneBuilder { surfaces: Vec, } -impl SceneBuilder { - pub fn build(&mut self) -> Scene { - let mut orders = vec![0; self.layers_by_order.len()]; - for (ix, layer_id) in self.layers_by_order.values().enumerate() { - orders[*layer_id as usize] = ix as u32; - } +impl Scene { + pub fn clear(&mut self) { self.layers_by_order.clear(); + self.orders_by_layer.clear(); + self.shadows.clear(); + self.quads.clear(); + self.paths.clear(); + self.underlines.clear(); + self.monochrome_sprites.clear(); + self.polychrome_sprites.clear(); + self.surfaces.clear(); + } - for shadow in &mut self.shadows { - shadow.order = orders[shadow.layer_id as usize]; - } - self.shadows.sort_by_key(|shadow| shadow.order); - - for quad in &mut self.quads { - quad.order = orders[quad.layer_id as usize]; - } - self.quads.sort_by_key(|quad| quad.order); - - for path in &mut self.paths { - path.order = orders[path.layer_id as usize]; - } - self.paths.sort_by_key(|path| path.order); - - for underline in &mut self.underlines { - underline.order = orders[underline.layer_id as usize]; - } - self.underlines.sort_by_key(|underline| underline.order); - - for monochrome_sprite in &mut self.monochrome_sprites { - monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; - } - self.monochrome_sprites.sort_by_key(|sprite| sprite.order); - - for polychrome_sprite in &mut self.polychrome_sprites { - polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; - } - self.polychrome_sprites.sort_by_key(|sprite| sprite.order); - - for surface in &mut self.surfaces { - surface.order = orders[surface.layer_id as usize]; - } - self.surfaces.sort_by_key(|surface| surface.order); + pub fn paths(&self) -> &[Path] { + &self.paths + } - Scene { - shadows: mem::take(&mut self.shadows), - quads: mem::take(&mut self.quads), - paths: mem::take(&mut self.paths), - underlines: mem::take(&mut self.underlines), - monochrome_sprites: mem::take(&mut self.monochrome_sprites), - polychrome_sprites: mem::take(&mut self.polychrome_sprites), - surfaces: mem::take(&mut self.surfaces), + pub(crate) fn batches(&self) -> impl Iterator { + BatchIterator { + shadows: &self.shadows, + shadows_start: 0, + shadows_iter: self.shadows.iter().peekable(), + quads: &self.quads, + quads_start: 0, + quads_iter: self.quads.iter().peekable(), + paths: &self.paths, + paths_start: 0, + paths_iter: self.paths.iter().peekable(), + underlines: &self.underlines, + underlines_start: 0, + underlines_iter: self.underlines.iter().peekable(), + monochrome_sprites: &self.monochrome_sprites, + monochrome_sprites_start: 0, + monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), + polychrome_sprites: &self.polychrome_sprites, + polychrome_sprites_start: 0, + polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), + surfaces: &self.surfaces, + surfaces_start: 0, + surfaces_iter: self.surfaces.iter().peekable(), } } @@ -135,47 +124,98 @@ impl SceneBuilder { next_id } } -} -pub struct Scene { - pub shadows: Vec, - pub quads: Vec, - pub paths: Vec>, - pub underlines: Vec, - pub monochrome_sprites: Vec, - pub polychrome_sprites: Vec, - pub surfaces: Vec, -} + pub fn insert_views_from_scene(&mut self, views: &FxHashSet, prev_scene: &mut Self) { + for shadow in prev_scene.shadows.drain(..) { + if views.contains(&EntityId::from(shadow.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&shadow.layer_id]; + self.insert(&order, shadow); + } + } -impl Scene { - pub fn paths(&self) -> &[Path] { - &self.paths + for quad in prev_scene.quads.drain(..) { + if views.contains(&EntityId::from(quad.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&quad.layer_id]; + self.insert(&order, quad); + } + } + + for path in prev_scene.paths.drain(..) { + if views.contains(&EntityId::from(path.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&path.layer_id]; + self.insert(&order, path); + } + } + + for underline in prev_scene.underlines.drain(..) { + if views.contains(&EntityId::from(underline.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&underline.layer_id]; + self.insert(&order, underline); + } + } + + for sprite in prev_scene.monochrome_sprites.drain(..) { + if views.contains(&EntityId::from(sprite.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&sprite.layer_id]; + self.insert(&order, sprite); + } + } + + for sprite in prev_scene.polychrome_sprites.drain(..) { + if views.contains(&EntityId::from(sprite.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&sprite.layer_id]; + self.insert(&order, sprite); + } + } + + for surface in prev_scene.surfaces.drain(..) { + if views.contains(&EntityId::from(surface.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&surface.layer_id]; + self.insert(&order, surface); + } + } } - pub(crate) fn batches(&self) -> impl Iterator { - BatchIterator { - shadows: &self.shadows, - shadows_start: 0, - shadows_iter: self.shadows.iter().peekable(), - quads: &self.quads, - quads_start: 0, - quads_iter: self.quads.iter().peekable(), - paths: &self.paths, - paths_start: 0, - paths_iter: self.paths.iter().peekable(), - underlines: &self.underlines, - underlines_start: 0, - underlines_iter: self.underlines.iter().peekable(), - monochrome_sprites: &self.monochrome_sprites, - monochrome_sprites_start: 0, - monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), - polychrome_sprites: &self.polychrome_sprites, - polychrome_sprites_start: 0, - polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), - surfaces: &self.surfaces, - surfaces_start: 0, - surfaces_iter: self.surfaces.iter().peekable(), + pub fn finish(&mut self) { + let mut orders = vec![0; self.layers_by_order.len()]; + for (ix, layer_id) in self.layers_by_order.values().enumerate() { + orders[*layer_id as usize] = ix as u32; + } + + for shadow in &mut self.shadows { + shadow.order = orders[shadow.layer_id as usize]; + } + self.shadows.sort_by_key(|shadow| shadow.order); + + for quad in &mut self.quads { + quad.order = orders[quad.layer_id as usize]; + } + self.quads.sort_by_key(|quad| quad.order); + + for path in &mut self.paths { + path.order = orders[path.layer_id as usize]; + } + self.paths.sort_by_key(|path| path.order); + + for underline in &mut self.underlines { + underline.order = orders[underline.layer_id as usize]; + } + self.underlines.sort_by_key(|underline| underline.order); + + for monochrome_sprite in &mut self.monochrome_sprites { + monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; } + self.monochrome_sprites.sort_by_key(|sprite| sprite.order); + + for polychrome_sprite in &mut self.polychrome_sprites { + polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; + } + self.polychrome_sprites.sort_by_key(|sprite| sprite.order); + + for surface in &mut self.surfaces { + surface.order = orders[surface.layer_id as usize]; + } + self.surfaces.sort_by_key(|surface| surface.order); } } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 24bc00ce39796890158bc6890e0fb779801a73b3..d049c472587fed22606e144bccc4d333ffed6558 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -231,7 +231,7 @@ impl AnyView { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); cx.compute_layout(layout_id, available_space); - rendered_element.paint(cx); + cx.with_view_id(self.entity_id(), |cx| rendered_element.paint(cx)); }) } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8f9a82672da6a3c006d788a3e10782403f7c0a17..45dc197e18ae07f1234f86c01d6558d768b890c6 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -7,9 +7,9 @@ use crate::{ 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, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, Style, SubscriberSet, + Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, + WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -289,7 +289,7 @@ pub(crate) struct Frame { pub(crate) element_states: FxHashMap, mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, - pub(crate) scene_builder: SceneBuilder, + pub(crate) scene: Scene, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, pub(crate) z_index_stack: StackingOrder, pub(crate) next_stacking_order_id: u32, @@ -305,7 +305,7 @@ impl Frame { element_states: FxHashMap::default(), mouse_listeners: FxHashMap::default(), dispatch_tree, - scene_builder: SceneBuilder::default(), + scene: Scene::default(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, depth_map: Default::default(), @@ -322,6 +322,7 @@ impl Frame { self.depth_map.clear(); self.next_stacking_order_id = 0; self.reused_views.clear(); + self.scene.clear(); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -1028,7 +1029,7 @@ impl<'a> WindowContext<'a> { let mut shadow_bounds = bounds; shadow_bounds.origin += shadow.offset; shadow_bounds.dilate(shadow.spread_radius); - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Shadow { view_id: view_id.as_u64() as u32, @@ -1053,7 +1054,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Quad { view_id: view_id.as_u64() as u32, @@ -1081,7 +1082,7 @@ impl<'a> WindowContext<'a> { let window = &mut *self.window; window .next_frame - .scene_builder + .scene .insert(&window.next_frame.z_index_stack, path.scale(scale_factor)); } @@ -1106,7 +1107,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Underline { view_id: view_id.as_u64() as u32, @@ -1162,7 +1163,7 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { view_id: view_id.as_u64() as u32, @@ -1216,7 +1217,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { view_id: view_id.as_u64() as u32, @@ -1261,7 +1262,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { view_id: view_id.as_u64() as u32, @@ -1300,7 +1301,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { view_id: view_id.as_u64() as u32, @@ -1323,7 +1324,7 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Surface { view_id: view_id.as_u64() as u32, @@ -1337,6 +1338,7 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_geometry(&mut self) { + println!("reusing geometry"); let view_id = self.active_view_id(); let window = &mut self.window; let grafted_view_ids = window @@ -1407,6 +1409,11 @@ impl<'a> WindowContext<'a> { }); } self.window.dirty_views.clear(); + self.window.next_frame.scene.insert_views_from_scene( + &self.window.next_frame.reused_views, + &mut self.window.rendered_frame.scene, + ); + self.window.next_frame.scene.finish(); self.window .next_frame @@ -1454,8 +1461,6 @@ impl<'a> WindowContext<'a> { .retain(&(), |listener| listener(&event, self)); } - let scene = self.window.rendered_frame.scene_builder.build(); - // Set the cursor only if we're the active window. let cursor_style = self .window @@ -1469,7 +1474,9 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - self.window.platform_window.draw(&scene); + self.window + .platform_window + .draw(&self.window.rendered_frame.scene); } /// Dispatch a mouse or keyboard event on the window. From d0c101cb6e5147ca9688e30afa34a26400c57fae Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 15:00:40 +0100 Subject: [PATCH 09/98] Reuse depth map entries and retain element states for cached trees --- crates/gpui/src/key_dispatch.rs | 34 ++-- crates/gpui/src/view.rs | 46 ++--- crates/gpui/src/window.rs | 307 ++++++++++++++++++-------------- 3 files changed, 211 insertions(+), 176 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 168b1da9fe7e54d9a36982deaed8972dd8b5c482..a4a048a930fee5bec2918d54787416c7aa6c9ddc 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -18,7 +18,6 @@ pub struct DispatchNodeId(usize); pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, - view_stack: Vec, nodes: Vec, focusable_node_ids: FxHashMap, view_node_ids: FxHashMap, @@ -50,7 +49,6 @@ impl DispatchTree { Self { node_stack: Vec::new(), context_stack: Vec::new(), - view_stack: Vec::new(), nodes: Vec::new(), focusable_node_ids: FxHashMap::default(), view_node_ids: FxHashMap::default(), @@ -63,7 +61,6 @@ impl DispatchTree { pub fn clear(&mut self) { self.node_stack.clear(); self.context_stack.clear(); - self.view_stack.clear(); self.nodes.clear(); self.focusable_node_ids.clear(); self.view_node_ids.clear(); @@ -76,6 +73,15 @@ impl DispatchTree { focus_id: Option, view_id: Option, ) { + // Associate a view id to this only if it is the root node for the view. + let view_id = view_id.and_then(|view_id| { + if self.view_node_ids.contains_key(&view_id) { + None + } else { + Some(view_id) + } + }); + let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { @@ -96,7 +102,6 @@ impl DispatchTree { } if let Some(view_id) = view_id { - self.view_stack.push(view_id); self.view_node_ids.insert(view_id, node_id); } } @@ -106,21 +111,14 @@ impl DispatchTree { if node.context.is_some() { self.context_stack.pop(); } - if node.view_id.is_some() { - self.view_stack.pop(); - } self.node_stack.pop(); } - fn move_node(&mut self, source_node: &mut DispatchNode) { - self.push_node( - source_node.context.take(), - source_node.focus_id, - source_node.view_id, - ); - let target_node = self.active_node(); - target_node.key_listeners = mem::take(&mut source_node.key_listeners); - target_node.action_listeners = mem::take(&mut source_node.action_listeners); + fn move_node(&mut self, source: &mut DispatchNode) { + self.push_node(source.context.take(), source.focus_id, source.view_id); + let target = self.active_node(); + target.key_listeners = mem::take(&mut source.key_listeners); + target.action_listeners = mem::take(&mut source.action_listeners); } pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { @@ -354,10 +352,6 @@ impl DispatchTree { view_path } - pub fn active_view_id(&self) -> Option { - self.view_stack.last().copied() - } - pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index d049c472587fed22606e144bccc4d333ffed6558..6ec78e0d2e8bd62dc5e30d7a47ba77cd254cb892 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -89,9 +89,11 @@ impl Element for View { _state: Option, cx: &mut WindowContext, ) -> (LayoutId, Self::State) { - let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); - let layout_id = element.request_layout(cx); - (layout_id, Some(element)) + cx.with_view_id(self.entity_id(), |cx| { + let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); + let layout_id = element.request_layout(cx); + (layout_id, Some(element)) + }) } fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { @@ -228,10 +230,12 @@ impl AnyView { available_space: Size, cx: &mut WindowContext, ) { - cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); - cx.compute_layout(layout_id, available_space); - cx.with_view_id(self.entity_id(), |cx| rendered_element.paint(cx)); + cx.with_view_id(self.entity_id(), |cx| { + cx.with_absolute_element_offset(origin, |cx| { + let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); + cx.compute_layout(layout_id, available_space); + rendered_element.paint(cx) + }); }) } } @@ -254,21 +258,23 @@ impl Element for AnyView { state: Option, cx: &mut WindowContext, ) -> (LayoutId, Self::State) { - if self.cache { - if let Some(state) = state { - let layout_id = cx.request_layout(&state.root_style, None); - return (layout_id, state); + cx.with_view_id(self.entity_id(), |cx| { + if self.cache { + if let Some(state) = state { + let layout_id = cx.request_layout(&state.root_style, None); + return (layout_id, state); + } } - } - let (layout_id, element) = (self.request_layout)(self, cx); - let root_style = cx.layout_style(layout_id).unwrap().clone(); - let state = AnyViewState { - root_style, - cache_key: None, - element: Some(element), - }; - (layout_id, state) + let (layout_id, element) = (self.request_layout)(self, cx); + let root_style = cx.layout_style(layout_id).unwrap().clone(); + let state = AnyViewState { + root_style, + cache_key: None, + element: Some(element), + }; + (layout_id, state) + }) } fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 45dc197e18ae07f1234f86c01d6558d768b890c6..376f4c24662f39a766c9aaa3ce3b2c69a4f75f9c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -280,6 +280,7 @@ pub struct Window { pub(crate) struct ElementStateBox { inner: Box, + parent_view_id: EntityId, #[cfg(debug_assertions)] type_name: &'static str, } @@ -290,11 +291,12 @@ pub(crate) struct Frame { mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene: Scene, - pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, + pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, pub(crate) z_index_stack: StackingOrder, pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, + pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -311,6 +313,7 @@ impl Frame { depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), + view_stack: Vec::new(), reused_views: FxHashSet::default(), } } @@ -323,6 +326,7 @@ impl Frame { self.next_stacking_order_id = 0; self.reused_views.clear(); self.scene.clear(); + debug_assert_eq!(self.view_stack.len(), 0); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -880,7 +884,7 @@ impl<'a> WindowContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let order = self.window.next_frame.z_index_stack.clone(); self.window .next_frame @@ -967,17 +971,18 @@ impl<'a> WindowContext<'a> { /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); - let depth_map = &mut self.window.next_frame.depth_map; - match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(level)) { - Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)), - } + let view_id = self.parent_view_id().unwrap(); + self.window + .next_frame + .depth_map + .push((stacking_order, view_id, bounds)); } /// Returns true if there is no opaque layer containing the given point /// on top of the given level. Layers whose level is an extension of the /// level are not considered to be on top of the level. pub fn was_top_layer(&self, point: &Point, level: &StackingOrder) -> bool { - for (opaque_level, bounds) in self.window.rendered_frame.depth_map.iter() { + for (opaque_level, _, bounds) in self.window.rendered_frame.depth_map.iter() { if level >= opaque_level { break; } @@ -994,7 +999,7 @@ impl<'a> WindowContext<'a> { point: &Point, level: &StackingOrder, ) -> bool { - for (opaque_level, bounds) in self.window.rendered_frame.depth_map.iter() { + for (opaque_level, _, bounds) in self.window.rendered_frame.depth_map.iter() { if level >= opaque_level { break; } @@ -1023,7 +1028,7 @@ impl<'a> WindowContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; @@ -1051,7 +1056,7 @@ impl<'a> WindowContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1074,7 +1079,7 @@ impl<'a> WindowContext<'a> { pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); path.content_mask = content_mask; path.color = color.into(); @@ -1104,7 +1109,7 @@ impl<'a> WindowContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1161,7 +1166,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1214,7 +1219,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1259,7 +1264,7 @@ impl<'a> WindowContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1298,7 +1303,7 @@ impl<'a> WindowContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1322,7 +1327,7 @@ impl<'a> WindowContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1338,8 +1343,7 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_geometry(&mut self) { - println!("reusing geometry"); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut self.window; let grafted_view_ids = window .next_frame @@ -1350,17 +1354,8 @@ impl<'a> WindowContext<'a> { } } - fn active_view_id(&self) -> EntityId { - self.window - .next_frame - .dispatch_tree - .active_view_id() - .expect("a view should always be active") - } - /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) { - println!("====================="); self.window.dirty = false; self.window.drawing = true; @@ -1409,11 +1404,6 @@ impl<'a> WindowContext<'a> { }); } self.window.dirty_views.clear(); - self.window.next_frame.scene.insert_views_from_scene( - &self.window.next_frame.reused_views, - &mut self.window.rendered_frame.scene, - ); - self.window.next_frame.scene.finish(); self.window .next_frame @@ -1425,6 +1415,7 @@ impl<'a> WindowContext<'a> { self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + // Reuse mouse listeners that didn't change since the last frame. for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { let next_listeners = self .window @@ -1439,6 +1430,43 @@ impl<'a> WindowContext<'a> { } } + // Reuse entries in the depth map that didn't change since the last frame. + for (order, view_id, bounds) in self.window.rendered_frame.depth_map.drain(..) { + if self.window.next_frame.reused_views.contains(&view_id) { + self.window + .next_frame + .depth_map + .push((order, view_id, bounds)); + } + } + self.window + .next_frame + .depth_map + .sort_by(|a, b| a.0.cmp(&b.0)); + + // Retain element states for views that didn't change since the last frame. + for (element_id, state) in self.window.rendered_frame.element_states.drain() { + if self + .window + .next_frame + .reused_views + .contains(&state.parent_view_id) + { + self.window + .next_frame + .element_states + .entry(element_id) + .or_insert(state); + } + } + + // Reuse geometry that didn't change since the last frame. + self.window.next_frame.scene.insert_views_from_scene( + &self.window.next_frame.reused_views, + &mut self.window.rendered_frame.scene, + ); + self.window.next_frame.scene.finish(); + let previous_focus_path = self.window.rendered_frame.focus_path(); mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); let current_focus_path = self.window.rendered_frame.focus_path(); @@ -1871,12 +1899,13 @@ impl<'a> WindowContext<'a> { focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R { + let parent_view_id = self.parent_view_id(); let window = &mut self.window; let focus_id = focus_handle.as_ref().map(|handle| handle.id); window .next_frame .dispatch_tree - .push_node(context.clone(), focus_id, None); + .push_node(context.clone(), focus_id, parent_view_id); let result = f(focus_handle, self); @@ -1885,6 +1914,114 @@ impl<'a> WindowContext<'a> { result } + pub(crate) fn with_view_id( + &mut self, + view_id: EntityId, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } + + /// Update or initialize state for an element with the given id that lives across multiple + /// frames. If an element with this id existed in the rendered frame, its state will be passed + /// to the given closure. The state returned by the closure will be stored so it can be referenced + /// when drawing the next frame. + pub(crate) fn with_element_state( + &mut self, + id: ElementId, + f: impl FnOnce(Option, &mut Self) -> (R, S), + ) -> R + where + S: 'static, + { + self.with_element_id(Some(id), |cx| { + let global_id = cx.window().element_id_stack.clone(); + + if let Some(any) = cx + .window_mut() + .next_frame + .element_states + .remove(&global_id) + .or_else(|| { + cx.window_mut() + .rendered_frame + .element_states + .remove(&global_id) + }) + { + let ElementStateBox { + inner, + parent_view_id, + #[cfg(debug_assertions)] + type_name + } = any; + // Using the extra inner option to avoid needing to reallocate a new box. + let mut state_box = inner + .downcast::>() + .map_err(|_| { + #[cfg(debug_assertions)] + { + anyhow!( + "invalid element state type for id, requested_type {:?}, actual type: {:?}", + std::any::type_name::(), + type_name + ) + } + + #[cfg(not(debug_assertions))] + { + anyhow!( + "invalid element state type for id, requested_type {:?}", + std::any::type_name::(), + ) + } + }) + .unwrap(); + + // Actual: Option <- View + // Requested: () <- AnyElemet + let state = state_box + .take() + .expect("element state is already on the stack"); + let (result, state) = f(Some(state), cx); + state_box.replace(state); + cx.window_mut() + .next_frame + .element_states + .insert(global_id, ElementStateBox { + inner: state_box, + parent_view_id, + #[cfg(debug_assertions)] + type_name + }); + result + } else { + let (result, state) = f(None, cx); + let parent_view_id = cx.parent_view_id().unwrap(); + cx.window_mut() + .next_frame + .element_states + .insert(global_id, + ElementStateBox { + inner: Box::new(Some(state)), + parent_view_id, + #[cfg(debug_assertions)] + type_name: std::any::type_name::() + } + + ); + result + } + }) + } + + fn parent_view_id(&self) -> Option { + self.window.next_frame.view_stack.last().copied() + } + /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. @@ -2169,16 +2306,6 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { result } - fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.window_mut() - .next_frame - .dispatch_tree - .push_node(None, None, Some(view_id)); - let result = f(self); - self.window_mut().next_frame.dispatch_tree.pop_node(); - result - } - /// Update the global element offset relative to the current offset. This is used to implement /// scrolling. fn with_element_offset( @@ -2220,98 +2347,6 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { .unwrap_or_default() } - /// Update or initialize state for an element with the given id that lives across multiple - /// frames. If an element with this id existed in the rendered frame, its state will be passed - /// to the given closure. The state returned by the closure will be stored so it can be referenced - /// when drawing the next frame. - fn with_element_state( - &mut self, - id: ElementId, - f: impl FnOnce(Option, &mut Self) -> (R, S), - ) -> R - where - S: 'static, - { - self.with_element_id(Some(id), |cx| { - let global_id = cx.window().element_id_stack.clone(); - - if let Some(any) = cx - .window_mut() - .next_frame - .element_states - .remove(&global_id) - .or_else(|| { - cx.window_mut() - .rendered_frame - .element_states - .remove(&global_id) - }) - { - let ElementStateBox { - inner, - - #[cfg(debug_assertions)] - type_name - } = any; - // Using the extra inner option to avoid needing to reallocate a new box. - let mut state_box = inner - .downcast::>() - .map_err(|_| { - #[cfg(debug_assertions)] - { - anyhow!( - "invalid element state type for id, requested_type {:?}, actual type: {:?}", - std::any::type_name::(), - type_name - ) - } - - #[cfg(not(debug_assertions))] - { - anyhow!( - "invalid element state type for id, requested_type {:?}", - std::any::type_name::(), - ) - } - }) - .unwrap(); - - // Actual: Option <- View - // Requested: () <- AnyElemet - let state = state_box - .take() - .expect("element state is already on the stack"); - let (result, state) = f(Some(state), cx); - state_box.replace(state); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, ElementStateBox { - inner: state_box, - - #[cfg(debug_assertions)] - type_name - }); - result - } else { - let (result, state) = f(None, cx); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, - ElementStateBox { - inner: Box::new(Some(state)), - - #[cfg(debug_assertions)] - type_name: std::any::type_name::() - } - - ); - result - } - }) - } - /// Obtain the current content mask. fn content_mask(&self) -> ContentMask { self.window() From 2923b71f83cfdc2d5168b683d784095b80ff27ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 17:27:02 +0100 Subject: [PATCH 10/98] Replace `WindowContext::notify` with `WindowContext::refresh` --- crates/copilot/src/sign_in.rs | 2 +- crates/gpui/src/elements/div.rs | 24 ++++++++++---------- crates/gpui/src/elements/img.rs | 2 +- crates/gpui/src/elements/list.rs | 2 +- crates/gpui/src/elements/text.rs | 4 ++-- crates/gpui/src/view.rs | 1 + crates/gpui/src/window.rs | 20 +++++++++------- crates/terminal_view/src/terminal_element.rs | 2 +- crates/ui/src/components/popover_menu.rs | 2 +- crates/ui/src/components/right_click_menu.rs | 4 ++-- crates/workspace/src/pane_group.rs | 2 +- 11 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index ba5dbe0e315828ac1761cf1d76ac37bb8211ea4c..2ca31c7d332f7e5c6a32bf85b93d79dd2ee714d9 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -109,7 +109,7 @@ impl CopilotCodeVerification { let user_code = data.user_code.clone(); move |_, cx| { cx.write_to_clipboard(ClipboardItem::new(user_code.clone())); - cx.notify(); + cx.refresh(); } }) .child(Label::new(data.user_code.clone())) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 45097411d13875c7b1a8fecc4888711dbc0d9dd3..d750d692f0d54c18f4e10ae92693920cec1825f6 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1027,7 +1027,7 @@ impl Interactivity { if e.modifiers.command != command_held && text_bounds.contains(&cx.mouse_position()) { - cx.notify(); + cx.refresh(); } } }); @@ -1038,7 +1038,7 @@ impl Interactivity { if phase == DispatchPhase::Capture && bounds.contains(&event.position) != hovered { - cx.notify(); + cx.refresh(); } }, ); @@ -1188,7 +1188,7 @@ impl Interactivity { if phase == DispatchPhase::Capture && group_bounds.contains(&event.position) != hovered { - cx.notify(); + cx.refresh(); } }); } @@ -1203,7 +1203,7 @@ impl Interactivity { if phase == DispatchPhase::Capture && bounds.contains(&event.position) != hovered { - cx.notify(); + cx.refresh(); } }); } @@ -1237,7 +1237,7 @@ impl Interactivity { if can_drop { listener(drag.value.as_ref(), cx); - cx.notify(); + cx.refresh(); cx.stop_propagation(); } } @@ -1268,7 +1268,7 @@ impl Interactivity { && interactive_bounds.visibly_contains(&event.position, cx) { *pending_mouse_down.borrow_mut() = Some(event.clone()); - cx.notify(); + cx.refresh(); } } }); @@ -1299,7 +1299,7 @@ impl Interactivity { cursor_offset, }); pending_mouse_down.take(); - cx.notify(); + cx.refresh(); cx.stop_propagation(); } } @@ -1319,7 +1319,7 @@ impl Interactivity { pending_mouse_down.borrow_mut(); if pending_mouse_down.is_some() { captured_mouse_down = pending_mouse_down.take(); - cx.notify(); + cx.refresh(); } } // Fire click handlers during the bubble phase. @@ -1413,7 +1413,7 @@ impl Interactivity { _task: None, }, ); - cx.notify(); + cx.refresh(); }) .ok(); } @@ -1453,7 +1453,7 @@ impl Interactivity { cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Capture { *active_state.borrow_mut() = ElementClickedState::default(); - cx.notify(); + cx.refresh(); } }); } else { @@ -1471,7 +1471,7 @@ impl Interactivity { if group || element { *active_state.borrow_mut() = ElementClickedState { group, element }; - cx.notify(); + cx.refresh(); } } }); @@ -1531,7 +1531,7 @@ impl Interactivity { } if *scroll_offset != old_scroll_offset { - cx.notify(); + cx.refresh(); cx.stop_propagation(); } } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 650b5b666bc821e15873c53f2cdc56c0546b6a20..123bfed42a38642b1789675ffa00764418169be9 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -109,7 +109,7 @@ impl Element for Img { } else { cx.spawn(|mut cx| async move { if image_future.await.ok().is_some() { - cx.on_next_frame(|cx| cx.notify()); + cx.on_next_frame(|cx| cx.refresh()); } }) .detach(); diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 2a47a16741cf67c0cefb8a094d2f9e506cacbdf4..9081ceadca8440e6ac7d631b460d1b4b277ff932 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -258,7 +258,7 @@ impl StateInner { ); } - cx.notify(); + cx.refresh(); } fn logical_scroll_top(&self) -> ListOffset { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 29c93fd19e91518a96a5f663352b92519264aca9..ec74eb2987db9e4234f15af6b13c926226b61ca9 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -389,7 +389,7 @@ impl Element for InteractiveText { } mouse_down.take(); - cx.notify(); + cx.refresh(); } }); } else { @@ -399,7 +399,7 @@ impl Element for InteractiveText { text_state.index_for_position(bounds, event.position) { mouse_down.set(Some(mouse_down_index)); - cx.notify(); + cx.refresh(); } } }); diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 6ec78e0d2e8bd62dc5e30d7a47ba77cd254cb892..22443a939546d9c86a534d62407ca18d090e3db4 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -290,6 +290,7 @@ impl Element for AnyView { && cache_key.stacking_order == *cx.stacking_order() && cache_key.text_style == cx.text_style() && !cx.window.dirty_views.contains(&self.entity_id()) + && !cx.window.refreshing { cx.reuse_geometry(); return; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 376f4c24662f39a766c9aaa3ce3b2c69a4f75f9c..fc60f9c5c317ad81a16d64cfd03232781676ca22 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -269,6 +269,7 @@ pub struct Window { bounds_observers: SubscriberSet<(), AnyObserver>, active: bool, pub(crate) dirty: bool, + pub(crate) refreshing: bool, pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, pub(crate) focus: Option, @@ -424,6 +425,7 @@ impl Window { bounds_observers: SubscriberSet::new(), active: false, dirty: false, + refreshing: false, drawing: false, activation_observers: SubscriberSet::new(), focus: None, @@ -478,8 +480,9 @@ impl<'a> WindowContext<'a> { } /// Mark the window as dirty, scheduling it to be redrawn on the next frame. - pub fn notify(&mut self) { + pub fn refresh(&mut self) { if !self.window.drawing { + self.window.refreshing = true; self.window.dirty = true; } } @@ -519,7 +522,7 @@ impl<'a> WindowContext<'a> { self.window.focus_invalidated = true; } - self.notify(); + self.refresh(); } /// Remove focus from all elements within this context's window. @@ -529,7 +532,7 @@ impl<'a> WindowContext<'a> { } self.window.focus = None; - self.notify(); + self.refresh(); } pub fn disable_focus(&mut self) { @@ -795,7 +798,7 @@ impl<'a> WindowContext<'a> { self.window.viewport_size = self.window.platform_window.content_size(); self.window.bounds = self.window.platform_window.bounds(); self.window.display_id = self.window.platform_window.display().id(); - self.notify(); + self.refresh(); self.window .bounds_observers @@ -1499,6 +1502,7 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } + self.window.refreshing = false; self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); @@ -1641,12 +1645,12 @@ impl<'a> WindowContext<'a> { if event.is::() { // If this was a mouse move event, redraw the window so that the // active drag can follow the mouse cursor. - self.notify(); + self.refresh(); } else if event.is::() { // If this was a mouse up event, cancel the active drag and redraw // the window. self.active_drag = None; - self.notify(); + self.refresh(); } } } @@ -2169,7 +2173,7 @@ impl VisualContext for WindowContext<'_> { { let view = self.new_view(build_view); self.window.root_view = Some(view.clone().into()); - self.notify(); + self.refresh(); view } @@ -2583,7 +2587,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } if !self.window.drawing { - self.window_cx.notify(); + self.window_cx.window.dirty = true; self.window_cx.app.push_effect(Effect::Notify { emitter: self.view.model.entity_id, }); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index d936716032a53b432d2f6f1a5dc6b79069656c8b..9cc55f0e7451698b3cfcc292cb0c8929ff2b489e 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -584,7 +584,7 @@ impl TerminalElement { this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); if handled { - cx.notify(); + cx.refresh(); } } }); diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index fb823b05dba3d18cea7992948abb50f827167e1c..bc27e5eea2f1ee4e01db197395ab9aeccdf3fb27 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -55,7 +55,7 @@ impl PopoverMenu { } } *menu2.borrow_mut() = None; - cx.notify(); + cx.refresh(); }) .detach(); cx.focus_view(&new_menu); diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 8bf40f61a8264ec3b00eec3ec844efa247876da7..56590cdde89f99a8f51f8bb4b10ddee7a99fc7bd 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -153,7 +153,7 @@ impl Element for RightClickMenu { } } *menu2.borrow_mut() = None; - cx.notify(); + cx.refresh(); }) .detach(); cx.focus_view(&new_menu); @@ -166,7 +166,7 @@ impl Element for RightClickMenu { } else { cx.mouse_position() }; - cx.notify(); + cx.refresh(); } }); } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 236daf60f8a747f4d43f4bc0a73109cd16e2ad3e..9ce191bc94644c3fa714d8b4c696aa0c7ae5f0e5 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -698,7 +698,7 @@ mod element { // todo!(schedule serialize) // workspace.schedule_serialize(cx); - cx.notify(); + cx.refresh(); } fn push_handle( From a4ef1bc096792e94610ea2bdb38fff1d887db71b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 23:06:10 +0100 Subject: [PATCH 11/98] Rename reuse_geometry to reuse_view --- crates/gpui/src/scene.rs | 2 +- crates/gpui/src/view.rs | 2 +- crates/gpui/src/window.rs | 82 ++++++++++++++++----------------------- 3 files changed, 35 insertions(+), 51 deletions(-) diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 9c63f803254b8f099be1cb7c369b41458a253df2..4b716413f50b8c0458023ce2cea926cb206376fa 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -125,7 +125,7 @@ impl Scene { } } - pub fn insert_views_from_scene(&mut self, views: &FxHashSet, prev_scene: &mut Self) { + pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { for shadow in prev_scene.shadows.drain(..) { if views.contains(&EntityId::from(shadow.view_id as u64)) { let order = &prev_scene.orders_by_layer[&shadow.layer_id]; diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 22443a939546d9c86a534d62407ca18d090e3db4..961c6aef64cde75b6ea1554a0dd6c16f1c8326af 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -292,7 +292,7 @@ impl Element for AnyView { && !cx.window.dirty_views.contains(&self.entity_id()) && !cx.window.refreshing { - cx.reuse_geometry(); + cx.reuse_view(); return; } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index be7391495174252a190bc69dadb6c7d1cc820d2a..2e84260b9e45aa89593e568296c623750189a3a8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -347,6 +347,37 @@ impl Frame { .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) .unwrap_or_default() } + + fn reuse_views(&mut self, prev_frame: &mut Self) { + // Reuse mouse listeners that didn't change since the last frame. + for (type_id, listeners) in &mut prev_frame.mouse_listeners { + let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); + for (order, view_id, listener) in listeners.drain(..) { + if self.reused_views.contains(&view_id) { + next_listeners.push((order, view_id, listener)); + } + } + } + + // Reuse entries in the depth map that didn't change since the last frame. + for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { + if self.reused_views.contains(&view_id) { + self.depth_map.push((order, view_id, bounds)); + } + } + self.depth_map.sort_by(|a, b| a.0.cmp(&b.0)); + + // Retain element states for views that didn't change since the last frame. + for (element_id, state) in prev_frame.element_states.drain() { + if self.reused_views.contains(&state.parent_view_id) { + self.element_states.entry(element_id).or_insert(state); + } + } + + // Reuse geometry that didn't change since the last frame. + self.scene + .reuse_views(&self.reused_views, &mut prev_frame.scene); + } } impl Window { @@ -1376,7 +1407,7 @@ impl<'a> WindowContext<'a> { ); } - pub(crate) fn reuse_geometry(&mut self) { + pub(crate) fn reuse_view(&mut self) { let view_id = self.parent_view_id().unwrap(); let window = &mut self.window; let grafted_view_ids = window @@ -1450,56 +1481,9 @@ impl<'a> WindowContext<'a> { self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); - // Reuse mouse listeners that didn't change since the last frame. - for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { - let next_listeners = self - .window - .next_frame - .mouse_listeners - .entry(*type_id) - .or_default(); - for (order, view_id, listener) in listeners.drain(..) { - if self.window.next_frame.reused_views.contains(&view_id) { - next_listeners.push((order, view_id, listener)); - } - } - } - - // Reuse entries in the depth map that didn't change since the last frame. - for (order, view_id, bounds) in self.window.rendered_frame.depth_map.drain(..) { - if self.window.next_frame.reused_views.contains(&view_id) { - self.window - .next_frame - .depth_map - .push((order, view_id, bounds)); - } - } self.window .next_frame - .depth_map - .sort_by(|a, b| a.0.cmp(&b.0)); - - // Retain element states for views that didn't change since the last frame. - for (element_id, state) in self.window.rendered_frame.element_states.drain() { - if self - .window - .next_frame - .reused_views - .contains(&state.parent_view_id) - { - self.window - .next_frame - .element_states - .entry(element_id) - .or_insert(state); - } - } - - // Reuse geometry that didn't change since the last frame. - self.window.next_frame.scene.insert_views_from_scene( - &self.window.next_frame.reused_views, - &mut self.window.rendered_frame.scene, - ); + .reuse_views(&mut self.window.rendered_frame); self.window.next_frame.scene.finish(); let previous_focus_path = self.window.rendered_frame.focus_path(); From 142a8b68c856264773f9cb593a155de378db87df Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 12:28:48 +0100 Subject: [PATCH 12/98] Avoid casting view ids to u32 Also, it looks like using a u64 directly doesn't work well with Metal shaders, so we unpack the u64 into two u32s. --- crates/editor/src/element.rs | 10 +++---- crates/gpui/src/scene.rs | 58 +++++++++++++++++++++++++----------- crates/gpui/src/window.rs | 18 +++++------ 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e..53578392e8cf2c95c3415f5e5e3de950c5db4abc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,11 +26,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, - InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, - SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, - TextStyle, View, ViewContext, WindowContext, + CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, + InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, + ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, + Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 4b716413f50b8c0458023ce2cea926cb206376fa..4179a1659e768c3967cbd18df030db0779535f79 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -13,6 +13,30 @@ pub(crate) type PathVertex_ScaledPixels = PathVertex; pub type LayerId = u32; pub type DrawOrder = u32; +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[repr(C)] +pub struct ViewId { + low_bits: u32, + high_bits: u32, +} + +impl From for ViewId { + fn from(value: EntityId) -> Self { + let value = value.as_u64(); + Self { + low_bits: value as u32, + high_bits: (value >> 32) as u32, + } + } +} + +impl From for EntityId { + fn from(value: ViewId) -> Self { + let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32); + value.into() + } +} + #[derive(Default)] pub struct Scene { layers_by_order: BTreeMap, @@ -127,49 +151,49 @@ impl Scene { pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { for shadow in prev_scene.shadows.drain(..) { - if views.contains(&EntityId::from(shadow.view_id as u64)) { + if views.contains(&shadow.view_id.into()) { let order = &prev_scene.orders_by_layer[&shadow.layer_id]; self.insert(&order, shadow); } } for quad in prev_scene.quads.drain(..) { - if views.contains(&EntityId::from(quad.view_id as u64)) { + if views.contains(&quad.view_id.into()) { let order = &prev_scene.orders_by_layer[&quad.layer_id]; self.insert(&order, quad); } } for path in prev_scene.paths.drain(..) { - if views.contains(&EntityId::from(path.view_id as u64)) { + if views.contains(&path.view_id.into()) { let order = &prev_scene.orders_by_layer[&path.layer_id]; self.insert(&order, path); } } for underline in prev_scene.underlines.drain(..) { - if views.contains(&EntityId::from(underline.view_id as u64)) { + if views.contains(&underline.view_id.into()) { let order = &prev_scene.orders_by_layer[&underline.layer_id]; self.insert(&order, underline); } } for sprite in prev_scene.monochrome_sprites.drain(..) { - if views.contains(&EntityId::from(sprite.view_id as u64)) { + if views.contains(&sprite.view_id.into()) { let order = &prev_scene.orders_by_layer[&sprite.layer_id]; self.insert(&order, sprite); } } for sprite in prev_scene.polychrome_sprites.drain(..) { - if views.contains(&EntityId::from(sprite.view_id as u64)) { + if views.contains(&sprite.view_id.into()) { let order = &prev_scene.orders_by_layer[&sprite.layer_id]; self.insert(&order, sprite); } } for surface in prev_scene.surfaces.drain(..) { - if views.contains(&EntityId::from(surface.view_id as u64)) { + if views.contains(&surface.view_id.into()) { let order = &prev_scene.orders_by_layer[&surface.layer_id]; self.insert(&order, surface); } @@ -470,7 +494,7 @@ pub(crate) enum PrimitiveBatch<'a> { #[derive(Default, Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Quad { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -502,7 +526,7 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Underline { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -533,7 +557,7 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Shadow { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -564,7 +588,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct MonochromeSprite { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -597,7 +621,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct PolychromeSprite { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -630,7 +654,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Surface { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -662,7 +686,7 @@ pub(crate) struct PathId(pub(crate) usize); #[derive(Debug)] pub struct Path { pub(crate) id: PathId, - pub(crate) view_id: u32, + pub(crate) view_id: ViewId, layer_id: LayerId, order: DrawOrder, pub(crate) bounds: Bounds

, @@ -678,9 +702,9 @@ impl Path { pub fn new(start: Point) -> Self { Self { id: PathId(0), - view_id: 0, - layer_id: 0, - order: 0, + view_id: ViewId::default(), + layer_id: LayerId::default(), + order: DrawOrder::default(), vertices: Vec::new(), start, current: start, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2e84260b9e45aa89593e568296c623750189a3a8..34df4b24236be52108e68771ded67140e4a43b0c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1102,7 +1102,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Shadow { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds: shadow_bounds.scale(scale_factor), @@ -1127,7 +1127,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Quad { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds: quad.bounds.scale(scale_factor), @@ -1148,7 +1148,7 @@ impl<'a> WindowContext<'a> { path.content_mask = content_mask; path.color = color.into(); - path.view_id = view_id.as_u64() as u32; + path.view_id = view_id.into(); let window = &mut *self.window; window .next_frame @@ -1180,7 +1180,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Underline { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds: bounds.scale(scale_factor), @@ -1236,7 +1236,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1290,7 +1290,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1335,7 +1335,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1374,7 +1374,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1397,7 +1397,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Surface { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, From d088ace4047e6a97a35b7372fc18dc0ac1823668 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 12:48:05 +0100 Subject: [PATCH 13/98] Explicitly push a node in the dispatch tree when painting a new view --- crates/editor/src/element.rs | 10 +++++----- crates/gpui/src/key_dispatch.rs | 9 --------- crates/gpui/src/view.rs | 6 +++--- crates/gpui/src/window.rs | 15 +++++++++++++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 53578392e8cf2c95c3415f5e5e3de950c5db4abc..7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,11 +26,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, - InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, - ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, - Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, + CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, + InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, + SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, + TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index b3fdddf00f0c20fcfeda0befab58bb0b485501bc..06d502d7780c9c59dc180d3508d7e4074009dbb6 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -73,15 +73,6 @@ impl DispatchTree { focus_id: Option, view_id: Option, ) { - // Associate a view id to this only if it is the root node for the view. - let view_id = view_id.and_then(|view_id| { - if self.view_node_ids.contains_key(&view_id) { - None - } else { - Some(view_id) - } - }); - let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 961c6aef64cde75b6ea1554a0dd6c16f1c8326af..968fbbd94cd142bdfc6629539da997652f71d91c 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -97,7 +97,7 @@ impl Element for View { } fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { - cx.with_view_id(self.entity_id(), |cx| element.take().unwrap().paint(cx)); + cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx)); } } @@ -230,7 +230,7 @@ impl AnyView { available_space: Size, cx: &mut WindowContext, ) { - cx.with_view_id(self.entity_id(), |cx| { + cx.paint_view(self.entity_id(), |cx| { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); cx.compute_layout(layout_id, available_space); @@ -278,7 +278,7 @@ impl Element for AnyView { } fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - cx.with_view_id(self.entity_id(), |cx| { + cx.paint_view(self.entity_id(), |cx| { if !self.cache { state.element.take().unwrap().paint(cx); return; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 34df4b24236be52108e68771ded67140e4a43b0c..3bdd8607c031008e161b64d607a6b41ff870f702 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1942,13 +1942,12 @@ impl<'a> WindowContext<'a> { focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R { - let parent_view_id = self.parent_view_id(); let window = &mut self.window; let focus_id = focus_handle.as_ref().map(|handle| handle.id); window .next_frame .dispatch_tree - .push_node(context.clone(), focus_id, parent_view_id); + .push_node(context.clone(), focus_id, None); let result = f(focus_handle, self); @@ -1968,6 +1967,18 @@ impl<'a> WindowContext<'a> { result } + pub(crate) fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + self.with_view_id(view_id, |cx| { + cx.window + .next_frame + .dispatch_tree + .push_node(None, None, Some(view_id)); + let result = f(cx); + cx.window.next_frame.dispatch_tree.pop_node(); + result + }) + } + /// Update or initialize state for an element with the given id that lives across multiple /// frames. If an element with this id existed in the rendered frame, its state will be passed /// to the given closure. The state returned by the closure will be stored so it can be referenced From 50ccdf5c167897baf39a929f8ec714d053733631 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 13:22:59 +0100 Subject: [PATCH 14/98] Reuse input handler when reusing a view tree --- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 4 +- crates/gpui/src/platform/test/window.rs | 4 +- crates/gpui/src/window.rs | 86 ++++++++++++++++++------- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index c41bf998f698443d5b6ca93dee2966206596d533..5a2335919ebe6ff9b2277e02d4f6f55b4dc9a80c 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -145,7 +145,7 @@ pub trait PlatformWindow { fn modifiers(&self) -> Modifiers; fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: Box); - fn clear_input_handler(&mut self); + fn take_input_handler(&mut self) -> Option>; fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 7c96fc0198895ce426f483c2c03ce0c888df8d3c..534c43284078697333c6a7e95186160b13302ad6 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -770,8 +770,8 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().input_handler = Some(input_handler); } - fn clear_input_handler(&mut self) { - self.0.as_ref().lock().input_handler = None; + fn take_input_handler(&mut self) -> Option> { + self.0.as_ref().lock().input_handler.take() } fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver { diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 029fd7300998f55f089696d6e01199b9c687ddfd..f05e13e3a027e2be9d4f17690fe7162a73396d03 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -167,8 +167,8 @@ impl PlatformWindow for TestWindow { self.0.lock().input_handler = Some(input_handler); } - fn clear_input_handler(&mut self) { - self.0.lock().input_handler = None; + fn take_input_handler(&mut self) -> Option> { + self.0.lock().input_handler.take() } fn prompt( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 3bdd8607c031008e161b64d607a6b41ff870f702..65f6aa42fffdaab131f4f9c96e65abb756ad5071 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -296,6 +296,11 @@ pub(crate) struct ElementStateBox { type_name: &'static str, } +struct RequestedInputHandler { + view_id: EntityId, + handler: Option>, +} + pub(crate) struct Frame { focus: Option, window_active: bool, @@ -308,6 +313,7 @@ pub(crate) struct Frame { pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, + requested_input_handler: Option, pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -323,9 +329,10 @@ impl Frame { scene: Scene::default(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, - depth_map: Default::default(), + depth_map: Vec::new(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), + requested_input_handler: None, view_stack: Vec::new(), reused_views: FxHashSet::default(), } @@ -947,7 +954,7 @@ impl<'a> WindowContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let order = self.window.next_frame.z_index_stack.clone(); self.window .next_frame @@ -1036,7 +1043,7 @@ impl<'a> WindowContext<'a> { /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); self.window .next_frame .depth_map @@ -1093,7 +1100,7 @@ impl<'a> WindowContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; @@ -1121,7 +1128,7 @@ impl<'a> WindowContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1144,7 +1151,7 @@ impl<'a> WindowContext<'a> { pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); path.content_mask = content_mask; path.color = color.into(); @@ -1174,7 +1181,7 @@ impl<'a> WindowContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1231,7 +1238,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1284,7 +1291,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1329,7 +1336,7 @@ impl<'a> WindowContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1368,7 +1375,7 @@ impl<'a> WindowContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1392,7 +1399,7 @@ impl<'a> WindowContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1408,14 +1415,27 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_view(&mut self) { - let view_id = self.parent_view_id().unwrap(); - let window = &mut self.window; - let grafted_view_ids = window + let view_id = self.parent_view_id(); + let grafted_view_ids = self + .window .next_frame .dispatch_tree - .graft(view_id, &mut window.rendered_frame.dispatch_tree); + .graft(view_id, &mut self.window.rendered_frame.dispatch_tree); for view_id in grafted_view_ids { - assert!(window.next_frame.reused_views.insert(view_id)); + assert!(self.window.next_frame.reused_views.insert(view_id)); + + // Reuse the previous input handler if it was associated with one of + // the views grafted from the tree in the previous frame. + if self + .window + .rendered_frame + .requested_input_handler + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.requested_input_handler = + self.window.rendered_frame.requested_input_handler.take(); + } } } @@ -1430,7 +1450,11 @@ impl<'a> WindowContext<'a> { } self.text_system().start_frame(); - self.window.platform_window.clear_input_handler(); + if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() + { + requested_handler.handler = self.window.platform_window.take_input_handler(); + } + self.window.layout_engine.as_mut().unwrap().clear(); self.window.next_frame.clear(); let root_view = self.window.root_view.take().unwrap(); @@ -1486,6 +1510,13 @@ impl<'a> WindowContext<'a> { .reuse_views(&mut self.window.rendered_frame); self.window.next_frame.scene.finish(); + // Register requested input handler with the platform window. + if let Some(requested_input) = self.window.next_frame.requested_input_handler.as_mut() { + if let Some(handler) = requested_input.handler.take() { + self.window.platform_window.set_input_handler(handler); + } + } + let previous_focus_path = self.window.rendered_frame.focus_path(); let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); @@ -2054,7 +2085,7 @@ impl<'a> WindowContext<'a> { result } else { let (result, state) = f(None, cx); - let parent_view_id = cx.parent_view_id().unwrap(); + let parent_view_id = cx.parent_view_id(); cx.window_mut() .next_frame .element_states @@ -2072,8 +2103,13 @@ impl<'a> WindowContext<'a> { }) } - fn parent_view_id(&self) -> Option { - self.window.next_frame.view_stack.last().copied() + fn parent_view_id(&self) -> EntityId { + *self + .window + .next_frame + .view_stack + .last() + .expect("a view should always be on the stack while drawing") } /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the @@ -2087,9 +2123,11 @@ impl<'a> WindowContext<'a> { input_handler: impl PlatformInputHandler, ) { if focus_handle.is_focused(self) { - self.window - .platform_window - .set_input_handler(Box::new(input_handler)); + let view_id = self.parent_view_id(); + self.window.next_frame.requested_input_handler = Some(RequestedInputHandler { + view_id, + handler: Some(Box::new(input_handler)), + }) } } From 18eaefd0ed31b90d5342c58e853365be61dfe0d0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 15:03:34 +0100 Subject: [PATCH 15/98] Reuse cursor style when reusing a view tree --- crates/gpui/src/window.rs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 65f6aa42fffdaab131f4f9c96e65abb756ad5071..7e38353e477d4002d4ee3dde58f1f86be3dba21e 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -273,7 +273,6 @@ pub struct Window { default_prevented: bool, mouse_position: Point, modifiers: Modifiers, - requested_cursor_style: Option, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, @@ -314,6 +313,8 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, + cursor_styles: FxHashMap, + requested_cursor_style: Option, pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -333,6 +334,8 @@ impl Frame { content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, + cursor_styles: FxHashMap::default(), + requested_cursor_style: None, view_stack: Vec::new(), reused_views: FxHashSet::default(), } @@ -346,6 +349,9 @@ impl Frame { self.next_stacking_order_id = 0; self.reused_views.clear(); self.scene.clear(); + self.requested_input_handler.take(); + self.cursor_styles.clear(); + self.requested_cursor_style.take(); debug_assert_eq!(self.view_stack.len(), 0); } @@ -469,7 +475,6 @@ impl Window { default_prevented: true, mouse_position, modifiers, - requested_cursor_style: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), @@ -1037,7 +1042,9 @@ impl<'a> WindowContext<'a> { /// Update the cursor style at the platform level. pub fn set_cursor_style(&mut self, style: CursorStyle) { - self.window.requested_cursor_style = Some(style) + let view_id = self.parent_view_id(); + self.window.next_frame.cursor_styles.insert(view_id, style); + self.window.next_frame.requested_cursor_style = Some(style); } /// Called during painting to track which z-index is on top at each pixel position @@ -1436,6 +1443,11 @@ impl<'a> WindowContext<'a> { self.window.next_frame.requested_input_handler = self.window.rendered_frame.requested_input_handler.take(); } + + if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { + self.window.next_frame.cursor_styles.insert(view_id, style); + self.window.next_frame.requested_cursor_style = Some(style); + } } } @@ -1505,6 +1517,17 @@ impl<'a> WindowContext<'a> { self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); + // Set the cursor only if we're the active window. + let cursor_style = self + .window + .next_frame + .requested_cursor_style + .take() + .unwrap_or(CursorStyle::Arrow); + if self.is_window_active() { + self.platform.set_cursor_style(cursor_style); + } + self.window .next_frame .reuse_views(&mut self.window.rendered_frame); @@ -1523,16 +1546,6 @@ impl<'a> WindowContext<'a> { let current_focus_path = self.window.rendered_frame.focus_path(); let current_window_active = self.window.rendered_frame.window_active; - // Set the cursor only if we're the active window. - let cursor_style = self - .window - .requested_cursor_style - .take() - .unwrap_or(CursorStyle::Arrow); - if self.is_window_active() { - self.platform.set_cursor_style(cursor_style); - } - self.window.refreshing = false; self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); From cbbba41748092724bcf3d3eefe1667894d958ba8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 16:57:06 +0100 Subject: [PATCH 16/98] Reuse line layouts when reusing view --- crates/gpui/src/text_system.rs | 16 +++++-- crates/gpui/src/text_system/line_layout.rs | 51 +++++++++++++++++++--- crates/gpui/src/window.rs | 36 ++++++++------- 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 47073bcde0ef77b7b6674d0c0a24b7dfa107e948..093ef3a7d5d0537975381ea1086eea68bdaf47bd 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -9,11 +9,11 @@ pub use line_layout::*; pub use line_wrapper::*; use crate::{ - px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, - UnderlineStyle, + px, Bounds, DevicePixels, EntityId, Hsla, Pixels, PlatformTextSystem, Point, Result, + SharedString, Size, UnderlineStyle, }; use anyhow::anyhow; -use collections::FxHashMap; +use collections::{FxHashMap, FxHashSet}; use core::fmt; use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; @@ -186,6 +186,10 @@ impl TextSystem { } } + pub fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { + self.line_layout_cache.with_view(view_id, f) + } + pub fn layout_line( &self, text: &str, @@ -361,7 +365,11 @@ impl TextSystem { } pub fn start_frame(&self) { - self.line_layout_cache.start_frame() + self.line_layout_cache.start_frame(); + } + + pub fn end_frame(&self, reused_views: &FxHashSet) { + self.line_layout_cache.end_frame(reused_views) } pub fn line_wrapper( diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 6506d7794c7b4693e61af6d278c06a191528f448..909f0513467a9f3f173e2edfeb489472fcd7afc8 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -1,5 +1,5 @@ -use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size}; -use collections::FxHashMap; +use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size}; +use collections::{FxHashMap, FxHashSet}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -236,6 +236,7 @@ impl WrappedLineLayout { } pub(crate) struct LineLayoutCache { + view_stack: Mutex>, previous_frame: Mutex>>, current_frame: RwLock>>, previous_frame_wrapped: Mutex>>, @@ -246,6 +247,7 @@ pub(crate) struct LineLayoutCache { impl LineLayoutCache { pub fn new(platform_text_system: Arc) -> Self { Self { + view_stack: Mutex::default(), previous_frame: Mutex::default(), current_frame: RwLock::default(), previous_frame_wrapped: Mutex::default(), @@ -254,11 +256,43 @@ impl LineLayoutCache { } } - pub fn start_frame(&self) { + pub fn end_frame(&self, reused_views: &FxHashSet) { + debug_assert_eq!(self.view_stack.lock().len(), 0); + let mut prev_frame = self.previous_frame.lock(); let mut curr_frame = self.current_frame.write(); + for (key, layout) in prev_frame.drain() { + if key + .parent_view_id + .map_or(false, |view_id| reused_views.contains(&view_id)) + { + curr_frame.insert(key, layout); + } + } std::mem::swap(&mut *prev_frame, &mut *curr_frame); - curr_frame.clear(); + + let mut prev_frame_wrapped = self.previous_frame_wrapped.lock(); + let mut curr_frame_wrapped = self.current_frame_wrapped.write(); + for (key, layout) in prev_frame_wrapped.drain() { + if key + .parent_view_id + .map_or(false, |view_id| reused_views.contains(&view_id)) + { + curr_frame_wrapped.insert(key, layout); + } + } + std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped); + } + + pub fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { + self.view_stack.lock().push(view_id); + let result = f(); + self.view_stack.lock().pop(); + result + } + + fn parent_view_id(&self) -> Option { + self.view_stack.lock().last().copied() } pub fn layout_wrapped_line( @@ -273,6 +307,7 @@ impl LineLayoutCache { font_size, runs, wrap_width, + parent_view_id: self.parent_view_id(), } as &dyn AsCacheKeyRef; let current_frame = self.current_frame_wrapped.upgradable_read(); @@ -301,6 +336,7 @@ impl LineLayoutCache { font_size, runs: SmallVec::from(runs), wrap_width, + parent_view_id: self.parent_view_id(), }; current_frame.insert(key, layout.clone()); layout @@ -313,6 +349,7 @@ impl LineLayoutCache { font_size, runs, wrap_width: None, + parent_view_id: self.parent_view_id(), } as &dyn AsCacheKeyRef; let current_frame = self.current_frame.upgradable_read(); @@ -331,6 +368,7 @@ impl LineLayoutCache { font_size, runs: SmallVec::from(runs), wrap_width: None, + parent_view_id: self.parent_view_id(), }; current_frame.insert(key, layout.clone()); layout @@ -348,12 +386,13 @@ trait AsCacheKeyRef { fn as_cache_key_ref(&self) -> CacheKeyRef; } -#[derive(Eq)] +#[derive(Debug, Eq)] struct CacheKey { text: String, font_size: Pixels, runs: SmallVec<[FontRun; 1]>, wrap_width: Option, + parent_view_id: Option, } #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -362,6 +401,7 @@ struct CacheKeyRef<'a> { font_size: Pixels, runs: &'a [FontRun], wrap_width: Option, + parent_view_id: Option, } impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) { @@ -385,6 +425,7 @@ impl AsCacheKeyRef for CacheKey { font_size: self.font_size, runs: self.runs.as_slice(), wrap_width: self.wrap_width, + parent_view_id: self.parent_view_id, } } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e38353e477d4002d4ee3dde58f1f86be3dba21e..00d42144c73755a7c2f6557a97d77adbbf7a67a3 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1461,14 +1461,11 @@ impl<'a> WindowContext<'a> { self.window.focus_invalidated = false; } - self.text_system().start_frame(); if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() { requested_handler.handler = self.window.platform_window.take_input_handler(); } - self.window.layout_engine.as_mut().unwrap().clear(); - self.window.next_frame.clear(); let root_view = self.window.root_view.take().unwrap(); self.with_z_index(0, |cx| { @@ -1528,11 +1525,6 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } - self.window - .next_frame - .reuse_views(&mut self.window.rendered_frame); - self.window.next_frame.scene.finish(); - // Register requested input handler with the platform window. if let Some(requested_input) = self.window.next_frame.requested_input_handler.as_mut() { if let Some(handler) = requested_input.handler.take() { @@ -1540,16 +1532,25 @@ impl<'a> WindowContext<'a> { } } + self.window + .next_frame + .reuse_views(&mut self.window.rendered_frame); + self.window.next_frame.scene.finish(); + self.window.layout_engine.as_mut().unwrap().clear(); + self.text_system() + .end_frame(&self.window.next_frame.reused_views); + ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + + self.window.refreshing = false; + self.window.drawing = false; + let previous_focus_path = self.window.rendered_frame.focus_path(); let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); + self.window.next_frame.clear(); let current_focus_path = self.window.rendered_frame.focus_path(); let current_window_active = self.window.rendered_frame.window_active; - self.window.refreshing = false; - self.window.drawing = false; - ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - if previous_focus_path != current_focus_path || previous_window_active != current_window_active { @@ -2005,10 +2006,13 @@ impl<'a> WindowContext<'a> { view_id: EntityId, f: impl FnOnce(&mut Self) -> R, ) -> R { - self.window.next_frame.view_stack.push(view_id); - let result = f(self); - self.window.next_frame.view_stack.pop(); - result + let text_system = self.text_system().clone(); + text_system.with_view(view_id, || { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + }) } pub(crate) fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { From 101cedb5f7e1cbe86e8340a428870081c59c39a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 16:59:33 +0100 Subject: [PATCH 17/98] :lipstick: --- crates/gpui/src/text_system.rs | 8 ++------ crates/gpui/src/text_system/line_layout.rs | 2 +- crates/gpui/src/window.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 093ef3a7d5d0537975381ea1086eea68bdaf47bd..7c5adef12d7de60c3a87c7280a41d9fd4c5df5bf 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -364,12 +364,8 @@ impl TextSystem { Ok(lines) } - pub fn start_frame(&self) { - self.line_layout_cache.start_frame(); - } - - pub fn end_frame(&self, reused_views: &FxHashSet) { - self.line_layout_cache.end_frame(reused_views) + pub fn finish_frame(&self, reused_views: &FxHashSet) { + self.line_layout_cache.finish_frame(reused_views) } pub fn line_wrapper( diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 909f0513467a9f3f173e2edfeb489472fcd7afc8..6c466f9680e8ec8ff75812bcdf15a172c9ff6258 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -256,7 +256,7 @@ impl LineLayoutCache { } } - pub fn end_frame(&self, reused_views: &FxHashSet) { + pub fn finish_frame(&self, reused_views: &FxHashSet) { debug_assert_eq!(self.view_stack.lock().len(), 0); let mut prev_frame = self.previous_frame.lock(); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 00d42144c73755a7c2f6557a97d77adbbf7a67a3..02683d655da04b57210a3bef6f99f6c85ab75873 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -361,7 +361,7 @@ impl Frame { .unwrap_or_default() } - fn reuse_views(&mut self, prev_frame: &mut Self) { + fn finish(&mut self, prev_frame: &mut Self) { // Reuse mouse listeners that didn't change since the last frame. for (type_id, listeners) in &mut prev_frame.mouse_listeners { let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); @@ -390,6 +390,7 @@ impl Frame { // Reuse geometry that didn't change since the last frame. self.scene .reuse_views(&self.reused_views, &mut prev_frame.scene); + self.scene.finish(); } } @@ -1532,13 +1533,12 @@ impl<'a> WindowContext<'a> { } } - self.window - .next_frame - .reuse_views(&mut self.window.rendered_frame); - self.window.next_frame.scene.finish(); self.window.layout_engine.as_mut().unwrap().clear(); self.text_system() - .end_frame(&self.window.next_frame.reused_views); + .finish_frame(&self.window.next_frame.reused_views); + self.window + .next_frame + .finish(&mut self.window.rendered_frame); ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); self.window.refreshing = false; From 11b433dc1cfee216796f36766f3b7ba492998982 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 18:24:07 +0100 Subject: [PATCH 18/98] Move back to sorting entries in the depth map as we insert them --- crates/gpui/src/window.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 02683d655da04b57210a3bef6f99f6c85ab75873..c11f12d89186ed91ffd41a1dcbc2b12189f73cc1 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -375,10 +375,14 @@ impl Frame { // Reuse entries in the depth map that didn't change since the last frame. for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { if self.reused_views.contains(&view_id) { - self.depth_map.push((order, view_id, bounds)); + match self + .depth_map + .binary_search_by(|(level, _, _)| order.cmp(level)) + { + Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)), + } } } - self.depth_map.sort_by(|a, b| a.0.cmp(&b.0)); // Retain element states for views that didn't change since the last frame. for (element_id, state) in prev_frame.element_states.drain() { @@ -1052,10 +1056,10 @@ impl<'a> WindowContext<'a> { pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); let view_id = self.parent_view_id(); - self.window - .next_frame - .depth_map - .push((stacking_order, view_id, bounds)); + let depth_map = &mut self.window.next_frame.depth_map; + match depth_map.binary_search_by(|(level, _, _)| stacking_order.cmp(level)) { + Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)), + } } /// Returns true if there is no opaque layer containing the given point From 5feae86900ed7a2738fe79caa144388f56333f0c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 09:55:16 -0800 Subject: [PATCH 19/98] Avoid bright green separators when displaying untitled buffers in multi-buffers --- crates/editor/src/element.rs | 51 ++++++++++++++---------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7b33a3239d05e7a6e73aa072e3b946d057253d0f..7deda0ba46b1e7755ba9df6b4e84747caab28a7d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2265,11 +2265,9 @@ impl EditorElement { .map_or(range.context.start, |primary| primary.start); let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); - let jump_handler = cx.listener_for(&self.editor, move |editor, _, cx| { + cx.listener_for(&self.editor, move |editor, _, cx| { editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); - }); - - jump_handler + }) }); let element = if *starts_new_buffer { @@ -2349,34 +2347,25 @@ impl EditorElement { .text_color(cx.theme().colors().editor_line_number) .child("..."), ) - .map(|this| { - if let Some(jump_handler) = jump_handler { - this.child( - ButtonLike::new("jump to collapsed context") - .style(ButtonStyle::Transparent) - .full_width() - .on_click(jump_handler) - .tooltip(|cx| { - Tooltip::for_action( - "Jump to Buffer", - &OpenExcerpts, - cx, - ) - }) - .child( - div() - .h_px() - .w_full() - .bg(cx.theme().colors().border_variant) - .group_hover("", |style| { - style.bg(cx.theme().colors().border) - }), - ), + .child( + ButtonLike::new("jump to collapsed context") + .style(ButtonStyle::Transparent) + .full_width() + .child( + div() + .h_px() + .w_full() + .bg(cx.theme().colors().border_variant) + .group_hover("", |style| { + style.bg(cx.theme().colors().border) + }), ) - } else { - this.child(div().size_full().bg(gpui::green())) - } - }) + .when_some(jump_handler, |this, jump_handler| { + this.on_click(jump_handler).tooltip(|cx| { + Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx) + }) + }), + ) }; element.into_any() } From a8b8be47e065475c350f539918ac4aff9be9b4e9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 13:52:50 -0700 Subject: [PATCH 20/98] Don't hold platform lock while calling user callbacks Inspired by a bug where using Edit -> Copy from the menu created a deadlock. --- crates/gpui/src/platform/mac/platform.rs | 66 +++++++++++++++++------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8370e2a4953c1280a59d4a9cb74a93ae97214db2..67af23bf7bd72f886a9ef32b0baca8a7f16bc257 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -985,8 +985,12 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { if let Some(event) = InputEvent::from_native(native_event, None) { let platform = get_mac_platform(this); - if let Some(callback) = platform.0.lock().event.as_mut() { - if !callback(event) { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.event.take() { + drop(lock); + let result = callback(event); + platform.0.lock().event.get_or_insert(callback); + if !result { return; } } @@ -1011,30 +1015,42 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { if !has_open_windows { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().reopen.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.reopen.take() { + drop(lock); callback(); + platform.0.lock().reopen.get_or_insert(callback); } } } extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().become_active.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.become_active.take() { + drop(lock); callback(); + platform.0.lock().become_active.get_or_insert(callback); } } extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().resign_active.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.resign_active.take() { + drop(lock); callback(); + platform.0.lock().resign_active.get_or_insert(callback); } } extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().quit.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.quit.take() { + drop(lock); callback(); + platform.0.lock().quit.get_or_insert(callback); } } @@ -1054,22 +1070,27 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { .collect::>() }; let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().open_urls.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.open_urls.take() { + drop(lock); callback(urls); + platform.0.lock().open_urls.get_or_insert(callback); } } extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.menu_command.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_actions.get(index) { - callback(action.as_ref()); + if let Some(action) = lock.menu_actions.get(index) { + let action = action.boxed_clone(); + drop(lock); + callback(&*action); } - platform.menu_command = Some(callback); + platform.0.lock().menu_command.get_or_insert(callback); } } } @@ -1078,14 +1099,20 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { unsafe { let mut result = false; let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.validate_menu_command.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.validate_menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_actions.get(index) { + if let Some(action) = lock.menu_actions.get(index) { + let action = action.boxed_clone(); + drop(lock); result = callback(action.as_ref()); } - platform.validate_menu_command = Some(callback); + platform + .0 + .lock() + .validate_menu_command + .get_or_insert(callback); } result } @@ -1094,10 +1121,11 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { unsafe { let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.will_open_menu.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.will_open_menu.take() { + drop(lock); callback(); - platform.will_open_menu = Some(callback); + platform.0.lock().will_open_menu.get_or_insert(callback); } } } From 258c2fdad439f032e57c6643322e27cffce7c224 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 13:41:28 -0800 Subject: [PATCH 21/98] Fix routing of leader updates from unshared projects Previously, leader updates in unshared projects would be sent to all followers regardless of project, as if they were not scoped to any project. --- .../collab/src/tests/channel_buffer_tests.rs | 146 --------------- crates/collab/src/tests/following_tests.rs | 171 +++++++++++++++++- crates/editor/src/items.rs | 4 +- crates/util/src/util.rs | 10 + crates/workspace/src/workspace.rs | 19 +- 5 files changed, 198 insertions(+), 152 deletions(-) diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 19411ed892b98c613f15d52e19e1365af0738610..76cc8cb9e1793f8b40f184a80b8d9efaaeb6e6d3 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -599,152 +599,6 @@ async fn test_channel_buffers_and_server_restarts( }); } -#[gpui::test(iterations = 10)] -async fn test_following_to_channel_notes_without_a_shared_project( - deterministic: BackgroundExecutor, - mut cx_a: &mut TestAppContext, - mut cx_b: &mut TestAppContext, - mut cx_c: &mut TestAppContext, -) { - let mut server = TestServer::start(deterministic.clone()).await; - let client_a = server.create_client(cx_a, "user_a").await; - let client_b = server.create_client(cx_b, "user_b").await; - - let client_c = server.create_client(cx_c, "user_c").await; - - cx_a.update(editor::init); - cx_b.update(editor::init); - cx_c.update(editor::init); - cx_a.update(collab_ui::channel_view::init); - cx_b.update(collab_ui::channel_view::init); - cx_c.update(collab_ui::channel_view::init); - - let channel_1_id = server - .make_channel( - "channel-1", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b), (&client_c, cx_c)], - ) - .await; - let channel_2_id = server - .make_channel( - "channel-2", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b), (&client_c, cx_c)], - ) - .await; - - // Clients A, B, and C join a channel. - let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); - let active_call_c = cx_c.read(ActiveCall::global); - for (call, cx) in [ - (&active_call_a, &mut cx_a), - (&active_call_b, &mut cx_b), - (&active_call_c, &mut cx_c), - ] { - call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) - .await - .unwrap(); - } - deterministic.run_until_parked(); - - // Clients A, B, and C all open their own unshared projects. - client_a.fs().insert_tree("/a", json!({})).await; - client_b.fs().insert_tree("/b", json!({})).await; - client_c.fs().insert_tree("/c", json!({})).await; - let (project_a, _) = client_a.build_local_project("/a", cx_a).await; - let (project_b, _) = client_b.build_local_project("/b", cx_b).await; - let (project_c, _) = client_b.build_local_project("/c", cx_c).await; - let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); - let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); - - active_call_a - .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) - .await - .unwrap(); - - // Client A opens the notes for channel 1. - let channel_view_1_a = cx_a - .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) - .await - .unwrap(); - channel_view_1_a.update(cx_a, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); - notes.editor.update(cx, |editor, cx| { - editor.insert("Hello from A.", cx); - editor.change_selections(None, cx, |selections| { - selections.select_ranges(vec![3..4]); - }); - }); - }); - - // Client B follows client A. - workspace_b - .update(cx_b, |workspace, cx| { - workspace - .start_following(client_a.peer_id().unwrap(), cx) - .unwrap() - }) - .await - .unwrap(); - - // Client B is taken to the notes for channel 1, with the same - // text selected as client A. - deterministic.run_until_parked(); - let channel_view_1_b = workspace_b.update(cx_b, |workspace, cx| { - assert_eq!( - workspace.leader_for_pane(workspace.active_pane()), - Some(client_a.peer_id().unwrap()) - ); - workspace - .active_item(cx) - .expect("no active item") - .downcast::() - .expect("active item is not a channel view") - }); - channel_view_1_b.update(cx_b, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); - let editor = notes.editor.read(cx); - assert_eq!(editor.text(cx), "Hello from A."); - assert_eq!(editor.selections.ranges::(cx), &[3..4]); - }); - - // Client A opens the notes for channel 2. - eprintln!("opening -------------------->"); - - let channel_view_2_a = cx_a - .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) - .await - .unwrap(); - channel_view_2_a.update(cx_a, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); - }); - - // Client B is taken to the notes for channel 2. - deterministic.run_until_parked(); - - eprintln!("opening <--------------------"); - - let channel_view_2_b = workspace_b.update(cx_b, |workspace, cx| { - assert_eq!( - workspace.leader_for_pane(workspace.active_pane()), - Some(client_a.peer_id().unwrap()) - ); - workspace - .active_item(cx) - .expect("no active item") - .downcast::() - .expect("active item is not a channel view") - }); - channel_view_2_b.update(cx_b, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); - }); -} - #[gpui::test] async fn test_channel_buffer_changes( deterministic: BackgroundExecutor, diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 6106f8d5f1531302374de4456bf9fd9aff11b871..dc5488ebb335ba0e1ecce9800a7c689d217d5a0e 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1,9 +1,12 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use call::{ActiveCall, ParticipantLocation}; -use collab_ui::notifications::project_shared_notification::ProjectSharedNotification; +use collab_ui::{ + channel_view::ChannelView, + notifications::project_shared_notification::ProjectSharedNotification, +}; use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ - point, BackgroundExecutor, Context, SharedString, TestAppContext, View, VisualContext, + point, BackgroundExecutor, Context, Entity, SharedString, TestAppContext, View, VisualContext, VisualTestContext, }; use language::Capability; @@ -1822,3 +1825,167 @@ fn pane_summaries(workspace: &View, cx: &mut VisualTestContext) -> Ve .collect() }) } + +#[gpui::test(iterations = 10)] +async fn test_following_to_channel_notes_without_a_shared_project( + deterministic: BackgroundExecutor, + mut cx_a: &mut TestAppContext, + mut cx_b: &mut TestAppContext, + mut cx_c: &mut TestAppContext, +) { + let mut server = TestServer::start(deterministic.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + + cx_a.update(editor::init); + cx_b.update(editor::init); + cx_c.update(editor::init); + cx_a.update(collab_ui::channel_view::init); + cx_b.update(collab_ui::channel_view::init); + cx_c.update(collab_ui::channel_view::init); + + let channel_1_id = server + .make_channel( + "channel-1", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b), (&client_c, cx_c)], + ) + .await; + let channel_2_id = server + .make_channel( + "channel-2", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b), (&client_c, cx_c)], + ) + .await; + + // Clients A, B, and C join a channel. + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + for (call, cx) in [ + (&active_call_a, &mut cx_a), + (&active_call_b, &mut cx_b), + (&active_call_c, &mut cx_c), + ] { + call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) + .await + .unwrap(); + } + deterministic.run_until_parked(); + + // Clients A, B, and C all open their own unshared projects. + client_a + .fs() + .insert_tree("/a", json!({ "1.txt": "" })) + .await; + client_b.fs().insert_tree("/b", json!({})).await; + client_c.fs().insert_tree("/c", json!({})).await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_b, _) = client_b.build_local_project("/b", cx_b).await; + let (project_c, _) = client_b.build_local_project("/c", cx_c).await; + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); + + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + + // Client A opens the notes for channel 1. + let channel_notes_1_a = cx_a + .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) + .await + .unwrap(); + channel_notes_1_a.update(cx_a, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); + notes.editor.update(cx, |editor, cx| { + editor.insert("Hello from A.", cx); + editor.change_selections(None, cx, |selections| { + selections.select_ranges(vec![3..4]); + }); + }); + }); + + // Client B follows client A. + workspace_b + .update(cx_b, |workspace, cx| { + workspace + .start_following(client_a.peer_id().unwrap(), cx) + .unwrap() + }) + .await + .unwrap(); + + // Client B is taken to the notes for channel 1, with the same + // text selected as client A. + deterministic.run_until_parked(); + let channel_notes_1_b = workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.leader_for_pane(workspace.active_pane()), + Some(client_a.peer_id().unwrap()) + ); + workspace + .active_item(cx) + .expect("no active item") + .downcast::() + .expect("active item is not a channel view") + }); + channel_notes_1_b.update(cx_b, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); + let editor = notes.editor.read(cx); + assert_eq!(editor.text(cx), "Hello from A."); + assert_eq!(editor.selections.ranges::(cx), &[3..4]); + }); + + // Client A opens the notes for channel 2. + let channel_notes_2_a = cx_a + .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) + .await + .unwrap(); + channel_notes_2_a.update(cx_a, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); + }); + + // Client B is taken to the notes for channel 2. + deterministic.run_until_parked(); + let channel_notes_2_b = workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.leader_for_pane(workspace.active_pane()), + Some(client_a.peer_id().unwrap()) + ); + workspace + .active_item(cx) + .expect("no active item") + .downcast::() + .expect("active item is not a channel view") + }); + channel_notes_2_b.update(cx_b, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); + }); + + // Client A opens a local buffer in their unshared project. + let _unshared_editor_a1 = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "1.txt"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + // This does not send any leader update message to client B. + // If it did, an error would occur on client B, since this buffer + // is not shared with them. + deterministic.run_until_parked(); + workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.active_item(cx).expect("no active item").item_id(), + channel_notes_2_b.entity_id() + ); + }); +} diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 78f9b150512e6ee153b3165e88bc162ea015aa14..a8583d48afae6abfee508026a65d109f78aafc95 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -82,7 +82,9 @@ impl FollowableItem for Editor { let pane = pane.downgrade(); Some(cx.spawn(|mut cx| async move { - let mut buffers = futures::future::try_join_all(buffers).await?; + let mut buffers = futures::future::try_join_all(buffers) + .await + .debug_assert_ok("leaders don't share views for unshared buffers")?; let editor = pane.update(&mut cx, |pane, cx| { let mut editors = pane.items_of_type::(); editors.find(|editor| { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index e32dd88b86fab6f0e8ad3fa9de1ce7ce6e8793a6..a4031da8cde538eac1833a85d08068488086b9a7 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -137,6 +137,8 @@ pub trait ResultExt { type Ok; fn log_err(self) -> Option; + /// Assert that this result should never be an error in development or tests. + fn debug_assert_ok(self, reason: &str) -> Self; fn warn_on_err(self) -> Option; fn inspect_error(self, func: impl FnOnce(&E)) -> Self; } @@ -159,6 +161,14 @@ where } } + #[track_caller] + fn debug_assert_ok(self, reason: &str) -> Self { + if let Err(error) = &self { + debug_panic!("{reason} - {error:?}"); + } + self + } + fn warn_on_err(self) -> Option { match self { Ok(value) => Some(value), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index db8554712e68b091fa59d20f71c61e1824a5cf05..ca463e76e058c645839f853eadd8e0877ecca6da 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2608,11 +2608,20 @@ impl Workspace { let cx = &cx; move |item| { let item = item.to_followable_item_handle(cx)?; - if (project_id.is_none() || project_id != follower_project_id) - && item.is_project_item(cx) + + // If the item belongs to a particular project, then it should + // only be included if this project is shared, and the follower + // is in thie project. + // + // Some items, like channel notes, do not belong to a particular + // project, so they should be included regardless of whether the + // current project is shared, or what project the follower is in. + if item.is_project_item(cx) + && (project_id.is_none() || project_id != follower_project_id) { return None; } + let id = item.remote_id(client, cx)?.to_proto(); let variant = item.to_state_proto(cx)?; Some(proto::View { @@ -2790,8 +2799,12 @@ impl Workspace { update: proto::update_followers::Variant, cx: &mut WindowContext, ) -> Option<()> { + // If this update only applies to for followers in the current project, + // then skip it unless this project is shared. If it applies to all + // followers, regardless of project, then set `project_id` to none, + // indicating that it goes to all followers. let project_id = if project_only { - self.project.read(cx).remote_id() + Some(self.project.read(cx).remote_id()?) } else { None }; From a1049546a2577b8458e64d526dbb3ad866448e6a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:58:36 +0100 Subject: [PATCH 22/98] gpui: Validate font contents at load time. During layout of EditorElement we use 'm' character from current font to calculate sizes, panicking with fonts that do not have that character (e.g. Arabic fonts). It's not really EditorElement's fault, as it assumes that the font it's dealing with is gonna have that character available. To prevent a crash, I added validation while loading a family that a given font contains the glyphs we're gonna use down the line. --- crates/gpui/src/platform/mac/text_system.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 79ffb8dc8e4aa54b954494a8cae4f3ca6186b377..68f4a63326757f8a87217a133aef005d0c8e5c86 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -190,6 +190,9 @@ impl MacTextSystemState { for font in family.fonts() { let mut font = font.load()?; open_type::apply_features(&mut font, features); + let Some(_) = font.glyph_for_char('m') else { + continue; + }; let font_id = FontId(self.fonts.len()); font_ids.push(font_id); let postscript_name = font.postscript_name().unwrap(); From 8d294211db81ce1d70a2e0de80da7995700ccfd3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 23:05:27 +0100 Subject: [PATCH 23/98] settings.json: Suggest font names for buffer_font_family --- crates/gpui/src/text_system.rs | 3 +++ crates/theme/src/settings.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 2d3cc34f3fc09e6402ece055f9aa6ba45fe5434e..d80c9163a996be96580f459c13e0e8eba8e71d10 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -65,6 +65,9 @@ impl TextSystem { } } + pub fn all_font_families(&self) -> Vec { + self.platform_text_system.all_font_families() + } pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { self.platform_text_system.add_fonts(fonts) } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 3ecf1935a47eeb7fb4c8e4edbce9d99f33a2b3f3..e51ff81b012aceec95aa296e4b8d1a2b58642288 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -194,9 +194,21 @@ impl settings::Settings for ThemeSettings { ..Default::default() }; - root_schema - .definitions - .extend([("ThemeName".into(), theme_name_schema.into())]); + let available_fonts = cx + .text_system() + .all_font_families() + .into_iter() + .map(Value::String) + .collect(); + let fonts_schema = SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(available_fonts), + ..Default::default() + }; + root_schema.definitions.extend([ + ("ThemeName".into(), theme_name_schema.into()), + ("FontFamilies".into(), fonts_schema.into()), + ]); root_schema .schema @@ -204,10 +216,16 @@ impl settings::Settings for ThemeSettings { .as_mut() .unwrap() .properties - .extend([( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - )]); + .extend([ + ( + "theme".to_owned(), + Schema::new_ref("#/definitions/ThemeName".into()), + ), + ( + "buffer_font_family".to_owned(), + Schema::new_ref("#/definitions/FontFamilies".into()), + ), + ]); root_schema } From 9d50697caabc351255a15443c7d57cbbc846832d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 15:44:43 -0800 Subject: [PATCH 24/98] Temporarily avoid releasing livekit RemoteAudioTracks on drop This release call was added during the conversion to gpui2. I think it is probably valid, but want to remove it on the off chance that it is causing the crash that we're seeing in the `livekit.multicast` thread when leaving a room. --- crates/live_kit_client/src/prod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 0827c0cbb41092f7643891c71c8b8c1f279a0860..3082c9b5338d7449b00a86fe60287213acd1e79d 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -864,7 +864,10 @@ impl RemoteAudioTrack { impl Drop for RemoteAudioTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.0) } + // todo: uncomment this `CFRelease`, unless we find that it was causing + // the crash in the `livekit.multicast` thread. + // + // unsafe { CFRelease(self.native_track.0) } } } From 02029c945a0f9c7bbb2c680102afd5c2d66f93c3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:21:55 -0800 Subject: [PATCH 25/98] Suppress unused field warning --- crates/live_kit_client/src/prod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 3082c9b5338d7449b00a86fe60287213acd1e79d..f1660cc3d1a7ce9f7951cfd5f1a354158a41d334 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -868,6 +868,7 @@ impl Drop for RemoteAudioTrack { // the crash in the `livekit.multicast` thread. // // unsafe { CFRelease(self.native_track.0) } + let _ = self.native_track; } } From 08a4307d716046ac77233d82fbe90b77383024c9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:22:34 -0800 Subject: [PATCH 26/98] Fix failure to write to keychain because of dropping a future --- crates/client/src/client.rs | 7 ++----- crates/util/src/util.rs | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 0821a8e534d9713b312667dce33eb2c11c93d429..189402308487870bee29da52c9333283ceb7fd1f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1371,10 +1371,7 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { }) } -async fn write_credentials_to_keychain( - credentials: Credentials, - cx: &AsyncAppContext, -) -> Result<()> { +fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| { cx.write_credentials( &ZED_SERVER_URL, @@ -1384,7 +1381,7 @@ async fn write_credentials_to_keychain( })? } -async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { +fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index a4031da8cde538eac1833a85d08068488086b9a7..a2f8b87feea91e5ccb09aea4c752cd1c5a583fda 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -244,6 +244,7 @@ where } } +#[must_use] pub struct LogErrorFuture(F, log::Level, core::panic::Location<'static>); impl Future for LogErrorFuture From 5f5505fe9a533d0e00e770a4390de1f4cf008726 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:51:40 -0800 Subject: [PATCH 27/98] Don't pass zed-local flags through to zed --- script/zed-local | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/script/zed-local b/script/zed-local index 4519ede38cf06bab355d0e65c530b76426e9108c..090fbd58760cd04779340be19d06a199d2a0a01d 100755 --- a/script/zed-local +++ b/script/zed-local @@ -1,31 +1,44 @@ #!/usr/bin/env node +const HELP = ` +USAGE + zed-local [options] [zed args] + +OPTIONS + --help Print this help message + --release Build Zed in release mode + -2, -3, -4 Spawn 2, 3, or 4 Zed instances, with their windows tiled. + --top Arrange the Zed windows so they take up the top half of the screen. +`.trim(); + const { spawn, execFileSync } = require("child_process"); const RESOLUTION_REGEX = /(\d+) x (\d+)/; const DIGIT_FLAG_REGEX = /^--?(\d+)$/; -// Parse the number of Zed instances to spawn. let instanceCount = 1; let isReleaseMode = false; let isTop = false; const args = process.argv.slice(2); -for (const arg of args) { +while (args.length > 0) { + const arg = args[0]; + const digitMatch = arg.match(DIGIT_FLAG_REGEX); if (digitMatch) { instanceCount = parseInt(digitMatch[1]); - continue; - } - - if (arg == "--release") { + } else if (arg === "--release") { isReleaseMode = true; - continue; - } - - if (arg == "--top") { + } else if (arg === "--top") { isTop = true; + } else if (arg === "--help") { + console.log(HELP); + process.exit(0); + } else { + break; } + + args.shift(); } // Parse the resolution of the main screen From d20ed0aacfb7ca13eef23f21ca4a94128791ea1e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 20:21:41 -0700 Subject: [PATCH 28/98] Try sqwauk --- .github/actions/check_style/action.yml | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 5dc7c42b02f387fe76295e9ae110f28972ef8450..e6eed28b66b9fe73ceb8c1c40fb2f5a6a298ec15 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,16 +2,26 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + using: "composite" + steps: + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + + - name: Find modified migrations + shell: bash -euxo pipefail {0} + run: | + modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') + echo "::set-output name=file_names::$modified_migrations" + id: modified-migrations + - uses: sbdchd/squawk-action@v1 + with: + pattern: ${{ steps.modified-migrations.outputs.file_names }} From 403fa7fbc90572653ccc6a3cabc57c708e986f4b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 20:40:34 -0700 Subject: [PATCH 29/98] Try the manual approach... --- .github/actions/check_style/action.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index e6eed28b66b9fe73ceb8c1c40fb2f5a6a298ec15..3174376736c79817fe077702a1483a61a283d04e 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -19,9 +19,11 @@ runs: - name: Find modified migrations shell: bash -euxo pipefail {0} run: | + cargo install squawk --git https://github.com/sbdchd/squawk --tag v0.26.0 modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') - echo "::set-output name=file_names::$modified_migrations" - id: modified-migrations - - uses: sbdchd/squawk-action@v1 - with: - pattern: ${{ steps.modified-migrations.outputs.file_names }} + + SQUAWK_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') + SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') + SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') + squawk upload-to-github $(modified_migrations) From 51e4db7d702fee06724bc1e4644871ee5ed6c959 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 20:43:49 -0700 Subject: [PATCH 30/98] try again --- .github/actions/check_style/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 3174376736c79817fe077702a1483a61a283d04e..d2f648ea8fa1e0cc7d2a1d9b0eb508d7088b3455 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -21,8 +21,7 @@ runs: run: | cargo install squawk --git https://github.com/sbdchd/squawk --tag v0.26.0 modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') - - SQUAWK_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + SQUAWK_GITHUB_TOKEN=${{ github.token }} SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') From 87ccbf6c19223da70318dd3ff5ecea5ad533db51 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 21:09:50 -0700 Subject: [PATCH 31/98] One of these days... --- .github/actions/check_style/action.yml | 9 ++------- script/squawk | 27 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100755 script/squawk diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index d2f648ea8fa1e0cc7d2a1d9b0eb508d7088b3455..25020e4e4c5399026c1ab32622903a3779ba86b2 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -19,10 +19,5 @@ runs: - name: Find modified migrations shell: bash -euxo pipefail {0} run: | - cargo install squawk --git https://github.com/sbdchd/squawk --tag v0.26.0 - modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') - SQUAWK_GITHUB_TOKEN=${{ github.token }} - SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') - SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') - SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') - squawk upload-to-github $(modified_migrations) + export SQUAWK_GITHUB_TOKEN=${{ github.token }} + . ./script/squawk diff --git a/script/squawk b/script/squawk new file mode 100755 index 0000000000000000000000000000000000000000..e4ade6fbed6e77dd7fc791ed33988c02020f0078 --- /dev/null +++ b/script/squawk @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Squawk is a linter for database migrations. It helps identify dangerous patterns, and suggests alternatives. +# Squawk flagging an error does not mean that you need to take a different approach, but it does indicate you need to think about what you're doing. +# See also: https://squawkhq.com + +set -e + +SQUAWK_VERSION=0.26.0 +SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION" +SQUAWK_ARGS="--assume-in-transaction" + + +if [ ! -f "$SQUAWK_BIN" ]; then + curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64" + chmod +x "$SQUAWK_BIN" +fi + +if [ -n "$SQUAWK_GITHUB_TOKEN" ]; then + export SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') + export SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') + export SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') + + $SQUAWK_BIN $SQUAWK_ARGS upload-to-github $(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crates/collab/migrations/*.sql') +else + $SQUAWK_BIN $SQUAWK_ARGS $(git ls-files --others crates/collab/migrations/*.sql) $(git diff --name-only main crates/collab/migrations/*.sql) +fi From a81f48f36b4ba5d73e481e85b2981651df7388ac Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 12 Jan 2024 12:00:18 +0200 Subject: [PATCH 32/98] Use auto formatter settings for Zed repo. Some users might have a language server formatter set in their settings, which is exclusive with prettier formatting. That causes disruptions in the way whitespaces and other things are formatted, esp. in json and yaml files, hence enforce one formatter settings for the entire Zed repo. --- .zed/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.zed/settings.json b/.zed/settings.json index d4b3375b0d21f95128c6bffb2c7a92f8bf97916c..205d610046fef48bbfa03911d2ababe86755dc71 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,5 +1,6 @@ { "JSON": { "tab_size": 4 - } + }, + "formatter": "auto" } From a32ad3f9074026f1a7bf13e96b8534095e4bfc31 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Jan 2024 14:50:42 +0100 Subject: [PATCH 33/98] Fix editor tests --- crates/editor/src/element.rs | 58 ++++++++++++++++++++---------------- crates/gpui/src/window.rs | 12 ++++---- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 29d3e0aaea38011a120401eb54aabb1a311bd3b6..fcf2ff36108b319b7b18990e0c9280ad8b752f6e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3404,14 +3404,16 @@ mod tests { }) .unwrap(); let state = cx - .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + .update_window(window.into(), |view, cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); @@ -3496,14 +3498,16 @@ mod tests { }); let state = cx - .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + .update_window(window.into(), |view, cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); assert_eq!(state.selections.len(), 1); @@ -3558,14 +3562,16 @@ mod tests { let mut element = EditorElement::new(&editor, style); let state = cx - .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + .update_window(window.into(), |view, cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); let size = state.position_map.size; @@ -3582,8 +3588,8 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |_, cx| { - element.paint(bounds, &mut (), cx); + cx.update_window(window.into(), |view, cx| { + cx.paint_view(view.entity_id(), |cx| element.paint(bounds, &mut (), cx)) }) .unwrap() } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f34342899660ac65b0206cbf582b5790e761f9be..bc5e7ee765941cca2af6b875ffaac1fbc68cf58a 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2004,11 +2004,9 @@ impl<'a> WindowContext<'a> { result } - pub(crate) fn with_view_id( - &mut self, - view_id: EntityId, - f: impl FnOnce(&mut Self) -> R, - ) -> R { + /// Invoke the given function with the given view id present on the view stack. + /// This is a fairly low-level method used to layout views. + pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { let text_system = self.text_system().clone(); text_system.with_view(view_id, || { self.window.next_frame.view_stack.push(view_id); @@ -2018,7 +2016,9 @@ impl<'a> WindowContext<'a> { }) } - pub(crate) fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + /// Invoke the given function with the given view id present on the view stack. + /// This is a fairly low-level method used to paint views. + pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { self.with_view_id(view_id, |cx| { cx.window .next_frame From bc09ce6ffcdf9de313209107c836c2b1792749c7 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 12 Jan 2024 10:37:18 -0500 Subject: [PATCH 34/98] Clean up some theme TODOs (#4034) This PR cleans up some TODOs we had in the themes. We won't be addressing these in the immediate term, so no need to have them show up when folks are looking for TODOs to burn down before launch. I added some general notes as signposts until we're ready to revisit this. Release Notes: - N/A --- crates/theme/src/default_colors.rs | 29 +++++++++++++++++------------ crates/theme/src/one_themes.rs | 6 +++++- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 8cbfc23fa34655283ddaa5418e15922929c1b5bc..fb88afb7dacf79d947a170e26136b293d093542b 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -8,6 +8,11 @@ pub(crate) fn neutral() -> ColorScaleSet { sand() } +// Note: We aren't currently making use of the default colors, as all of the +// themes have a value set for each color. +// +// We'll need to revisit these once we're ready to launch user themes, which may +// not specify a value for each color (and thus should fall back to the defaults). impl ThemeColors { pub fn light() -> Self { let system = SystemColors::default(); @@ -23,12 +28,12 @@ impl ThemeColors { surface_background: neutral().light().step_2(), background: neutral().light().step_1(), element_background: neutral().light().step_3(), - element_hover: neutral().light_alpha().step_4(), // todo!("pick the right colors") + element_hover: neutral().light_alpha().step_4(), element_active: neutral().light_alpha().step_5(), element_selected: neutral().light_alpha().step_5(), - element_disabled: neutral().light_alpha().step_3(), // todo!("pick the right colors") - drop_target_background: blue().light_alpha().step_2(), // todo!("pick the right colors") - ghost_element_background: system.transparent, // todo!("pick the right colors") + element_disabled: neutral().light_alpha().step_3(), + drop_target_background: blue().light_alpha().step_2(), + ghost_element_background: system.transparent, ghost_element_hover: neutral().light_alpha().step_3(), ghost_element_active: neutral().light_alpha().step_4(), ghost_element_selected: neutral().light_alpha().step_5(), @@ -59,7 +64,7 @@ impl ThemeColors { scrollbar_track_background: gpui::transparent_black(), scrollbar_track_border: neutral().light().step_5(), editor_foreground: neutral().light().step_12(), - editor_background: neutral().light().step_1(), // todo!(this was inserted by Mikayla) + editor_background: neutral().light().step_1(), editor_gutter_background: neutral().light().step_1(), editor_subheader_background: neutral().light().step_2(), editor_active_line_background: neutral().light_alpha().step_3(), @@ -106,17 +111,17 @@ impl ThemeColors { surface_background: neutral().dark().step_2(), background: neutral().dark().step_1(), element_background: neutral().dark().step_3(), - element_hover: neutral().dark_alpha().step_4(), // todo!("pick the right colors") + element_hover: neutral().dark_alpha().step_4(), element_active: neutral().dark_alpha().step_5(), - element_selected: neutral().dark_alpha().step_5(), // todo!("pick the right colors") - element_disabled: neutral().dark_alpha().step_3(), // todo!("pick the right colors") + element_selected: neutral().dark_alpha().step_5(), + element_disabled: neutral().dark_alpha().step_3(), drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, - ghost_element_hover: neutral().dark_alpha().step_4(), // todo!("pick the right colors") - ghost_element_active: neutral().dark_alpha().step_5(), // todo!("pick the right colors") + ghost_element_hover: neutral().dark_alpha().step_4(), + ghost_element_active: neutral().dark_alpha().step_5(), ghost_element_selected: neutral().dark_alpha().step_5(), ghost_element_disabled: neutral().dark_alpha().step_3(), - text: neutral().dark().step_12(), // todo!("pick the right colors") + text: neutral().dark().step_12(), text_muted: neutral().dark().step_11(), text_placeholder: neutral().dark().step_10(), text_disabled: neutral().dark().step_9(), @@ -140,7 +145,7 @@ impl ThemeColors { scrollbar_thumb_hover_background: neutral().dark_alpha().step_4(), scrollbar_thumb_border: gpui::transparent_black(), scrollbar_track_background: gpui::transparent_black(), - scrollbar_track_border: neutral().dark().step_5(), // todo!(this was inserted by Mikayla) + scrollbar_track_border: neutral().dark().step_5(), editor_foreground: neutral().dark().step_12(), editor_background: neutral().dark().step_1(), editor_gutter_background: neutral().dark().step_1(), diff --git a/crates/theme/src/one_themes.rs b/crates/theme/src/one_themes.rs index d0bae590f6d32797ec65b34eb39de13d4befe177..fab3631d13ca890ca3ccb9fa2e06605534602abd 100644 --- a/crates/theme/src/one_themes.rs +++ b/crates/theme/src/one_themes.rs @@ -7,6 +7,10 @@ use crate::{ ThemeColors, ThemeFamily, ThemeStyles, }; +// Note: This theme family is not the one you see in Zed at the moment. +// This is a from-scratch rebuild that Nate started work on. We currently +// only use this in the tests, and the One family from the `themes/` directory +// is what gets loaded into Zed when running it. pub fn one_family() -> ThemeFamily { ThemeFamily { id: "one".to_string(), @@ -75,7 +79,7 @@ pub(crate) fn one_dark() -> Theme { tab_bar_background: bg, tab_inactive_background: bg, tab_active_background: editor, - search_match_background: bg, // todo!(this was inserted by Mikayla) + search_match_background: bg, editor_background: editor, editor_gutter_background: editor, From 817b641c17d2ab5aed2debed61e1dda269690540 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Jan 2024 17:36:11 +0100 Subject: [PATCH 35/98] Ensure editor elements invalidate their parent views on notify Co-Authored-By: Nathan Sobo Co-Authored-By: Conrad Irwin --- crates/editor/src/element.rs | 201 ++++++++++++++++++----------------- crates/gpui/src/window.rs | 34 +++--- 2 files changed, 125 insertions(+), 110 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fcf2ff36108b319b7b18990e0c9280ad8b752f6e..a2f1b0c09dd1f48bef0d8111054756d48e433589 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,11 +26,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, - InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, - SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, - TextStyle, View, ViewContext, WindowContext, + CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, + InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, + ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, + Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -2801,44 +2801,49 @@ impl Element for EditorElement { _element_state: Option, cx: &mut gpui::WindowContext, ) -> (gpui::LayoutId, Self::State) { - self.editor.update(cx, |editor, cx| { - editor.set_style(self.style.clone(), cx); - - let layout_id = match editor.mode { - EditorMode::SingleLine => { - let rem_size = cx.rem_size(); - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); - cx.request_layout(&style, None) - } - EditorMode::AutoHeight { max_lines } => { - let editor_handle = cx.view().clone(); - let max_line_number_width = - self.max_line_number_width(&editor.snapshot(cx), cx); - cx.request_measured_layout(Style::default(), move |known_dimensions, _, cx| { - editor_handle - .update(cx, |editor, cx| { - compute_auto_height_layout( - editor, - max_lines, - max_line_number_width, - known_dimensions, - cx, - ) - }) - .unwrap_or_default() - }) - } - EditorMode::Full => { - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - cx.request_layout(&style, None) - } - }; + cx.with_view_id(self.editor.entity_id(), |cx| { + self.editor.update(cx, |editor, cx| { + editor.set_style(self.style.clone(), cx); + + let layout_id = match editor.mode { + EditorMode::SingleLine => { + let rem_size = cx.rem_size(); + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); + cx.request_layout(&style, None) + } + EditorMode::AutoHeight { max_lines } => { + let editor_handle = cx.view().clone(); + let max_line_number_width = + self.max_line_number_width(&editor.snapshot(cx), cx); + cx.request_measured_layout( + Style::default(), + move |known_dimensions, _, cx| { + editor_handle + .update(cx, |editor, cx| { + compute_auto_height_layout( + editor, + max_lines, + max_line_number_width, + known_dimensions, + cx, + ) + }) + .unwrap_or_default() + }, + ) + } + EditorMode::Full => { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) + } + }; - (layout_id, ()) + (layout_id, ()) + }) }) } @@ -2850,65 +2855,67 @@ impl Element for EditorElement { ) { let editor = self.editor.clone(); - cx.with_text_style( - Some(gpui::TextStyleRefinement { - font_size: Some(self.style.text.font_size), - ..Default::default() - }), - |cx| { - let mut layout = self.compute_layout(bounds, cx); - let gutter_bounds = Bounds { - origin: bounds.origin, - size: layout.gutter_size, - }; - let text_bounds = Bounds { - origin: gutter_bounds.upper_right(), - size: layout.text_size, - }; + cx.paint_view(self.editor.entity_id(), |cx| { + cx.with_text_style( + Some(gpui::TextStyleRefinement { + font_size: Some(self.style.text.font_size), + ..Default::default() + }), + |cx| { + let mut layout = self.compute_layout(bounds, cx); + let gutter_bounds = Bounds { + origin: bounds.origin, + size: layout.gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: layout.text_size, + }; - let focus_handle = editor.focus_handle(cx); - let key_context = self.editor.read(cx).key_context(cx); - cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { - self.register_actions(cx); - self.register_key_listeners(cx); + let focus_handle = editor.focus_handle(cx); + let key_context = self.editor.read(cx).key_context(cx); + cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { + self.register_actions(cx); + self.register_key_listeners(cx); - 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); + 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() { cx.with_z_index(0, |cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }); - }) - } + self.paint_mouse_listeners( + bounds, + gutter_bounds, + text_bounds, + &layout, + cx, + ); + }); + if !layout.blocks.is_empty() { + 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(1, |cx| { - self.paint_overlays(text_bounds, &mut layout, cx); - }); + cx.with_z_index(1, |cx| { + self.paint_overlays(text_bounds, &mut layout, cx); + }); - cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); - }); - }) - }, - ); + cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); + }); + }) + }, + ) + }) } } @@ -3588,10 +3595,8 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |view, cx| { - cx.paint_view(view.entity_id(), |cx| element.paint(bounds, &mut (), cx)) - }) - .unwrap() + cx.update_window(window.into(), |view, cx| element.paint(bounds, &mut (), cx)) + .unwrap() } #[gpui::test] diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index bc5e7ee765941cca2af6b875ffaac1fbc68cf58a..619a1bfeb6c23071a1c7e8071b396eb908bcf0f8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2009,24 +2009,34 @@ impl<'a> WindowContext<'a> { pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { let text_system = self.text_system().clone(); text_system.with_view(view_id, || { - self.window.next_frame.view_stack.push(view_id); - let result = f(self); - self.window.next_frame.view_stack.pop(); - result + if self.window.next_frame.view_stack.last() == Some(&view_id) { + return f(self); + } else { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } }) } /// Invoke the given function with the given view id present on the view stack. /// This is a fairly low-level method used to paint views. pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.with_view_id(view_id, |cx| { - cx.window - .next_frame - .dispatch_tree - .push_node(None, None, Some(view_id)); - let result = f(cx); - cx.window.next_frame.dispatch_tree.pop_node(); - result + let text_system = self.text_system().clone(); + text_system.with_view(view_id, || { + if self.window.next_frame.view_stack.last() == Some(&view_id) { + return f(self); + } else { + self.window.next_frame.view_stack.push(view_id); + self.window + .next_frame + .dispatch_tree + .push_node(None, None, Some(view_id)); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } }) } From 981858ef3c280043524c30b9d44f47b5c71a7f2e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 10:01:18 -0700 Subject: [PATCH 36/98] Fix panic with many participants --- crates/ui/src/styles/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/styles/color.rs b/crates/ui/src/styles/color.rs index 434183e5606135cdcb7e420c023c1108d0aa0a42..1c9fd789d958a1d0b886ab23e62232fc219589d3 100644 --- a/crates/ui/src/styles/color.rs +++ b/crates/ui/src/styles/color.rs @@ -37,7 +37,7 @@ impl Color { Color::Info => cx.theme().status().info, Color::Placeholder => cx.theme().colors().text_placeholder, Color::Accent => cx.theme().colors().text_accent, - Color::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor, + Color::Player(i) => cx.theme().styles.player.color_for_participant(*i).cursor, Color::Error => cx.theme().status().error, Color::Selected => cx.theme().colors().text_accent, Color::Success => cx.theme().status().success, From f4a7f6f4c3cf0d257387464bef1c107b0c127258 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Jan 2024 18:06:18 +0100 Subject: [PATCH 37/98] Fix warning --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a2f1b0c09dd1f48bef0d8111054756d48e433589..f5c016a7c78cbee00bd5e606b3f8999fcb1a3661 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3595,7 +3595,7 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |view, cx| element.paint(bounds, &mut (), cx)) + cx.update_window(window.into(), |_, cx| element.paint(bounds, &mut (), cx)) .unwrap() } From dc158f708f2334205feee39c8ccb7c57edd573c1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 10:23:22 -0700 Subject: [PATCH 38/98] Update chat panel with current channel --- crates/call/src/call.rs | 2 ++ crates/call/src/room.rs | 3 +++ crates/collab_ui/src/chat_panel.rs | 14 +++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 87d2e9aa78df53676d003de0fd044cbab797bc5a..a37cc3bc789a569e86e770712a1cc8ec58820352 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -442,6 +442,8 @@ impl ActiveCall { .location .as_ref() .and_then(|location| location.upgrade()); + let channel_id = room.update(cx, |room, cx| room.channel_id()); + cx.emit(Event::RoomJoined { channel_id }); room.update(cx, |room, cx| room.set_location(location.as_ref(), cx)) } } else { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 45c6c15fb00a4cc42131d7b3acfa8524201044c1..6dae1e6a4d5e7ad5ba0b8b96a5622fa323f4194c 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -26,6 +26,9 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { + RoomJoined { + channel_id: Option, + }, ParticipantLocationChanged { participant_id: proto::PeerId, }, diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5786ab10d4ca59b998b1b16ea7bb3c53611b4399..bbe0a6b4fe39541c12bf66db246f6a039fe6850d 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,6 +1,6 @@ use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; -use call::ActiveCall; +use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; use client::Client; use collections::HashMap; @@ -140,6 +140,18 @@ impl ChatPanel { cx.notify(); }, )); + this.subscriptions.push(cx.subscribe( + &ActiveCall::global(cx), + move |this: &mut Self, _, event: &room::Event, cx| match event { + room::Event::RoomJoined { channel_id } => { + if let Some(channel_id) = channel_id { + this.select_channel(*channel_id, None, cx) + .detach_and_log_err(cx) + } + } + _ => {} + }, + )); this }) From f0d490c671400c75cfb87a267729610bf6998a3e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 10:34:24 -0700 Subject: [PATCH 39/98] Open chat panel for guests --- crates/call/src/call.rs | 6 ++++-- crates/call/src/room.rs | 1 + crates/collab_ui/src/chat_panel.rs | 9 +++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index a37cc3bc789a569e86e770712a1cc8ec58820352..72bb6a4e6e81ddd6dad6bcd78a8e4125130fa37e 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -442,8 +442,10 @@ impl ActiveCall { .location .as_ref() .and_then(|location| location.upgrade()); - let channel_id = room.update(cx, |room, cx| room.channel_id()); - cx.emit(Event::RoomJoined { channel_id }); + let (channel_id, role) = room.update(cx, |room, _| { + (room.channel_id(), room.local_participant().role) + }); + cx.emit(Event::RoomJoined { channel_id, role }); room.update(cx, |room, cx| room.set_location(location.as_ref(), cx)) } } else { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 6dae1e6a4d5e7ad5ba0b8b96a5622fa323f4194c..23a6ea4a1cd44eba03db92f40dca867eafb7a9f3 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -28,6 +28,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub enum Event { RoomJoined { channel_id: Option, + role: proto::ChannelRole, }, ParticipantLocationChanged { participant_id: proto::PeerId, diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index bbe0a6b4fe39541c12bf66db246f6a039fe6850d..a170f956978f1a8a999f3c5460db4d103f18cb3e 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -16,6 +16,7 @@ use menu::Confirm; use message_editor::MessageEditor; use project::Fs; use rich_text::RichText; +use rpc::proto; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; @@ -143,10 +144,14 @@ impl ChatPanel { this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), move |this: &mut Self, _, event: &room::Event, cx| match event { - room::Event::RoomJoined { channel_id } => { + room::Event::RoomJoined { channel_id, role } => { if let Some(channel_id) = channel_id { this.select_channel(*channel_id, None, cx) - .detach_and_log_err(cx) + .detach_and_log_err(cx); + + if *role == proto::ChannelRole::Guest { + cx.emit(PanelEvent::Activate) + } } } _ => {} From 7dc28aad787ae3f88f2c6c95979358ba170c0a2e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 10:38:05 -0800 Subject: [PATCH 40/98] Forbid paste, undo, redo on read-only editors --- crates/editor/src/editor.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7fe942f14561c8781b17c9ae66ea356190425422..66d30b049cb396651368d77c345c7b50e5c6554d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5443,6 +5443,10 @@ impl Editor { } pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } + self.transact(cx, |this, cx| { if let Some(item) = cx.read_from_clipboard() { let clipboard_text = Cow::Borrowed(item.text()); @@ -5515,6 +5519,10 @@ impl Editor { } pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } + if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { self.change_selections(None, cx, |s| { @@ -5529,6 +5537,10 @@ impl Editor { } pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } + if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned() { From 551fd9ba7ee3d5a4c51da110a140965bb25f8569 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 12:40:09 -0700 Subject: [PATCH 41/98] Boop --- .../gpui/src/platform/mac/metal_renderer.rs | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index a6cdd166d312d5eb97fc749a645a8cb09ea0dd8a..2a1f9ef92def62b85b436f053c37e74c9600c00e 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. [] pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -429,6 +429,13 @@ impl MetalRenderer { let shadow_bytes_len = std::mem::size_of_val(shadows); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + shadow_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + unsafe { ptr::copy_nonoverlapping( shadows.as_ptr() as *const u8, @@ -437,12 +444,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + shadow_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -489,15 +490,15 @@ impl MetalRenderer { let quad_bytes_len = std::mem::size_of_val(quads); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - unsafe { - ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); - } let next_offset = *offset + quad_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" ); + unsafe { + ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -586,23 +587,33 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); + // hypothesis: sprites.as_ptr() does something bogus sometimes? + // let sprite_bytes_len = mem::size_of::() * sprites.len(); + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - unsafe { - ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, - buffer_contents, - sprite_bytes_len, - ); - } + // buffer_contents.len() < spite_bytes_len must be out of range. + // PANIC HERE! let next_offset = *offset + sprite_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" ); + unsafe { + ptr::copy_nonoverlapping( + sprites.as_ptr() as *const u8, //src + buffer_contents, //dest + sprite_bytes_len, // count + ); + } + command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -723,6 +734,13 @@ impl MetalRenderer { let sprite_bytes_len = std::mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -731,12 +749,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -794,6 +806,12 @@ impl MetalRenderer { let sprite_bytes_len = std::mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -802,12 +820,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, From 324d1d119ba4066064bd13505053b308ffc9ae07 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 12:40:37 -0800 Subject: [PATCH 42/98] Add some context to assert --- .../gpui/src/platform/mac/metal_renderer.rs | 70 +++++++------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 2a1f9ef92def62b85b436f053c37e74c9600c00e..8365525ebd905902dbed3a8eea815ca298566a82 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. [] +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -337,10 +337,7 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::>(); - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, vertices.len(), "Path Vertexes"); let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -431,10 +428,7 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, shadows.len(), "Shadows"); unsafe { ptr::copy_nonoverlapping( @@ -492,10 +486,8 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, quads.len(), "Quads"); + unsafe { ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); } @@ -587,30 +579,18 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - // hypothesis: sprites.as_ptr() does something bogus sometimes? - // let sprite_bytes_len = mem::size_of::() * sprites.len(); let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Path Sprites"); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - // buffer_contents.len() < spite_bytes_len must be out of range. - // PANIC HERE! - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - unsafe { ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, //src - buffer_contents, //dest - sprite_bytes_len, // count + sprites.as_ptr() as *const u8, + buffer_contents, + sprite_bytes_len, ); } @@ -672,10 +652,7 @@ impl MetalRenderer { } let next_offset = *offset + quad_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, underlines.len(), "Underlines"); command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -736,10 +713,7 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Monoschrome Sprites"); unsafe { ptr::copy_nonoverlapping( @@ -808,10 +782,8 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Polychrome Sprites"); + unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -886,10 +858,7 @@ impl MetalRenderer { align_offset(offset); let next_offset = *offset + mem::size_of::(); - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, 1, "Surface"); command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, @@ -926,6 +895,15 @@ impl MetalRenderer { *offset = next_offset; } } + + fn assert_instance_buffer_bounds(&self, next_offset: usize, count: usize, item: &'static str) { + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted attempting to copy {} of {}", + count, + item + ); + } } fn build_pipeline_state( From aa5c6a8aa354e424a60f736559355045b091775c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 14:35:50 -0700 Subject: [PATCH 43/98] Update graphics memory assert to be more helpful --- .../gpui/src/platform/mac/metal_renderer.rs | 192 ++++++++++-------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 8365525ebd905902dbed3a8eea815ca298566a82..20e749a2f607fa96ec6fbdfc76574307648a621d 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...) pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -204,7 +204,11 @@ impl MetalRenderer { let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; - let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer); + let Some(path_tiles) = + self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer) + else { + panic!("failed to rasterize {} paths", scene.paths().len()); + }; let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -228,67 +232,67 @@ impl MetalRenderer { zfar: 1.0, }); for batch in scene.batches() { - match batch { - PrimitiveBatch::Shadows(shadows) => { - self.draw_shadows( - shadows, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + let ok = match batch { + PrimitiveBatch::Shadows(shadows) => self.draw_shadows( + shadows, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::Quads(quads) => { - self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); - } - PrimitiveBatch::Paths(paths) => { - self.draw_paths( - paths, - &path_tiles, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - PrimitiveBatch::Underlines(underlines) => { - self.draw_underlines( - underlines, - &mut instance_offset, - viewport_size, - command_encoder, - ); + self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder) } + PrimitiveBatch::Paths(paths) => self.draw_paths( + paths, + &path_tiles, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Underlines(underlines) => self.draw_underlines( + underlines, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::MonochromeSprites { texture_id, sprites, - } => { - self.draw_monochrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + } => self.draw_monochrome_sprites( + texture_id, + sprites, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::PolychromeSprites { texture_id, sprites, - } => { - self.draw_polychrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - PrimitiveBatch::Surfaces(surfaces) => { - self.draw_surfaces( - surfaces, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + } => self.draw_polychrome_sprites( + texture_id, + sprites, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( + surfaces, + &mut instance_offset, + viewport_size, + command_encoder, + ), + }; + + if !ok { + panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", + scene.paths.len(), + scene.shadows.len(), + scene.quads.len(), + scene.underlines.len(), + scene.monochrome_sprites.len(), + scene.polychrome_sprites.len(), + scene.surfaces.len(), + ) } } @@ -311,7 +315,7 @@ impl MetalRenderer { paths: &[Path], offset: &mut usize, command_buffer: &metal::CommandBufferRef, - ) -> HashMap { + ) -> Option> { let mut tiles = HashMap::default(); let mut vertices_by_texture_id = HashMap::default(); for path in paths { @@ -337,7 +341,9 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::>(); - self.assert_instance_buffer_bounds(next_offset, vertices.len(), "Path Vertexes"); + if next_offset > INSTANCE_BUFFER_SIZE { + return None; + } let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -386,7 +392,7 @@ impl MetalRenderer { *offset = next_offset; } - tiles + Some(tiles) } fn draw_shadows( @@ -395,9 +401,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if shadows.is_empty() { - return; + return true; } align_offset(offset); @@ -428,7 +434,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; - self.assert_instance_buffer_bounds(next_offset, shadows.len(), "Shadows"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -445,6 +453,7 @@ impl MetalRenderer { shadows.len() as u64, ); *offset = next_offset; + true } fn draw_quads( @@ -453,9 +462,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if quads.is_empty() { - return; + return true; } align_offset(offset); @@ -486,7 +495,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; - self.assert_instance_buffer_bounds(next_offset, quads.len(), "Quads"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); @@ -499,6 +510,7 @@ impl MetalRenderer { quads.len() as u64, ); *offset = next_offset; + true } fn draw_paths( @@ -508,9 +520,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if paths.is_empty() { - return; + return true; } command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state); @@ -581,7 +593,9 @@ impl MetalRenderer { let sprite_bytes_len = mem::size_of::() * sprites.len(); let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Path Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; @@ -604,6 +618,7 @@ impl MetalRenderer { sprites.clear(); } } + true } fn draw_underlines( @@ -612,9 +627,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if underlines.is_empty() { - return; + return true; } align_offset(offset); @@ -652,7 +667,9 @@ impl MetalRenderer { } let next_offset = *offset + quad_bytes_len; - self.assert_instance_buffer_bounds(next_offset, underlines.len(), "Underlines"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -661,6 +678,7 @@ impl MetalRenderer { underlines.len() as u64, ); *offset = next_offset; + true } fn draw_monochrome_sprites( @@ -670,9 +688,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if sprites.is_empty() { - return; + return true; } align_offset(offset); @@ -713,7 +731,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Monoschrome Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -730,6 +750,7 @@ impl MetalRenderer { sprites.len() as u64, ); *offset = next_offset; + true } fn draw_polychrome_sprites( @@ -739,9 +760,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if sprites.is_empty() { - return; + return true; } align_offset(offset); @@ -782,7 +803,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Polychrome Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -799,6 +822,7 @@ impl MetalRenderer { sprites.len() as u64, ); *offset = next_offset; + true } fn draw_surfaces( @@ -807,7 +831,7 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state); command_encoder.set_vertex_buffer( SurfaceInputIndex::Vertices as u64, @@ -858,7 +882,9 @@ impl MetalRenderer { align_offset(offset); let next_offset = *offset + mem::size_of::(); - self.assert_instance_buffer_bounds(next_offset, 1, "Surface"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, @@ -894,15 +920,7 @@ impl MetalRenderer { command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6); *offset = next_offset; } - } - - fn assert_instance_buffer_bounds(&self, next_offset: usize, count: usize, item: &'static str) { - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted attempting to copy {} of {}", - count, - item - ); + true } } From 50f3bbbc8b38bec8e687db8bcebd1667454450c0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 15:08:14 -0700 Subject: [PATCH 44/98] Open chat when joining a channel with guests (and close it when leaving a channel again) --- crates/call/src/call.rs | 6 ++---- crates/call/src/room.rs | 17 ++++++++++++--- crates/collab_ui/src/chat_panel.rs | 21 ++++++++++++++++--- .../project_shared_notification.rs | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 72bb6a4e6e81ddd6dad6bcd78a8e4125130fa37e..6d57a42ff7e689cb51140f89d0ef7f8863e394d2 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -442,10 +442,8 @@ impl ActiveCall { .location .as_ref() .and_then(|location| location.upgrade()); - let (channel_id, role) = room.update(cx, |room, _| { - (room.channel_id(), room.local_participant().role) - }); - cx.emit(Event::RoomJoined { channel_id, role }); + let channel_id = room.read(cx).channel_id(); + cx.emit(Event::RoomJoined { channel_id }); room.update(cx, |room, cx| room.set_location(location.as_ref(), cx)) } } else { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 23a6ea4a1cd44eba03db92f40dca867eafb7a9f3..e17a3667bae639186063685923a53a71867ffa27 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -28,7 +28,6 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub enum Event { RoomJoined { channel_id: Option, - role: proto::ChannelRole, }, ParticipantLocationChanged { participant_id: proto::PeerId, @@ -53,7 +52,9 @@ pub enum Event { RemoteProjectInvitationDiscarded { project_id: u64, }, - Left, + Left { + channel_id: Option, + }, } pub struct Room { @@ -361,7 +362,9 @@ impl Room { pub(crate) fn leave(&mut self, cx: &mut ModelContext) -> Task> { cx.notify(); - cx.emit(Event::Left); + cx.emit(Event::Left { + channel_id: self.channel_id(), + }); self.leave_internal(cx) } @@ -602,6 +605,14 @@ impl Room { .map(|participant| participant.role) } + pub fn contains_guests(&self) -> bool { + self.local_participant.role == proto::ChannelRole::Guest + || self + .remote_participants + .values() + .any(|p| p.role == proto::ChannelRole::Guest) + } + pub fn local_participant_is_admin(&self) -> bool { self.local_participant.role == proto::ChannelRole::Admin } diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index a170f956978f1a8a999f3c5460db4d103f18cb3e..f8ee12ef814d3ebb98e69bad087252fda7a621d9 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -143,17 +143,26 @@ impl ChatPanel { )); this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), - move |this: &mut Self, _, event: &room::Event, cx| match event { - room::Event::RoomJoined { channel_id, role } => { + move |this: &mut Self, call, event: &room::Event, cx| match event { + room::Event::RoomJoined { channel_id } => { if let Some(channel_id) = channel_id { this.select_channel(*channel_id, None, cx) .detach_and_log_err(cx); - if *role == proto::ChannelRole::Guest { + if call + .read(cx) + .room() + .is_some_and(|room| room.read(cx).contains_guests()) + { cx.emit(PanelEvent::Activate) } } } + room::Event::Left { channel_id } => { + if channel_id == &this.channel_id(cx) { + cx.emit(PanelEvent::Close) + } + } _ => {} }, )); @@ -162,6 +171,12 @@ impl ChatPanel { }) } + pub fn channel_id(&self, cx: &AppContext) -> Option { + self.active_chat + .as_ref() + .map(|(chat, _)| chat.read(cx).channel_id) + } + pub fn is_scrolled_to_bottom(&self) -> bool { self.is_scrolled_to_bottom } diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 88fe540c397b65c8ddc3ad0230c47210b8bc0e7e..b8ceefcd765f4e1797bcf1584ac93594a1fffdaa 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -58,7 +58,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { } } - room::Event::Left => { + room::Event::Left { .. } => { for (_, windows) in notification_windows.drain() { for window in windows { window From cdc227b32fd23f979342a34cd519816ce4b44728 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 12 Jan 2024 17:44:15 -0500 Subject: [PATCH 45/98] Still paint group hover handler for invisible divs Fixes bug where tab close icon show on hover is inconsistent --- crates/gpui/src/elements/div.rs | 37 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 627a2ac339d631c666926d4fc8e1354396cb36f7..0c1b0b74bb33fd6d06068463dee0c80b87573e98 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -978,12 +978,31 @@ impl Interactivity { f: impl FnOnce(&Style, Point, &mut WindowContext), ) { let style = self.compute_style(Some(bounds), element_state, cx); + let z_index = style.z_index.unwrap_or(0); + + let paint_hover_group_handler = |cx: &mut WindowContext| { + let hover_group_bounds = self + .group_hover_style + .as_ref() + .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); + + if let Some(group_bounds) = hover_group_bounds { + let hovered = group_bounds.contains(&cx.mouse_position()); + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture + && group_bounds.contains(&event.position) != hovered + { + cx.notify(); + } + }); + } + }; if style.visibility == Visibility::Hidden { + cx.with_z_index(z_index, |cx| paint_hover_group_handler(cx)); return; } - let z_index = style.z_index.unwrap_or(0); cx.with_z_index(z_index, |cx| { style.paint(bounds, cx, |cx| { cx.with_text_style(style.text_style().cloned(), |cx| { @@ -1166,21 +1185,7 @@ impl Interactivity { }) } - let hover_group_bounds = self - .group_hover_style - .as_ref() - .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); - - if let Some(group_bounds) = hover_group_bounds { - let hovered = group_bounds.contains(&cx.mouse_position()); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture - && group_bounds.contains(&event.position) != hovered - { - cx.notify(); - } - }); - } + paint_hover_group_handler(cx); if self.hover_style.is_some() || self.base_style.mouse_cursor.is_some() From aa50f699400ca66869fa3007cdebd4587c2ba1aa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 14:46:17 -0800 Subject: [PATCH 46/98] Add LiveKit APIs for starting and stopping audio tracks --- .../Sources/LiveKitBridge/LiveKitBridge.swift | 12 ++++++++++++ crates/live_kit_client/src/prod.rs | 14 +++++++------- crates/live_kit_client/src/test.rs | 10 ++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index db5da8e0e9ec5608e81fe34f3aa901d2e819f21d..7468c08791f8b0782cf711cb9b546bf2940ae6fe 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -286,6 +286,18 @@ public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString { return track.sid! as CFString } +@_cdecl("LKRemoteAudioTrackStart") +public func LKRemoteAudioTrackStart(track: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + track.start() +} + +@_cdecl("LKRemoteAudioTrackStop") +public func LKRemoteAudioTrackStop(track: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + track.stop() +} + @_cdecl("LKDisplaySources") public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) { MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index f1660cc3d1a7ce9f7951cfd5f1a354158a41d334..a4bd9d4f07b6c88f3b7fe7dca343a972659d975f 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -18,8 +18,6 @@ use std::{ sync::{Arc, Weak}, }; -// SAFETY: Most live kit types are threadsafe: -// https://github.com/livekit/client-sdk-swift#thread-safety macro_rules! pointer_type { ($pointer_name:ident) => { #[repr(transparent)] @@ -134,8 +132,10 @@ extern "C" { ) -> *const c_void; fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef; - fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef; + fn LKRemoteAudioTrackStart(track: swift::RemoteAudioTrack); + fn LKRemoteAudioTrackStop(track: swift::RemoteAudioTrack); + fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); fn LKDisplaySources( callback_data: *mut c_void, @@ -853,12 +853,12 @@ impl RemoteAudioTrack { &self.publisher_id } - pub fn enable(&self) -> impl Future> { - async { Ok(()) } + pub fn start(&self) { + unsafe { LKRemoteAudioTrackStart(self.native_track) } } - pub fn disable(&self) -> impl Future> { - async { Ok(()) } + pub fn stop(&self) { + unsafe { LKRemoteAudioTrackStop(self.native_track) } } } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 0716042ff196e1e0ea8f3543f03b645648ed473c..1b7fd20bc257ca5559a37b13ed70b05fe8eb90f7 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -262,6 +262,7 @@ impl TestServer { let track = Arc::new(RemoteAudioTrack { sid: sid.clone(), publisher_id: identity.clone(), + running: AtomicBool::new(true), }); let publication = Arc::new(RemoteTrackPublication); @@ -644,6 +645,7 @@ impl RemoteVideoTrack { pub struct RemoteAudioTrack { sid: Sid, publisher_id: Sid, + running: AtomicBool, } impl RemoteAudioTrack { @@ -655,12 +657,12 @@ impl RemoteAudioTrack { &self.publisher_id } - pub fn enable(&self) -> impl Future> { - async { Ok(()) } + pub fn start(&self) { + self.running.store(true, SeqCst); } - pub fn disable(&self) -> impl Future> { - async { Ok(()) } + pub fn stop(&self) { + self.running.store(false, SeqCst); } } From eafe0944e0b4b8c0a23f6fef9f90e6b56fd06710 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 16:01:51 -0700 Subject: [PATCH 47/98] Some tweaks for chat panels --- crates/collab_ui/src/chat_panel.rs | 189 +++++++++++------------------ 1 file changed, 69 insertions(+), 120 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index f8ee12ef814d3ebb98e69bad087252fda7a621d9..ecd7d1a739e957bfa7c7bf7b19de774e97675e64 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; +use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings, CollabPanel}; use anyhow::Result; use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -7,9 +7,9 @@ use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ - actions, div, list, prelude::*, px, AnyElement, AppContext, AsyncWindowContext, ClickEvent, - ElementId, EventEmitter, FocusableView, ListOffset, ListScrollEvent, ListState, Model, Render, - Subscription, Task, View, ViewContext, VisualContext, WeakView, + actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, + ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, ListOffset, ListScrollEvent, + ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; @@ -21,7 +21,9 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; -use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip}; +use ui::{ + prelude::*, Avatar, Button, IconButton, IconName, Key, KeyBinding, Label, TabBar, Tooltip, +}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -55,7 +57,6 @@ pub struct ChatPanel { active: bool, pending_serialization: Task>, subscriptions: Vec, - workspace: WeakView, is_scrolled_to_bottom: bool, markdown_data: HashMap, } @@ -90,8 +91,6 @@ impl ChatPanel { ) }); - let workspace_handle = workspace.weak_handle(); - cx.new_view(|cx: &mut ViewContext| { let view = cx.view().downgrade(); let message_list = @@ -123,7 +122,6 @@ impl ChatPanel { message_editor: input_editor, local_timezone: cx.local_timezone(), subscriptions: Vec::new(), - workspace: workspace_handle, is_scrolled_to_bottom: true, active: false, width: None, @@ -291,50 +289,6 @@ impl ChatPanel { } } - fn render_channel(&self, cx: &mut ViewContext) -> AnyElement { - v_stack() - .full() - .on_action(cx.listener(Self::send)) - .child( - h_stack().z_index(1).child( - TabBar::new("chat_header") - .child( - h_stack() - .w_full() - .h(rems(ui::Tab::HEIGHT_IN_REMS)) - .px_2() - .child(Label::new( - self.active_chat - .as_ref() - .and_then(|c| { - Some(format!("#{}", c.0.read(cx).channel(cx)?.name)) - }) - .unwrap_or_default(), - )), - ) - .end_child( - IconButton::new("notes", IconName::File) - .on_click(cx.listener(Self::open_notes)) - .tooltip(|cx| Tooltip::text("Open notes", cx)), - ) - .end_child( - IconButton::new("call", IconName::AudioOn) - .on_click(cx.listener(Self::join_call)) - .tooltip(|cx| Tooltip::text("Join call", cx)), - ), - ), - ) - .child(div().flex_grow().px_2().py_1().map(|this| { - if self.active_chat.is_some() { - this.child(list(self.message_list.clone()).full()) - } else { - this - } - })) - .child(h_stack().p_2().child(self.message_editor.clone())) - .into_any() - } - fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> impl IntoElement { let active_chat = &self.active_chat.as_ref().unwrap().0; let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) = @@ -453,44 +407,6 @@ impl ChatPanel { rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None) } - fn render_sign_in_prompt(&self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() - .gap_2() - .p_4() - .child( - Button::new("sign-in", "Sign in") - .style(ButtonStyle::Filled) - .icon_color(Color::Muted) - .icon(IconName::Github) - .icon_position(IconPosition::Start) - .full_width() - .on_click(cx.listener(move |this, _, cx| { - let client = this.client.clone(); - cx.spawn(|this, mut cx| async move { - if client - .authenticate_and_connect(true, &cx) - .log_err() - .await - .is_some() - { - this.update(&mut cx, |_, cx| { - cx.focus_self(); - }) - .ok(); - } - }) - .detach(); - })), - ) - .child( - div().flex().w_full().items_center().child( - Label::new("Sign in to chat.") - .color(Color::Muted) - .size(LabelSize::Small), - ), - ) - } - fn send(&mut self, _: &Confirm, cx: &mut ViewContext) { if let Some((chat, _)) = self.active_chat.as_ref() { let message = self @@ -567,24 +483,6 @@ impl ChatPanel { Ok(()) }) } - - fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext) { - if let Some((chat, _)) = &self.active_chat { - let channel_id = chat.read(cx).channel_id; - if let Some(workspace) = self.workspace.upgrade() { - ChannelView::open(channel_id, workspace, cx).detach(); - } - } - } - - fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext) { - if let Some((chat, _)) = &self.active_chat { - let channel_id = chat.read(cx).channel_id; - ActiveCall::global(cx) - .update(cx, |call, cx| call.join_channel(channel_id, cx)) - .detach_and_log_err(cx); - } - } } impl EventEmitter for ChatPanel {} @@ -592,19 +490,70 @@ impl EventEmitter for ChatPanel {} impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_stack() - .size_full() - .map(|this| match (self.client.user_id(), self.active_chat()) { - (Some(_), Some(_)) => this.child(self.render_channel(cx)), - (Some(_), None) => this.child( - div().p_4().child( - Label::new("Select a channel to chat in.") - .size(LabelSize::Small) - .color(Color::Muted), + .full() + .on_action(cx.listener(Self::send)) + .child( + h_stack().z_index(1).child( + TabBar::new("chat_header").child( + h_stack() + .w_full() + .h(rems(ui::Tab::HEIGHT_IN_REMS)) + .px_2() + .child(Label::new( + self.active_chat + .as_ref() + .and_then(|c| { + Some(format!("#{}", c.0.read(cx).channel(cx)?.name)) + }) + .unwrap_or("Chat".to_string()), + )), ), ), - (None, _) => this.child(self.render_sign_in_prompt(cx)), - }) - .min_w(px(150.)) + ) + .child(div().flex_grow().px_2().py_1().map(|this| { + if self.active_chat.is_some() { + this.child(list(self.message_list.clone()).full()) + } else { + this.child( + div() + .p_4() + .child( + Label::new("Select a channel to chat in.") + .size(LabelSize::Small) + .color(Color::Muted), + ) + .child( + div().pt_1().w_full().items_center().child( + Button::new("toggle-collab", "Open") + .full_width() + .key_binding(KeyBinding::for_action( + &collab_panel::ToggleFocus, + cx, + )) + .on_click(|_, cx| { + cx.dispatch_action( + collab_panel::ToggleFocus.boxed_clone(), + ) + }), + ), + ), + ) + } + })) + .child(h_stack().p_2().map(|el| { + if self.active_chat.is_some() { + el.child(self.message_editor.clone()) + } else { + el.child( + div() + .rounded_md() + .h_7() + .w_full() + .bg(cx.theme().colors().editor_background), + ) + } + })) + .into_any() } } From c2cf28804a761e21ef1007fbf150aecd0b368c52 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 15:12:29 -0800 Subject: [PATCH 48/98] Don't run newly published audio tracks when deafened Also, simplify the management of the muted and deafened state in Room. --- crates/call/src/room.rs | 263 +++++++++---------- crates/collab_ui/src/collab_titlebar_item.rs | 4 +- crates/collab_ui/src/collab_ui.rs | 13 +- 3 files changed, 133 insertions(+), 147 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 45c6c15fb00a4cc42131d7b3acfa8524201044c1..0979ad8bb93092ad5e5d75506bff4872f723da08 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -150,17 +150,14 @@ impl Room { let connect = room.connect(&connection_info.server_url, &connection_info.token); cx.spawn(|this, mut cx| async move { connect.await?; - - let is_read_only = this - .update(&mut cx, |room, _| room.read_only()) - .unwrap_or(true); - - if !cx.update(|cx| Self::mute_on_join(cx))? && !is_read_only { - this.update(&mut cx, |this, cx| this.share_microphone(cx))? - .await?; - } - - anyhow::Ok(()) + this.update(&mut cx, |this, cx| { + if this.read_only() || this.is_muted() { + Task::ready(Ok(())) + } else { + this.share_microphone(cx) + } + })? + .await }) .detach_and_log_err(cx); @@ -169,7 +166,7 @@ impl Room { screen_track: LocalTrack::None, microphone_track: LocalTrack::None, next_publish_id: 0, - muted_by_user: false, + muted_by_user: Self::mute_on_join(cx), deafened: false, speaking: false, _maintain_room, @@ -1032,6 +1029,15 @@ impl Room { } RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { + if let Some(live_kit) = &self.live_kit { + if live_kit.deafened { + track.stop(); + cx.foreground_executor() + .spawn(publication.set_enabled(false)) + .detach(); + } + } + let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self @@ -1286,15 +1292,12 @@ impl Room { }) } - pub fn is_muted(&self, cx: &AppContext) -> bool { - self.live_kit - .as_ref() - .and_then(|live_kit| match &live_kit.microphone_track { - LocalTrack::None => Some(Self::mute_on_join(cx)), - LocalTrack::Pending { muted, .. } => Some(*muted), - LocalTrack::Published { muted, .. } => Some(*muted), - }) - .unwrap_or(false) + pub fn is_muted(&self) -> bool { + self.live_kit.as_ref().map_or(false, |live_kit| { + matches!(live_kit.microphone_track, LocalTrack::None) + || live_kit.muted_by_user + || live_kit.deafened + }) } pub fn read_only(&self) -> bool { @@ -1316,16 +1319,11 @@ impl Room { pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); - } else if self.is_sharing_mic() { - return Task::ready(Err(anyhow!("microphone was already shared"))); } let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); - live_kit.microphone_track = LocalTrack::Pending { - publish_id, - muted: false, - }; + live_kit.microphone_track = LocalTrack::Pending { publish_id }; cx.notify(); publish_id } else { @@ -1354,14 +1352,13 @@ impl Room { .as_mut() .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - let (canceled, muted) = if let LocalTrack::Pending { + let canceled = if let LocalTrack::Pending { publish_id: cur_publish_id, - muted, } = &live_kit.microphone_track { - (*cur_publish_id != publish_id, *muted) + *cur_publish_id != publish_id } else { - (true, false) + true }; match publication { @@ -1369,14 +1366,13 @@ impl Room { if canceled { live_kit.room.unpublish_track(publication); } else { - if muted { + if live_kit.muted_by_user || live_kit.deafened { cx.background_executor() - .spawn(publication.set_mute(muted)) + .spawn(publication.set_mute(true)) .detach(); } live_kit.microphone_track = LocalTrack::Published { track_publication: publication, - muted, }; cx.notify(); } @@ -1405,10 +1401,7 @@ impl Room { let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); - live_kit.screen_track = LocalTrack::Pending { - publish_id, - muted: false, - }; + live_kit.screen_track = LocalTrack::Pending { publish_id }; cx.notify(); (live_kit.room.display_sources(), publish_id) } else { @@ -1442,14 +1435,13 @@ impl Room { .as_mut() .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - let (canceled, muted) = if let LocalTrack::Pending { + let canceled = if let LocalTrack::Pending { publish_id: cur_publish_id, - muted, } = &live_kit.screen_track { - (*cur_publish_id != publish_id, *muted) + *cur_publish_id != publish_id } else { - (true, false) + true }; match publication { @@ -1457,14 +1449,8 @@ impl Room { if canceled { live_kit.room.unpublish_track(publication); } else { - if muted { - cx.background_executor() - .spawn(publication.set_mute(muted)) - .detach(); - } live_kit.screen_track = LocalTrack::Published { track_publication: publication, - muted, }; cx.notify(); } @@ -1487,61 +1473,48 @@ impl Room { }) } - pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { - let should_mute = !self.is_muted(cx); + pub fn toggle_mute(&mut self, cx: &mut ModelContext) { if let Some(live_kit) = self.live_kit.as_mut() { - if matches!(live_kit.microphone_track, LocalTrack::None) { - return Ok(self.share_microphone(cx)); + // When unmuting, undeafen if the user was deafened before. + let was_deafened = live_kit.deafened; + if live_kit.muted_by_user || live_kit.deafened { + live_kit.muted_by_user = false; + live_kit.deafened = false; + } else { + live_kit.muted_by_user = true; } + let muted = live_kit.muted_by_user; + let should_undeafen = was_deafened && !live_kit.deafened; - let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; - live_kit.muted_by_user = should_mute; + if let Some(task) = self.set_mute(muted, cx) { + task.detach_and_log_err(cx); + } - if old_muted == true && live_kit.deafened == true { - if let Some(task) = self.toggle_deafen(cx).ok() { - task.detach(); + if should_undeafen { + if let Some(task) = self.set_deafened(false, cx) { + task.detach_and_log_err(cx); } } - - Ok(ret_task) - } else { - Err(anyhow!("LiveKit not started")) } } - pub fn toggle_deafen(&mut self, cx: &mut ModelContext) -> Result>> { + pub fn toggle_deafen(&mut self, cx: &mut ModelContext) { if let Some(live_kit) = self.live_kit.as_mut() { - (*live_kit).deafened = !live_kit.deafened; - - let mut tasks = Vec::with_capacity(self.remote_participants.len()); - // Context notification is sent within set_mute itself. - let mut mute_task = None; - // When deafening, mute user's mic as well. - // When undeafening, unmute user's mic unless it was manually muted prior to deafening. - if live_kit.deafened || !live_kit.muted_by_user { - mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); - }; - for participant in self.remote_participants.values() { - for track in live_kit - .room - .remote_audio_track_publications(&participant.user.id.to_string()) - { - let deafened = live_kit.deafened; - tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened))); - } + // When deafening, mute the microphone if it was not already muted. + // When un-deafening, unmute the microphone, unless it was explicitly muted. + let deafened = !live_kit.deafened; + live_kit.deafened = deafened; + let should_change_mute = !live_kit.muted_by_user; + + if let Some(task) = self.set_deafened(deafened, cx) { + task.detach_and_log_err(cx); } - Ok(cx.foreground_executor().spawn(async move { - if let Some(mute_task) = mute_task { - mute_task.await?; - } - for task in tasks { - task.await?; + if should_change_mute { + if let Some(task) = self.set_mute(deafened, cx) { + task.detach_and_log_err(cx); } - Ok(()) - })) - } else { - Err(anyhow!("LiveKit not started")) + } } } @@ -1572,6 +1545,70 @@ impl Room { } } + fn set_deafened( + &mut self, + deafened: bool, + cx: &mut ModelContext, + ) -> Option>> { + let live_kit = self.live_kit.as_mut()?; + cx.notify(); + + let mut track_updates = Vec::new(); + for participant in self.remote_participants.values() { + for publication in live_kit + .room + .remote_audio_track_publications(&participant.user.id.to_string()) + { + track_updates.push(publication.set_enabled(!deafened)); + } + + for track in participant.audio_tracks.values() { + if deafened { + track.stop(); + } else { + track.start(); + } + } + } + + Some(cx.foreground_executor().spawn(async move { + for result in futures::future::join_all(track_updates).await { + result?; + } + Ok(()) + })) + } + + fn set_mute( + &mut self, + should_mute: bool, + cx: &mut ModelContext, + ) -> Option>> { + let live_kit = self.live_kit.as_mut()?; + cx.notify(); + + if should_mute { + Audio::play_sound(Sound::Mute, cx); + } else { + Audio::play_sound(Sound::Unmute, cx); + } + + match &mut live_kit.microphone_track { + LocalTrack::None => { + if should_mute { + None + } else { + Some(self.share_microphone(cx)) + } + } + LocalTrack::Pending { .. } => None, + LocalTrack::Published { track_publication } => Some( + cx.foreground_executor() + .spawn(track_publication.set_mute(should_mute)), + ), + } + } + #[cfg(any(test, feature = "test-support"))] pub fn set_display_sources(&self, sources: Vec) { self.live_kit @@ -1596,50 +1633,6 @@ struct LiveKitRoom { } impl LiveKitRoom { - fn set_mute( - self: &mut LiveKitRoom, - should_mute: bool, - cx: &mut ModelContext, - ) -> Result<(Task>, bool)> { - if !should_mute { - // clear user muting state. - self.muted_by_user = false; - } - - let (result, old_muted) = match &mut self.microphone_track { - LocalTrack::None => Err(anyhow!("microphone was not shared")), - LocalTrack::Pending { muted, .. } => { - let old_muted = *muted; - *muted = should_mute; - cx.notify(); - Ok((Task::Ready(Some(Ok(()))), old_muted)) - } - LocalTrack::Published { - track_publication, - muted, - } => { - let old_muted = *muted; - *muted = should_mute; - cx.notify(); - Ok(( - cx.background_executor() - .spawn(track_publication.set_mute(*muted)), - old_muted, - )) - } - }?; - - if old_muted != should_mute { - if should_mute { - Audio::play_sound(Sound::Mute, cx); - } else { - Audio::play_sound(Sound::Unmute, cx); - } - } - - Ok((result, old_muted)) - } - fn stop_publishing(&mut self, cx: &mut ModelContext) { if let LocalTrack::Published { track_publication, .. @@ -1663,11 +1656,9 @@ enum LocalTrack { None, Pending { publish_id: usize, - muted: bool, }, Published { track_publication: LocalTrackPublication, - muted: bool, }, } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 03dfd450704153b31c52ec3d0baad0d215389190..1485bb996aa9825f32c84032272ca720a2ec91dd 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -102,7 +102,7 @@ impl Render for CollabTitlebarItem { peer_id, true, room.is_speaking(), - room.is_muted(cx), + room.is_muted(), &room, project_id, ¤t_user, @@ -168,7 +168,7 @@ impl Render for CollabTitlebarItem { let project = self.project.read(cx); let is_local = project.is_local(); let is_shared = is_local && project.is_shared(); - let is_muted = room.is_muted(cx); + let is_muted = room.is_muted(); let is_deafened = room.is_deafened().unwrap_or(false); let is_screen_sharing = room.is_screen_sharing(); let read_only = room.read_only(); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index c8230620b4c7756dbfee2b26011c32634f3b005a..a4faffdff2391381bd96cd1fa319b2bbccfa1a96 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -9,7 +9,7 @@ mod panel_settings; use std::{rc::Rc, sync::Arc}; -use call::{report_call_event_for_room, ActiveCall, Room}; +use call::{report_call_event_for_room, ActiveCall}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; @@ -21,7 +21,6 @@ pub use panel_settings::{ ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, }; use settings::Settings; -use util::ResultExt; use workspace::AppState; actions!( @@ -79,7 +78,7 @@ pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { if let Some(room) = call.room().cloned() { let client = call.client(); room.update(cx, |room, cx| { - let operation = if room.is_muted(cx) { + let operation = if room.is_muted() { "enable microphone" } else { "disable microphone" @@ -87,17 +86,13 @@ pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { report_call_event_for_room(operation, room.id(), room.channel_id(), &client); room.toggle_mute(cx) - }) - .map(|task| task.detach_and_log_err(cx)) - .log_err(); + }); } } pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - room.update(cx, Room::toggle_deafen) - .map(|task| task.detach_and_log_err(cx)) - .log_err(); + room.update(cx, |room, cx| room.toggle_deafen(cx)); } } From 51218811cff497b99668b7bef95da0ac9aa2f221 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 17:29:33 -0800 Subject: [PATCH 49/98] Don't initialize audio crate in integration tests Otherwise, a lot of time is spent in Audio::play_sound --- crates/collab/src/tests/test_server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 4fcf6aa67652fabe9187ba5f78b8899379dc471b..cda0621cb32385a399fdfdaef51821dd531281b2 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -248,7 +248,6 @@ impl TestServer { language::init(cx); editor::init(cx); workspace::init(app_state.clone(), cx); - audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); channel::init(&client, user_store.clone(), cx); notifications::init(client.clone(), user_store, cx); From 3a836b80263b6c05957ac1c625323701e22a0913 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 10:28:22 -0800 Subject: [PATCH 50/98] Remove some comments --- crates/collab_ui/src/collab_ui.rs | 32 ----------------- crates/editor/src/editor.rs | 35 ++++++++----------- crates/gpui/src/color.rs | 10 ------ crates/gpui/src/taffy.rs | 14 -------- crates/gpui/src/view.rs | 10 ------ crates/gpui_macros/src/register_action.rs | 13 ------- .../src/derive_refineable.rs | 11 ------ 7 files changed, 15 insertions(+), 110 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index c8230620b4c7756dbfee2b26011c32634f3b005a..779fd121f8afbfa59eac556c17e7abba605e8eee 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -41,10 +41,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { chat_panel::init(cx); notification_panel::init(cx); notifications::init(&app_state, cx); - - // cx.add_global_action(toggle_screen_sharing); - // cx.add_global_action(toggle_mute); - // cx.add_global_action(toggle_deafen); } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { @@ -131,34 +127,6 @@ fn notification_window_options( } } -// fn render_avatar( -// avatar: Option>, -// avatar_style: &AvatarStyle, -// container: ContainerStyle, -// ) -> AnyElement { -// avatar -// .map(|avatar| { -// Image::from_data(avatar) -// .with_style(avatar_style.image) -// .aligned() -// .contained() -// .with_corner_radius(avatar_style.outer_corner_radius) -// .constrained() -// .with_width(avatar_style.outer_width) -// .with_height(avatar_style.outer_width) -// .into_any() -// }) -// .unwrap_or_else(|| { -// Empty::new() -// .constrained() -// .with_width(avatar_style.outer_width) -// .into_any() -// }) -// .contained() -// .with_style(container) -// .into_any() -// } - fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { cx.is_staff() || cx.has_flag::() } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 66d30b049cb396651368d77c345c7b50e5c6554d..0bfd6afa3b265dfb3845e944b6d7a916ca066261 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1955,17 +1955,21 @@ impl Editor { } } - // pub fn language_at<'a, T: ToOffset>( - // &self, - // point: T, - // cx: &'a AppContext, - // ) -> Option> { - // self.buffer.read(cx).language_at(point, cx) - // } - - // pub fn file_at<'a, T: ToOffset>(&self, point: T, cx: &'a AppContext) -> Option> { - // self.buffer.read(cx).read(cx).file_at(point).cloned() - // } + pub fn language_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> Option> { + self.buffer.read(cx).language_at(point, cx) + } + + pub fn file_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> Option> { + self.buffer.read(cx).read(cx).file_at(point).cloned() + } pub fn active_excerpt( &self, @@ -1976,15 +1980,6 @@ impl Editor { .excerpt_containing(self.selections.newest_anchor().head(), cx) } - // pub fn style(&self, cx: &AppContext) -> EditorStyle { - // build_style( - // settings::get::(cx), - // self.get_field_editor_theme.as_deref(), - // self.override_text_style.as_deref(), - // cx, - // ) - // } - pub fn mode(&self) -> EditorMode { self.mode } diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index bc764e564c3340957f947ef669243be0a7d27e4d..23fcc25f6aeed436309a3670393d4cbca10536e6 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -355,16 +355,6 @@ impl Hsla { } } -// impl From for Rgba { -// fn from(value: Hsla) -> Self { -// let h = value.h; -// let s = value.s; -// let l = value.l; - -// let c = (1 - |2L - 1|) X s -// } -// } - impl From for Hsla { fn from(color: Rgba) -> Self { let r = color.r; diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 0ebd394217ae06c6a2256281f389ab506bfca934..cf8cb9ec327d66176ced0d6aaa51bb71a91c20b8 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -271,20 +271,6 @@ impl ToTaffy for Style { } } -// impl ToTaffy for Bounds { -// type Output = taffy::prelude::Bounds; - -// fn to_taffy( -// &self, -// rem_size: Pixels, -// ) -> taffy::prelude::Bounds { -// taffy::prelude::Bounds { -// origin: self.origin.to_taffy(rem_size), -// size: self.size.to_taffy(rem_size), -// } -// } -// } - impl ToTaffy for Length { fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto { match self { diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 4472da02e71fda1bb17d4353056b67ad58639813..247a5649967bc8cc1ca0c0a7ff369e78912bdd03 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -60,16 +60,6 @@ impl View { self.model.read(cx) } - // pub fn render_with(&self, component: E) -> RenderViewWith - // where - // E: 'static + Element, - // { - // RenderViewWith { - // view: self.clone(), - // element: Some(component), - // } - // } - pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle where V: FocusableView, diff --git a/crates/gpui_macros/src/register_action.rs b/crates/gpui_macros/src/register_action.rs index c18e4f4b89a68859b1c413357649cf6ad025e8d5..2772ec963485852e570ee27a8e7e3381dcd465d2 100644 --- a/crates/gpui_macros/src/register_action.rs +++ b/crates/gpui_macros/src/register_action.rs @@ -1,16 +1,3 @@ -// Input: -// -// struct FooBar {} - -// Output: -// -// struct FooBar {} -// -// #[allow(non_snake_case)] -// #[gpui2::ctor] -// fn register_foobar_builder() { -// gpui2::register_action_builder::() -// } use proc_macro::TokenStream; use proc_macro2::Ident; use quote::{format_ident, quote}; diff --git a/crates/refineable/derive_refineable/src/derive_refineable.rs b/crates/refineable/derive_refineable/src/derive_refineable.rs index ad7678b58fe696f61c14776c316bb9d159044b2f..99418206462a0dc7bc3babd2f9bda534a69a0f39 100644 --- a/crates/refineable/derive_refineable/src/derive_refineable.rs +++ b/crates/refineable/derive_refineable/src/derive_refineable.rs @@ -69,13 +69,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { path: parse_quote!(Clone), })); - // punctuated.push_punct(syn::token::Add::default()); - // punctuated.push_value(TypeParamBound::Trait(TraitBound { - // paren_token: None, - // modifier: syn::TraitBoundModifier::None, - // lifetimes: None, - // path: parse_quote!(Default), - // })); punctuated }, }) @@ -94,10 +87,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }, }; - // refinable_refine_assignments - // refinable_refined_assignments - // refinement_refine_assignments - let refineable_refine_assignments: Vec = fields .iter() .map(|field| { From 5897b18cfd353bebb9a53a4add4530766b8df2b2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 11:42:03 -0800 Subject: [PATCH 51/98] remove more commented code --- crates/collab/src/tests/channel_tests.rs | 2 - crates/collab_ui/src/collab_panel.rs | 9 -- crates/gpui/src/style.rs | 104 +++++++++--------- crates/gpui/src/window.rs | 7 -- crates/project/src/project.rs | 14 --- crates/project_panel/src/project_panel.rs | 32 +++--- crates/rich_text/src/rich_text.rs | 33 +----- crates/rpc/build.rs | 1 - crates/search/src/buffer_search.rs | 1 - .../src/semantic_index_tests.rs | 2 - 10 files changed, 67 insertions(+), 138 deletions(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 2a88bc4c579db9855184e278d1fef37200d1f470..e80fe0fdca312bed54d98fce0a1ea69a8a4a6e86 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1418,8 +1418,6 @@ async fn test_channel_moving( ) { let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; - // let client_b = server.create_client(cx_b, "user_b").await; - // let client_c = server.create_client(cx_c, "user_c").await; let channels = server .make_channel_tree( diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3213119551ed341be33816336f8ca5c..13b378a341b419ce66a7685e11490e46f850f6b1 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1426,14 +1426,6 @@ impl CollabPanel { self.toggle_channel_collapsed(id, cx) } - // fn toggle_channel_collapsed_action( - // &mut self, - // action: &ToggleCollapse, - // cx: &mut ViewContext, - // ) { - // self.toggle_channel_collapsed(action.location, cx); - // } - fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { match self.collapsed_channels.binary_search(&channel_id) { Ok(ix) => { @@ -1910,7 +1902,6 @@ impl CollabPanel { let mut channel_link = None; let mut channel_tooltip_text = None; let mut channel_icon = None; - // let mut is_dragged_over = false; let text = match section { Section::ActiveCall => { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index a21957611d09feb25f59d4a842ab9b998e83b4d4..32b749c257b0def7913c835141ce25801dcf4afd 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -1,10 +1,10 @@ use std::{iter, mem, ops::Range}; use crate::{ - black, phi, point, quad, rems, AbsoluteLength, BorrowWindow, Bounds, ContentMask, Corners, - CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, - FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, - SizeRefinement, Styled, TextRun, WindowContext, + black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, + ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, + Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, + SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext, }; use collections::HashSet; use refineable::{Cascade, Refineable}; @@ -308,54 +308,54 @@ impl Style { } } - // pub fn apply_text_style(&self, cx: &mut C, f: F) -> R - // where - // C: BorrowAppContext, - // F: FnOnce(&mut C) -> R, - // { - // if self.text.is_some() { - // cx.with_text_style(Some(self.text.clone()), f) - // } else { - // f(cx) - // } - // } - - // /// Apply overflow to content mask - // pub fn apply_overflow(&self, bounds: Bounds, cx: &mut C, f: F) -> R - // where - // C: BorrowWindow, - // F: FnOnce(&mut C) -> R, - // { - // let current_mask = cx.content_mask(); - - // let min = current_mask.bounds.origin; - // let max = current_mask.bounds.lower_right(); - - // let mask_bounds = match ( - // self.overflow.x == Overflow::Visible, - // self.overflow.y == Overflow::Visible, - // ) { - // // x and y both visible - // (true, true) => return f(cx), - // // x visible, y hidden - // (true, false) => Bounds::from_corners( - // point(min.x, bounds.origin.y), - // point(max.x, bounds.lower_right().y), - // ), - // // x hidden, y visible - // (false, true) => Bounds::from_corners( - // point(bounds.origin.x, min.y), - // point(bounds.lower_right().x, max.y), - // ), - // // both hidden - // (false, false) => bounds, - // }; - // let mask = ContentMask { - // bounds: mask_bounds, - // }; - - // cx.with_content_mask(Some(mask), f) - // } + pub fn apply_text_style(&self, cx: &mut C, f: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&mut C) -> R, + { + if self.text.is_some() { + cx.with_text_style(Some(self.text.clone()), f) + } else { + f(cx) + } + } + + /// Apply overflow to content mask + pub fn apply_overflow(&self, bounds: Bounds, cx: &mut C, f: F) -> R + where + C: BorrowWindow, + F: FnOnce(&mut C) -> R, + { + let current_mask = cx.content_mask(); + + let min = current_mask.bounds.origin; + let max = current_mask.bounds.lower_right(); + + let mask_bounds = match ( + self.overflow.x == Overflow::Visible, + self.overflow.y == Overflow::Visible, + ) { + // x and y both visible + (true, true) => return f(cx), + // x visible, y hidden + (true, false) => Bounds::from_corners( + point(min.x, bounds.origin.y), + point(max.x, bounds.lower_right().y), + ), + // x hidden, y visible + (false, true) => Bounds::from_corners( + point(bounds.origin.x, min.y), + point(bounds.lower_right().x, max.y), + ), + // both hidden + (false, false) => bounds, + }; + let mask = ContentMask { + bounds: mask_bounds, + }; + + cx.with_content_mask(Some(mask), f) + } /// Paints the background of an element styled with this style. pub fn paint( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 509a6d8466609b5041f4f958ca1676107b639a14..470f076d78c4a06a2d3b0856974a8ce0bf8a7612 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3137,13 +3137,6 @@ impl AnyWindowHandle { } } -// #[cfg(any(test, feature = "test-support"))] -// impl From> for StackingOrder { -// fn from(small_vec: SmallVec<[u32; 16]>) -> Self { -// StackingOrder(small_vec) -// } -// } - /// An identifier for an [`Element`](crate::Element). /// /// Can be constructed with a string, a number, or both, as well diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 06b6da75b3ed382d03becbb5a418ddf28cbc05c0..5f37bbfce6483e359866a0dadb1d63b2e32b4651 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -99,20 +99,6 @@ pub trait Item { fn project_path(&self, cx: &AppContext) -> Option; } -// Language server state is stored across 3 collections: -// language_servers => -// a mapping from unique server id to LanguageServerState which can either be a task for a -// server in the process of starting, or a running server with adapter and language server arcs -// language_server_ids => a mapping from worktreeId and server name to the unique server id -// language_server_statuses => a mapping from unique server id to the current server status -// -// Multiple worktrees can map to the same language server for example when you jump to the definition -// of a file in the standard library. So language_server_ids is used to look up which server is active -// for a given worktree and language server name -// -// When starting a language server, first the id map is checked to make sure a server isn't already available -// for that worktree. If there is one, it finishes early. Otherwise, a new id is allocated and and -// the Starting variant of LanguageServerState is stored in the language_servers map. pub struct Project { worktrees: Vec, active_entry: Option, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 251e26ebfba004b81a49c1ce28956e01f42bbce5..ef48cd683207db2a8e51790dc5026133c68cb2df 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -221,10 +221,10 @@ impl ProjectPanel { }) .detach(); - // cx.observe_global::(|_, cx| { - // cx.notify(); - // }) - // .detach(); + cx.observe_global::(|_, cx| { + cx.notify(); + }) + .detach(); let mut this = Self { project: project.clone(), @@ -292,16 +292,16 @@ impl ProjectPanel { } &Event::SplitEntry { entry_id } => { if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { - if let Some(_entry) = worktree.read(cx).entry_for_id(entry_id) { - // workspace - // .split_path( - // ProjectPath { - // worktree_id: worktree.read(cx).id(), - // path: entry.path.clone(), - // }, - // cx, - // ) - // .detach_and_log_err(cx); + if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { + workspace + .split_path( + ProjectPath { + worktree_id: worktree.read(cx).id(), + path: entry.path.clone(), + }, + cx, + ) + .detach_and_log_err(cx); } } } @@ -788,10 +788,6 @@ impl ProjectPanel { cx.notify(); } } - - // cx.update_global(|drag_and_drop: &mut DragAndDrop, cx| { - // drag_and_drop.cancel_dragging::(cx); - // }) } } diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index b4a87b1e5de3ed3c71af8c1dcaf9a3ab7a2e4e96..83dd007308721f38cf9305f0b9739f0901fc2942 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use futures::FutureExt; use gpui::{ AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, @@ -85,31 +86,6 @@ impl RichText { }) .into_any_element() } - - // pub fn add_mention( - // &mut self, - // range: Range, - // is_current_user: bool, - // mention_style: HighlightStyle, - // ) -> anyhow::Result<()> { - // if range.end > self.text.len() { - // bail!( - // "Mention in range {range:?} is outside of bounds for a message of length {}", - // self.text.len() - // ); - // } - - // if is_current_user { - // self.region_ranges.push(range.clone()); - // self.regions.push(RenderedRegion { - // background_kind: Some(BackgroundKind::Mention), - // link_url: None, - // }); - // } - // self.highlights - // .push((range, Highlight::Highlight(mention_style))); - // Ok(()) - // } } pub fn render_markdown_mut( @@ -272,13 +248,6 @@ pub fn render_markdown( language_registry: &Arc, language: Option<&Arc>, ) -> RichText { - // let mut data = RichText { - // text: Default::default(), - // highlights: Default::default(), - // region_ranges: Default::default(), - // regions: Default::default(), - // }; - let mut text = String::new(); let mut highlights = Vec::new(); let mut link_ranges = Vec::new(); diff --git a/crates/rpc/build.rs b/crates/rpc/build.rs index 66b289f1db83ab47d9ffaeaff8ec172838b4921f..25dff9b007c148c25aab0f4bd87e07bcb543d08a 100644 --- a/crates/rpc/build.rs +++ b/crates/rpc/build.rs @@ -1,6 +1,5 @@ fn main() { let mut build = prost_build::Config::new(); - // build.protoc_arg("--experimental_allow_proto3_optional"); build .type_attribute(".", "#[derive(serde::Serialize)]") .compile_protos(&["proto/zed.proto"], &["proto"]) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index f7e36fe696258fa1a361bc6bd212f1d888efc2f8..9cbe49d99ea65414a89649be17fb3c5dc196cd83 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1648,7 +1648,6 @@ mod tests { #[gpui::test] async fn test_search_query_history(cx: &mut TestAppContext) { - //crate::project_search::tests::init_test(cx); init_globals(cx); let buffer_text = r#" A regular expression (shortened as regex or regexp;[1] also referred to as diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index ced08f4cbc30a991bfad0577af24f96c8ff81d8b..e340b44a58377b8a9bda52786dea660637ce54c1 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -1677,8 +1677,6 @@ fn elixir_lang() -> Arc { #[gpui::test] fn test_subtract_ranges() { - // collapsed_ranges: Vec>, keep_ranges: Vec> - assert_eq!( subtract_ranges(&[0..5, 10..21], &[0..1, 4..5]), vec![1..4, 10..21] From bfb59f1598e8e2497fd7e635c51340c7a40a07f1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 20:59:19 -0800 Subject: [PATCH 52/98] Remove last stale code --- crates/collab_ui/src/collab_panel.rs | 2 +- crates/storybook/src/storybook.rs | 5 - .../ui/src/components/stories/icon_button.rs | 50 ------- crates/workspace/src/item.rs | 21 --- crates/workspace/src/pane.rs | 137 ------------------ crates/workspace/src/persistence.rs | 2 - crates/workspace/src/workspace.rs | 30 ---- crates/zed/src/languages/python.rs | 2 +- crates/zed/src/zed.rs | 6 - 9 files changed, 2 insertions(+), 253 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 13b378a341b419ce66a7685e11490e46f850f6b1..ca54aa49c80f134da3f17c51ba124324f8845c69 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2043,7 +2043,7 @@ impl CollabPanel { }), ) .start_slot( - // todo!() handle contacts with no avatar + // todo handle contacts with no avatar Avatar::new(contact.user.avatar_uri.clone()) .availability_indicator(if online { Some(!busy) } else { None }), ) diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 8d60e29a136013a8e38e33f8ebb5ce8044d4b2d6..ceab82e12b02d0080c2374517317bb2401e1556b 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -21,11 +21,6 @@ use crate::assets::Assets; use crate::story_selector::{ComponentStory, StorySelector}; pub use indoc::indoc; -// gpui::actions! { -// storybook, -// [ToggleInspector] -// } - #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Args { diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index 6a67183e97c73b3795fc14a71c11a16b6012f549..df9f37b164782f35c9a2ca1cfba6aa96d8783d60 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -117,55 +117,5 @@ impl Render for IconButtonStory { ) .children(vec![StorySection::new().children(buttons)]) .into_element() - - // Story::container() - // .child(Story::title_for::()) - // .child(Story::label("Default")) - // .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash))) - // .child(Story::label("Selected")) - // .child( - // div() - // .w_8() - // .child(IconButton::new("icon_a", Icon::Hash).selected(true)), - // ) - // .child(Story::label("Selected with `selected_icon`")) - // .child( - // div().w_8().child( - // IconButton::new("icon_a", Icon::AudioOn) - // .selected(true) - // .selected_icon(Icon::AudioOff), - // ), - // ) - // .child(Story::label("Disabled")) - // .child( - // div() - // .w_8() - // .child(IconButton::new("icon_a", Icon::Hash).disabled(true)), - // ) - // .child(Story::label("With `on_click`")) - // .child( - // div() - // .w_8() - // .child( - // IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| { - // println!("Clicked!"); - // }), - // ), - // ) - // .child(Story::label("With `tooltip`")) - // .child( - // div().w_8().child( - // IconButton::new("with_tooltip", Icon::MessageBubbles) - // .tooltip(|cx| Tooltip::text("Open messages", cx)), - // ), - // ) - // .child(Story::label("Selected with `tooltip`")) - // .child( - // div().w_8().child( - // IconButton::new("selected_with_tooltip", Icon::InlayHint) - // .selected(true) - // .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), - // ), - // ) } } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index c629edc696b87e0552ca05956e2a1c5cf3b5e6b0..fb4ed05f6c006d8118304418a76dd6dcffc67576 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -809,27 +809,6 @@ pub mod test { Edit, } - // impl Clone for TestItem { - // fn clone(&self) -> Self { - // Self { - // state: self.state.clone(), - // label: self.label.clone(), - // save_count: self.save_count, - // save_as_count: self.save_as_count, - // reload_count: self.reload_count, - // is_dirty: self.is_dirty, - // is_singleton: self.is_singleton, - // has_conflict: self.has_conflict, - // project_items: self.project_items.clone(), - // nav_history: None, - // tab_descriptions: None, - // tab_detail: Default::default(), - // workspace_id: self.workspace_id, - // focus_handle: self.focus_handle.clone(), - // } - // } - // } - impl TestProjectItem { pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model { let entry_id = Some(ProjectEntryId::from_proto(id)); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aec33c2dd32ee9bda77e7fe7927e1346a86178df..1b95671398a613a01f41590bdce20e0a846592d0 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -60,24 +60,6 @@ pub enum SaveIntent { #[derive(Clone, Deserialize, PartialEq, Debug)] pub struct ActivateItem(pub usize); -// #[derive(Clone, PartialEq)] -// pub struct CloseItemById { -// pub item_id: usize, -// pub pane: WeakView, -// } - -// #[derive(Clone, PartialEq)] -// pub struct CloseItemsToTheLeftById { -// pub item_id: usize, -// pub pane: WeakView, -// } - -// #[derive(Clone, PartialEq)] -// pub struct CloseItemsToTheRightById { -// pub item_id: usize, -// pub pane: WeakView, -// } - #[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct CloseActiveItem { @@ -1226,125 +1208,6 @@ impl Pane { cx.emit(Event::Split(direction)); } - // fn deploy_split_menu(&mut self, cx: &mut ViewContext) { - // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - // menu.toggle( - // Default::default(), - // AnchorCorner::TopRight, - // vec![ - // ContextMenuItem::action("Split Right", SplitRight), - // ContextMenuItem::action("Split Left", SplitLeft), - // ContextMenuItem::action("Split Up", SplitUp), - // ContextMenuItem::action("Split Down", SplitDown), - // ], - // cx, - // ); - // }); - - // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; - // } - - // fn deploy_new_menu(&mut self, cx: &mut ViewContext) { - // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - // menu.toggle( - // Default::default(), - // AnchorCorner::TopRight, - // vec![ - // ContextMenuItem::action("New File", NewFile), - // ContextMenuItem::action("New Terminal", NewCenterTerminal), - // ContextMenuItem::action("New Search", NewSearch), - // ], - // cx, - // ); - // }); - - // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; - // } - - // fn deploy_tab_context_menu( - // &mut self, - // position: Vector2F, - // target_item_id: usize, - // cx: &mut ViewContext, - // ) { - // let active_item_id = self.items[self.active_item_index].id(); - // let is_active_item = target_item_id == active_item_id; - // let target_pane = cx.weak_handle(); - - // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab - - // self.tab_context_menu.update(cx, |menu, cx| { - // menu.show( - // position, - // AnchorCorner::TopLeft, - // if is_active_item { - // vec![ - // ContextMenuItem::action( - // "Close Active Item", - // CloseActiveItem { save_intent: None }, - // ), - // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - // ContextMenuItem::action("Close Clean Items", CloseCleanItems), - // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), - // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), - // ContextMenuItem::action( - // "Close All Items", - // CloseAllItems { save_intent: None }, - // ), - // ] - // } else { - // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. - // vec![ - // ContextMenuItem::handler("Close Inactive Item", { - // let pane = target_pane.clone(); - // move |cx| { - // if let Some(pane) = pane.upgrade(cx) { - // pane.update(cx, |pane, cx| { - // pane.close_item_by_id( - // target_item_id, - // SaveIntent::Close, - // cx, - // ) - // .detach_and_log_err(cx); - // }) - // } - // } - // }), - // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - // ContextMenuItem::action("Close Clean Items", CloseCleanItems), - // ContextMenuItem::handler("Close Items To The Left", { - // let pane = target_pane.clone(); - // move |cx| { - // if let Some(pane) = pane.upgrade(cx) { - // pane.update(cx, |pane, cx| { - // pane.close_items_to_the_left_by_id(target_item_id, cx) - // .detach_and_log_err(cx); - // }) - // } - // } - // }), - // ContextMenuItem::handler("Close Items To The Right", { - // let pane = target_pane.clone(); - // move |cx| { - // if let Some(pane) = pane.upgrade(cx) { - // pane.update(cx, |pane, cx| { - // pane.close_items_to_the_right_by_id(target_item_id, cx) - // .detach_and_log_err(cx); - // }) - // } - // } - // }), - // ContextMenuItem::action( - // "Close All Items", - // CloseAllItems { save_intent: None }, - // ), - // ] - // }, - // cx, - // ); - // }); - // } - pub fn toolbar(&self) -> &View { &self.toolbar } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d03c7b3d0f73c21bde176d416060ccc200aaa62e..56aa6e4322bce652aacda0ddb1f0c77cef61b7ee 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -1,5 +1,3 @@ -//#![allow(dead_code)] - pub mod model; use std::path::Path; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ca463e76e058c645839f853eadd8e0877ecca6da..efd2c52989edb51cff2559382f0ec62a2ce2702e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3324,36 +3324,6 @@ impl Workspace { workspace } - // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { - // let dock = match position { - // DockPosition::Left => &self.left_dock, - // DockPosition::Right => &self.right_dock, - // DockPosition::Bottom => &self.bottom_dock, - // }; - // let active_panel = dock.read(cx).visible_panel()?; - // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { - // dock.read(cx).render_placeholder(cx) - // } else { - // ChildView::new(dock, cx).into_any() - // }; - - // Some( - // element - // .constrained() - // .dynamically(move |constraint, _, cx| match position { - // DockPosition::Left | DockPosition::Right => SizeConstraint::new( - // Vector2F::new(20., constraint.min.y()), - // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), - // ), - // DockPosition::Bottom => SizeConstraint::new( - // Vector2F::new(constraint.min.x(), 20.), - // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), - // ), - // }) - // .into_any(), - // ) - // } - // } pub fn register_action( &mut self, callback: impl Fn(&mut Self, &A, &mut ViewContext) + 'static, diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index d28cd9f6e410cec04b3a3081e65166fea80ec159..8a30121d49c2ba350a42e5352065e1b7f886f0c7 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -184,7 +184,7 @@ mod tests { #[gpui::test] async fn test_python_autoindent(cx: &mut TestAppContext) { - // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); + cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); let language = crate::languages::language("python", tree_sitter_python::language(), None).await; cx.update(|cx| { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c2725eef64029a11cbf449769ee5f025ae7b0535..d7686c425ad6a40663aeb6552e790d00aab50f77 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -113,12 +113,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }) .detach(); - // cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - - // let collab_titlebar_item = - // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); - // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let copilot = cx.new_view(|cx| copilot_ui::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.new_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); From bb35805f9bf0dcd9d505e26613b3ffa1f95b3a4b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 21:02:01 -0800 Subject: [PATCH 53/98] fmt --- crates/rich_text/src/rich_text.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 83dd007308721f38cf9305f0b9739f0901fc2942..771f5602369093454b8252bd76dacf5e5002a5f8 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -1,4 +1,3 @@ -use anyhow::bail; use futures::FutureExt; use gpui::{ AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, From 78858d4d11de009a3c54beea87d8c3022707ec1d Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 21:11:17 -0800 Subject: [PATCH 54/98] Disable searches for '.', so that users with large monitors don't accidentally crash the terminal when searching for dot files. --- crates/terminal_view/src/terminal_view.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index ced122402f138e5f5b964792d7a1561260b063b6..98a04eb5f5598edab5010125da60d6a171994422 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -600,6 +600,9 @@ fn possible_open_targets( pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option { let query = query.as_str(); + if query == "." { + return None; + } let searcher = RegexSearch::new(&query); searcher.ok() } From 1d7dc96135fcf5b7dea358aeaa6d23f52d9eee9f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 21:37:09 -0800 Subject: [PATCH 55/98] Restore temp file initialization in telemetry code --- crates/client/src/telemetry.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index aa0c7c4af58be6c181a04e2b73cff55230096bf9..ee001614400def4fe40f1d5328027704e76b0141 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -14,6 +14,7 @@ use sysinfo::{ }; use tempfile::NamedTempFile; use util::http::HttpClient; +use util::ResultExt; use util::{channel::ReleaseChannel, TryFutureExt}; use self::event_coalescer::EventCoalescer; @@ -167,6 +168,19 @@ impl Telemetry { event_coalescer: EventCoalescer::new(), })); + cx.background_executor() + .spawn({ + let state = state.clone(); + async move { + if let Some(tempfile) = + NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err() + { + state.lock().log_file = Some(tempfile); + } + } + }) + .detach(); + cx.observe_global::({ let state = state.clone(); From 4d6dfa319d5d85211a71d0a0837d94732f0bcaff Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 22:44:07 -0800 Subject: [PATCH 56/98] Don't open files unescessary in dev builds --- crates/client/src/telemetry.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ee001614400def4fe40f1d5328027704e76b0141..ca717c9d6a6463b0351c0aa312f8aeeefab8c8ac 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -14,6 +14,7 @@ use sysinfo::{ }; use tempfile::NamedTempFile; use util::http::HttpClient; +#[cfg(not(debug_assertions))] use util::ResultExt; use util::{channel::ReleaseChannel, TryFutureExt}; @@ -168,6 +169,7 @@ impl Telemetry { event_coalescer: EventCoalescer::new(), })); + #[cfg(not(debug_assertions))] cx.background_executor() .spawn({ let state = state.clone(); From c2ff9fe2da012be54a78b3d335bb3983aeb424d2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 14:32:24 -0700 Subject: [PATCH 57/98] Don't lose focus on default panel state --- crates/collab_ui/src/chat_panel.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index ecd7d1a739e957bfa7c7bf7b19de774e97675e64..20c5857ad2e07d84c03f54793571851e5783bac5 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -59,6 +59,7 @@ pub struct ChatPanel { subscriptions: Vec, is_scrolled_to_bottom: bool, markdown_data: HashMap, + focus_handle: FocusHandle, } #[derive(Serialize, Deserialize)] @@ -126,6 +127,7 @@ impl ChatPanel { active: false, width: None, markdown_data: Default::default(), + focus_handle: cx.focus_handle(), }; let mut old_dock_position = this.position(cx); @@ -490,6 +492,7 @@ impl EventEmitter for ChatPanel {} impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_stack() + .track_focus(&self.focus_handle) .full() .on_action(cx.listener(Self::send)) .child( @@ -559,7 +562,11 @@ impl Render for ChatPanel { impl FocusableView for ChatPanel { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.message_editor.read(cx).focus_handle(cx) + if self.active_chat.is_some() { + self.message_editor.read(cx).focus_handle(cx) + } else { + self.focus_handle.clone() + } } } From f6ef07e7166eadbb4e65de941e4c5422162319d5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 21:37:13 -0700 Subject: [PATCH 58/98] Make chat prettier (to my eyes at least) --- crates/channel/src/channel_chat.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 106 ++++++++++++++++++++++------- crates/gpui/src/styled.rs | 11 ++- crates/ui/src/components/avatar.rs | 10 ++- 4 files changed, 101 insertions(+), 30 deletions(-) diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index d2250972f3095893458a873980d26ceb13240cf3..e9353a14419adf069a6319ed76997754008caa18 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -144,7 +144,7 @@ impl ChannelChat { message: MessageParams, cx: &mut ModelContext, ) -> Result>> { - if message.text.is_empty() { + if message.text.trim().is_empty() { Err(anyhow!("message body can't be empty"))?; } @@ -174,6 +174,8 @@ impl ChannelChat { let user_store = self.user_store.clone(); let rpc = self.rpc.clone(); let outgoing_messages_lock = self.outgoing_messages_lock.clone(); + + // todo - handle messages that fail to send (e.g. >1024 chars) Ok(cx.spawn(move |this, mut cx| async move { let outgoing_message_guard = outgoing_messages_lock.lock().await; let request = rpc.request(proto::SendChannelMessage { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 20c5857ad2e07d84c03f54793571851e5783bac5..dd3d7415225b4aae56bd8927c6884981a6681c74 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -8,8 +8,9 @@ use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, - ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, ListOffset, ListScrollEvent, - ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, + ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, + ListOffset, ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext, + VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; @@ -22,7 +23,8 @@ use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; use ui::{ - prelude::*, Avatar, Button, IconButton, IconName, Key, KeyBinding, Label, TabBar, Tooltip, + popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, Key, KeyBinding, + Label, TabBar, Tooltip, }; use util::{ResultExt, TryFutureExt}; use workspace::{ @@ -60,6 +62,7 @@ pub struct ChatPanel { is_scrolled_to_bottom: bool, markdown_data: HashMap, focus_handle: FocusHandle, + open_context_menu: Option<(u64, Subscription)>, } #[derive(Serialize, Deserialize)] @@ -128,6 +131,7 @@ impl ChatPanel { width: None, markdown_data: Default::default(), focus_handle: cx.focus_handle(), + open_context_menu: None, }; let mut old_dock_position = this.position(cx); @@ -348,50 +352,100 @@ impl ChatPanel { ChannelMessageId::Saved(id) => ("saved-message", id).into(), ChannelMessageId::Pending(id) => ("pending-message", id).into(), }; + let this = cx.view().clone(); v_stack() .w_full() - .id(element_id) .relative() .overflow_hidden() - .group("") .when(!is_continuation_from_previous, |this| { - this.child( + this.pt_3().child( h_stack() - .gap_2() - .child(Avatar::new(message.sender.avatar_uri.clone())) - .child(Label::new(message.sender.github_login.clone())) + .child( + div().absolute().child( + Avatar::new(message.sender.avatar_uri.clone()) + .size(cx.rem_size() * 1.5), + ), + ) + .child( + div() + .pl(cx.rem_size() * 1.5 + px(6.0)) + .pr(px(8.0)) + .font_weight(FontWeight::BOLD) + .child(Label::new(message.sender.github_login.clone())), + ) .child( Label::new(format_timestamp( message.timestamp, now, self.local_timezone, )) + .size(LabelSize::Small) .color(Color::Muted), ), ) }) - .when(!is_continuation_to_next, |this| - // HACK: This should really be a margin, but margins seem to get collapsed. - this.pb_2()) - .child(text.element("body".into(), cx)) + .when(is_continuation_from_previous, |this| this.pt_1()) .child( - div() - .absolute() - .top_1() - .right_2() - .w_8() - .visible_on_hover("") - .children(message_id_to_remove.map(|message_id| { - IconButton::new(("remove", message_id), IconName::XCircle).on_click( - cx.listener(move |this, _, cx| { - this.remove_message(message_id, cx); - }), - ) - })), + v_stack() + .w_full() + .text_ui_sm() + .id(element_id) + .group("") + .child(text.element("body".into(), cx)) + .child( + div() + .absolute() + .z_index(1) + .right_0() + .w_6() + .bg(cx.theme().colors().panel_background) + .when(!self.has_open_menu(message_id_to_remove), |el| { + el.visible_on_hover("") + }) + .children(message_id_to_remove.map(|message_id| { + popover_menu(("menu", message_id)) + .trigger(IconButton::new( + ("trigger", message_id), + IconName::Ellipsis, + )) + .menu(move |cx| { + Some(Self::render_message_menu(&this, message_id, cx)) + }) + })), + ), ) } + fn has_open_menu(&self, message_id: Option) -> bool { + match self.open_context_menu.as_ref() { + Some((id, _)) => Some(*id) == message_id, + None => false, + } + } + + fn render_message_menu( + this: &View, + message_id: u64, + cx: &mut WindowContext, + ) -> View { + let menu = { + let this = this.clone(); + ContextMenu::build(cx, move |menu, _| { + menu.entry("Delete message", None, move |cx| { + this.update(cx, |this, cx| this.remove_message(message_id, cx)) + }) + }) + }; + this.update(cx, |this, cx| { + let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| { + this.open_context_menu = None; + }); + this.open_context_menu = Some((message_id, subscription)); + }); + menu + } + fn render_markdown_with_mentions( language_registry: &Arc, current_user_id: u64, diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index 2749c31a788f8d1fd83fe1353aaae15179b859cb..0eba1771f52d47bde32f465a887e52547f3a89b2 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -1,7 +1,7 @@ use crate::{ self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, - DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, - SharedString, StyleRefinement, Visibility, WhiteSpace, + DefiniteLength, Display, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length, + Position, SharedString, StyleRefinement, Visibility, WhiteSpace, }; use crate::{BoxShadow, TextStyleRefinement}; use smallvec::{smallvec, SmallVec}; @@ -494,6 +494,13 @@ pub trait Styled: Sized { self } + fn font_weight(mut self, weight: FontWeight) -> Self { + self.text_style() + .get_or_insert_with(Default::default) + .font_weight = Some(weight); + self + } + fn text_bg(mut self, bg: impl Into) -> Self { self.text_style() .get_or_insert_with(Default::default) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 9e64e1223c346f4d153fb7d2955b606ff53736ae..a97adb73b7d88ae0dfb1c60c25eff3942c5ad52d 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -26,6 +26,7 @@ pub enum AvatarShape { #[derive(IntoElement)] pub struct Avatar { image: Img, + size: Option, border_color: Option, is_available: Option, } @@ -36,7 +37,7 @@ impl RenderOnce for Avatar { self = self.shape(AvatarShape::Circle); } - let size = cx.rem_size(); + let size = self.size.unwrap_or_else(|| cx.rem_size()); div() .size(size + px(2.)) @@ -78,6 +79,7 @@ impl Avatar { image: img(src), is_available: None, border_color: None, + size: None, } } @@ -124,4 +126,10 @@ impl Avatar { self.is_available = is_available.into(); self } + + /// Size overrides the avatar size. By default they are 1rem. + pub fn size(mut self, size: impl Into>) -> Self { + self.size = size.into(); + self + } } From c810af40d3a63ef7c82696101c6023e66b6e1185 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 21:53:22 -0700 Subject: [PATCH 59/98] Fix multiple mentions in one message --- crates/collab/src/db/queries/messages.rs | 1 + crates/rich_text/src/rich_text.rs | 28 +++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index 47bb27df39060a7fff27bd50218e9b3626d13dd6..96942c052df75c9406272da3a563533342f64406 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -256,6 +256,7 @@ impl Database { message_id = result.last_insert_id; let mentioned_user_ids = mentions.iter().map(|m| m.user_id).collect::>(); + let mentions = mentions .iter() .filter_map(|mention| { diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index b4a87b1e5de3ed3c71af8c1dcaf9a3ab7a2e4e96..ac3414555aeff0bec698e788da485517259c8ad1 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -39,6 +39,7 @@ pub struct RichText { /// Allows one to specify extra links to the rendered markdown, which can be used /// for e.g. mentions. +#[derive(Debug)] pub struct Mention { pub range: Range, pub is_self_mention: bool, @@ -138,20 +139,21 @@ pub fn render_markdown_mut( if let Some(language) = ¤t_language { render_code(text, highlights, t.as_ref(), language); } else { - if let Some(mention) = mentions.first() { - if source_range.contains_inclusive(&mention.range) { - mentions = &mentions[1..]; - let range = (prev_len + mention.range.start - source_range.start) - ..(prev_len + mention.range.end - source_range.start); - highlights.push(( - range.clone(), - if mention.is_self_mention { - Highlight::SelfMention - } else { - Highlight::Mention - }, - )); + while let Some(mention) = mentions.first() { + if !source_range.contains_inclusive(&mention.range) { + break; } + mentions = &mentions[1..]; + let range = (prev_len + mention.range.start - source_range.start) + ..(prev_len + mention.range.end - source_range.start); + highlights.push(( + range.clone(), + if mention.is_self_mention { + Highlight::SelfMention + } else { + Highlight::Mention + }, + )); } text.push_str(t.as_ref()); From 818cbb24157334adac4a448c237e0ab35019e41c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:19:21 -0700 Subject: [PATCH 60/98] Show a border when scrolled in chat --- crates/collab_ui/src/chat_panel.rs | 37 ++++++++++++++++++------------ crates/gpui/src/elements/list.rs | 2 ++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index dd3d7415225b4aae56bd8927c6884981a6681c74..ee0241eb1209dc6a36150c7d68b68250476c59e5 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -112,7 +112,7 @@ impl ChatPanel { if event.visible_range.start < MESSAGE_LOADING_THRESHOLD { this.load_more_messages(cx); } - this.is_scrolled_to_bottom = event.visible_range.end == event.count; + this.is_scrolled_to_bottom = !event.is_scrolled; })); let mut this = Self { @@ -567,7 +567,7 @@ impl Render for ChatPanel { ), ), ) - .child(div().flex_grow().px_2().py_1().map(|this| { + .child(div().flex_grow().px_2().pt_1().map(|this| { if self.active_chat.is_some() { this.child(list(self.message_list.clone()).full()) } else { @@ -597,19 +597,26 @@ impl Render for ChatPanel { ) } })) - .child(h_stack().p_2().map(|el| { - if self.active_chat.is_some() { - el.child(self.message_editor.clone()) - } else { - el.child( - div() - .rounded_md() - .h_7() - .w_full() - .bg(cx.theme().colors().editor_background), - ) - } - })) + .child( + h_stack() + .when(!self.is_scrolled_to_bottom, |el| { + el.border_t_1().border_color(cx.theme().colors().border) + }) + .p_2() + .map(|el| { + if self.active_chat.is_some() { + el.child(self.message_editor.clone()) + } else { + el.child( + div() + .rounded_md() + .h_7() + .w_full() + .bg(cx.theme().colors().editor_background), + ) + } + }), + ) .into_any() } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 2a47a16741cf67c0cefb8a094d2f9e506cacbdf4..50e7af5138480562212c8d182352bfebdc88db36 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -43,6 +43,7 @@ pub enum ListAlignment { pub struct ListScrollEvent { pub visible_range: Range, pub count: usize, + pub is_scrolled: bool, } #[derive(Clone)] @@ -253,6 +254,7 @@ impl StateInner { &ListScrollEvent { visible_range, count: self.items.summary().count, + is_scrolled: self.logical_scroll_top.is_some(), }, cx, ); From fee369bca1571fd00fd9c7b59f8fbc36bb96e6e0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:26:25 -0700 Subject: [PATCH 61/98] Fix re-docking chat panel --- crates/collab_ui/src/chat_panel.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index ee0241eb1209dc6a36150c7d68b68250476c59e5..13b217eec48a9f6380facc5539b1d58831ebd84a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -70,13 +70,6 @@ struct SerializedChatPanel { width: Option, } -#[derive(Debug)] -pub enum Event { - DockPositionChanged, - Focus, - Dismissed, -} - actions!(chat_panel, [ToggleFocus]); impl ChatPanel { @@ -140,7 +133,7 @@ impl ChatPanel { let new_dock_position = this.position(cx); if new_dock_position != old_dock_position { old_dock_position = new_dock_position; - cx.emit(Event::DockPositionChanged); + cx.emit(PanelEvent::ChangePosition); } cx.notify(); }, @@ -541,8 +534,6 @@ impl ChatPanel { } } -impl EventEmitter for ChatPanel {} - impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_stack() @@ -662,7 +653,7 @@ impl Panel for ChatPanel { if active { self.acknowledge_last_message(cx); if !is_channels_feature_enabled(cx) { - cx.emit(Event::Dismissed); + cx.emit(PanelEvent::Close); } } } From e90ddba2c3ba63e6025fac42a0abdc7e1afdbc9c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:38:22 -0700 Subject: [PATCH 62/98] Default to Zed Sans for UI --- assets/settings/default.json | 2 +- crates/gpui/src/text_system.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index bd157c3e6137ccf5e990b94a3085896563931e60..87cf0517a2b6f1a32ca8b0d33dadda2d3418f3db 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -36,7 +36,7 @@ // }, "buffer_line_height": "comfortable", // The name of a font to use for rendering text in the UI - "ui_font_family": "Zed Mono", + "ui_font_family": "Zed Sans", // The OpenType features to enable for text in the UI "ui_font_features": { // Disable ligatures: diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index d80c9163a996be96580f459c13e0e8eba8e71d10..6c84a7716f48e62fbd64dc8872dc68a7c6a7dcb2 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -59,7 +59,7 @@ impl TextSystem { fallback_font_stack: smallvec![ // TODO: This is currently Zed-specific. // We should allow GPUI users to provide their own fallback font stack. - font("Zed Mono"), + font("Zed Sans"), font("Helvetica") ], } From 898645681fe6e14e746988b376bdeffbebcb7090 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:35:33 -0700 Subject: [PATCH 63/98] Move settings subscription to dock Reduces likelihood of panels being unable to move themselves --- crates/assistant/src/assistant_panel.rs | 11 ----------- crates/collab_ui/src/chat_panel.rs | 11 ----------- crates/collab_ui/src/collab_panel.rs | 13 ------------- crates/project_panel/src/project_panel.rs | 12 ------------ crates/terminal_view/src/terminal_panel.rs | 9 --------- crates/workspace/src/dock.rs | 19 +++++++++++++------ 6 files changed, 13 insertions(+), 62 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index d4743afb714ab47be3e38b196a45174fb33c2aa5..9519cfacdb1715afd9cdd618d723547d40c231a5 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -192,17 +192,6 @@ impl AssistantPanel { retrieve_context_in_next_inline_assist: false, }; - let mut old_dock_position = this.position(cx); - this.subscriptions = - vec![cx.observe_global::(move |this, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - cx.notify(); - })]; - this }) }) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 13b217eec48a9f6380facc5539b1d58831ebd84a..e5c3a13442b8e27c60e6352130d46724fa57a069 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -127,17 +127,6 @@ impl ChatPanel { open_context_menu: None, }; - let mut old_dock_position = this.position(cx); - this.subscriptions.push(cx.observe_global::( - move |this: &mut Self, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - cx.notify(); - }, - )); this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), move |this: &mut Self, call, event: &room::Event, cx| match event { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3213119551ed341be33816336f8ca5c..bbd24136ad3ba34f9a994f2c05e455a681931588 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -254,19 +254,6 @@ impl CollabPanel { this.update_entries(false, cx); - // Update the dock position when the setting changes. - let mut old_dock_position = this.position(cx); - this.subscriptions.push(cx.observe_global::( - move |this: &mut Self, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - cx.notify(); - }, - )); - let active_call = ActiveCall::global(cx); this.subscriptions .push(cx.observe(&this.user_store, |this, _, cx| { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 251e26ebfba004b81a49c1ce28956e01f42bbce5..5fa8fb102d2370d0d0e056008173b287103208d3 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -246,18 +246,6 @@ impl ProjectPanel { }; this.update_visible_entries(None, cx); - // Update the dock position when the setting changes. - let mut old_dock_position = this.position(cx); - ProjectPanelSettings::register(cx); - cx.observe_global::(move |this, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - }) - .detach(); - this }); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index d0b52f5eb217ed60cc9b62e20657e5033506f133..2156aad18af283c0c9176856db38cfbf9a7f7adc 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -159,15 +159,6 @@ impl TerminalPanel { height: None, _subscriptions: subscriptions, }; - let mut old_dock_position = this.position(cx); - cx.observe_global::(move |this, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - }) - .detach(); this } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0c752597262ca1f64a996d09795db289941a30b2..742ff3b6f859988a74d34c9f1625167a78d76927 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -7,6 +7,7 @@ use gpui::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use settings::SettingsStore; use std::sync::Arc; use ui::{h_stack, ContextMenu, IconButton, Tooltip}; use ui::{prelude::*, right_click_menu}; @@ -14,7 +15,6 @@ use ui::{prelude::*, right_click_menu}; const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.); pub enum PanelEvent { - ChangePosition, ZoomIn, ZoomOut, Activate, @@ -177,7 +177,7 @@ impl DockPosition { struct PanelEntry { panel: Arc, - _subscriptions: [Subscription; 2], + _subscriptions: [Subscription; 3], } pub struct PanelButtons { @@ -321,9 +321,15 @@ impl Dock { ) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), - cx.subscribe(&panel, move |this, panel, event, cx| match event { - PanelEvent::ChangePosition => { + cx.observe_global::({ + let workspace = workspace.clone(); + let panel = panel.clone(); + + move |this, cx| { let new_position = panel.read(cx).position(cx); + if new_position == this.position { + return; + } let Ok(new_dock) = workspace.update(cx, |workspace, cx| { if panel.is_zoomed(cx) { @@ -354,6 +360,8 @@ impl Dock { } }); } + }), + cx.subscribe(&panel, move |this, panel, event, cx| match event { PanelEvent::ZoomIn => { this.set_panel_zoomed(&panel.to_any(), true, cx); if !panel.focus_handle(cx).contains_focused(cx) { @@ -735,9 +743,8 @@ pub mod test { true } - fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + fn set_position(&mut self, position: DockPosition, _: &mut ViewContext) { self.position = position; - cx.emit(PanelEvent::ChangePosition); } fn size(&self, _: &WindowContext) -> Pixels { From 4d87a67af8626a43f37c3458c4e9f04ed2414d4e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:44:47 -0700 Subject: [PATCH 64/98] Remove unused imports --- crates/assistant/src/assistant_panel.rs | 9 +++---- crates/collab_ui/src/chat_panel.rs | 29 +++++++--------------- crates/collab_ui/src/collab_panel.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 2 +- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 9519cfacdb1715afd9cdd618d723547d40c231a5..2b0a0941c0f014e306d522100477b1e9a9477399 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -40,7 +40,7 @@ use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset a use project::Project; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use semantic_index::{SemanticIndex, SemanticIndexStatus}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use std::{ cell::Cell, cmp, @@ -165,7 +165,7 @@ impl AssistantPanel { cx.on_focus_in(&focus_handle, Self::focus_in).detach(); cx.on_focus_out(&focus_handle, Self::focus_out).detach(); - let mut this = Self { + Self { workspace: workspace_handle, active_editor_index: Default::default(), prev_active_editor_index: Default::default(), @@ -190,9 +190,7 @@ impl AssistantPanel { _watch_saved_conversations, semantic_index, retrieve_context_in_next_inline_assist: false, - }; - - this + } }) }) }) @@ -3122,6 +3120,7 @@ mod tests { use crate::MessageId; use ai::test::FakeCompletionProvider; use gpui::AppContext; + use settings::SettingsStore; #[gpui::test] fn test_inserting_and_removing_messages(cx: &mut AppContext) { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index e5c3a13442b8e27c60e6352130d46724fa57a069..c919ceac9fd679f734e3a72b2596af72d0191b56 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings, CollabPanel}; +use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -7,24 +7,22 @@ use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ - actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, - ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, - ListOffset, ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext, - VisualContext, WeakView, + actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, DismissEvent, + ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, ListOffset, ListScrollEvent, + ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; use message_editor::MessageEditor; use project::Fs; use rich_text::RichText; -use rpc::proto; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; use ui::{ - popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, Key, KeyBinding, - Label, TabBar, Tooltip, + popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, + TabBar, }; use util::{ResultExt, TryFutureExt}; use workspace::{ @@ -279,7 +277,7 @@ impl ChatPanel { fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> impl IntoElement { let active_chat = &self.active_chat.as_ref().unwrap().0; - let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) = + let (message, is_continuation_from_previous, is_admin) = active_chat.update(cx, |active_chat, cx| { let is_admin = self .channel_store @@ -288,13 +286,9 @@ impl ChatPanel { let last_message = active_chat.message(ix.saturating_sub(1)); let this_message = active_chat.message(ix).clone(); - let next_message = - active_chat.message(ix.saturating_add(1).min(active_chat.message_count() - 1)); let is_continuation_from_previous = last_message.id != this_message.id && last_message.sender.id == this_message.sender.id; - let is_continuation_to_next = this_message.id != next_message.id - && this_message.sender.id == next_message.sender.id; if let ChannelMessageId::Saved(id) = this_message.id { if this_message @@ -306,12 +300,7 @@ impl ChatPanel { } } - ( - this_message, - is_continuation_from_previous, - is_continuation_to_next, - is_admin, - ) + (this_message, is_continuation_from_previous, is_admin) }); let _is_pending = message.is_pending(); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index bbd24136ad3ba34f9a994f2c05e455a681931588..38569adb20586308c5236cd67216e13cbc466e3c 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -26,7 +26,7 @@ use menu::{Cancel, Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; use rpc::proto::{self, PeerId}; use serde_derive::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 5fa8fb102d2370d0d0e056008173b287103208d3..113900dcd20360ef3f418adedf624a6e951eafcd 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,6 +1,6 @@ pub mod file_associations; mod project_panel_settings; -use settings::{Settings, SettingsStore}; +use settings::Settings; use db::kvp::KEY_VALUE_STORE; use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor}; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 2156aad18af283c0c9176856db38cfbf9a7f7adc..dee18ea73b55f7d17c9cc742de179ed580f3d596 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -11,7 +11,7 @@ use itertools::Itertools; use project::{Fs, ProjectEntryId}; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; use ui::{h_stack, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip}; use util::{ResultExt, TryFutureExt}; From b34c78016fe051cef04547c89b5a74b1181c5ec3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sun, 14 Jan 2024 12:26:54 -0700 Subject: [PATCH 65/98] Fix tests for TestPanel --- crates/workspace/src/dock.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 742ff3b6f859988a74d34c9f1625167a78d76927..52f768e03f9c53460b539d53be1729a7d68c81ae 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -743,8 +743,9 @@ pub mod test { true } - fn set_position(&mut self, position: DockPosition, _: &mut ViewContext) { + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { self.position = position; + cx.update_global::(|_, _| {}); } fn size(&self, _: &WindowContext) -> Pixels { From 05d05b051b17f64fc99fa1b4888761caba0058d5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 11:40:42 +0100 Subject: [PATCH 66/98] Pop node from dispatch tree during `cx.paint_view` Co-Authored-By: Thorsten --- crates/gpui/src/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index b707066559dd5526b9430d298c35dbe843c1a6f9..8e38992251f5da196038f2718060d4f12a279496 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2034,6 +2034,7 @@ impl<'a> WindowContext<'a> { .dispatch_tree .push_node(None, None, Some(view_id)); let result = f(self); + self.window.next_frame.dispatch_tree.pop_node(); self.window.next_frame.view_stack.pop(); result } From 74f3366f425d070a450abadc68d774f3367efb9f Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 15 Jan 2024 13:47:09 +0100 Subject: [PATCH 67/98] Fix editor stealing click events from copy-error button Co-authored-by: Antonio --- crates/editor/src/element.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 29d3e0aaea38011a120401eb54aabb1a311bd3b6..e602b23c8e9f840d3c1e9730407d80a6193f55bf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -388,7 +388,9 @@ impl EditorElement { let mut click_count = event.click_count; let modifiers = event.modifiers; - if gutter_bounds.contains(&event.position) { + if cx.default_prevented() { + return; + } else if gutter_bounds.contains(&event.position) { click_count = 3; // Simulate triple-click when clicking the gutter to select lines } else if !text_bounds.contains(&event.position) { return; From 253c8dbe8ea6683d9915bade2f29331037392ec5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 15:49:23 +0200 Subject: [PATCH 68/98] Disable copilot for feedback and lsp log editors LSP log editor caused recursive flood of messages, and feedback editor is better with people writing their own feedback. --- crates/editor/src/editor.rs | 13 ++++++++++--- crates/feedback/src/feedback_modal.rs | 1 + crates/language_tools/src/lsp_log.rs | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0bfd6afa3b265dfb3845e944b6d7a916ca066261..56c5be0fbd69c64824ec9635199732d224a1bd6e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -604,6 +604,7 @@ pub struct Editor { gutter_width: Pixels, style: Option, editor_actions: Vec)>>, + show_copilot_suggestions: bool, } pub struct EditorSnapshot { @@ -1804,6 +1805,7 @@ impl Editor { gutter_width: Default::default(), style: None, editor_actions: Default::default(), + show_copilot_suggestions: mode == EditorMode::Full, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), @@ -2066,6 +2068,10 @@ impl Editor { self.read_only = read_only; } + pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) { + self.show_copilot_suggestions = show_copilot_suggestions; + } + fn selections_did_change( &mut self, local: bool, @@ -3976,7 +3982,7 @@ impl Editor { cx: &mut ViewContext, ) -> Option<()> { let copilot = Copilot::global(cx)?; - if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + if !self.show_copilot_suggestions || !copilot.read(cx).status().is_authorized() { self.clear_copilot_suggestions(cx); return None; } @@ -4036,7 +4042,7 @@ impl Editor { cx: &mut ViewContext, ) -> Option<()> { let copilot = Copilot::global(cx)?; - if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + if !self.show_copilot_suggestions || !copilot.read(cx).status().is_authorized() { return None; } @@ -4161,7 +4167,8 @@ impl Editor { let file = snapshot.file_at(location); let language = snapshot.language_at(location); let settings = all_language_settings(file, cx); - settings.copilot_enabled(language, file.map(|f| f.path().as_ref())) + self.show_copilot_suggestions + && settings.copilot_enabled(language, file.map(|f| f.path().as_ref())) } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 2444a8e94850f8128aafb0948cb356eee81f1086..38e06376cb6a46795181a3498a0d048b7a968e39 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -186,6 +186,7 @@ impl FeedbackModal { cx, ); editor.set_show_gutter(false, cx); + editor.set_show_copilot_suggestions(false); editor.set_vertical_scroll_margin(5, cx); editor }); diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index e3718267582b5331faa0dc71be3338a93022730f..62aead0857c87e6f88482991b4d7f18dae34ca59 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -449,6 +449,7 @@ impl LspLogView { editor.set_text(log_contents, cx); editor.move_to_end(&MoveToEnd, cx); editor.set_read_only(true); + editor.set_show_copilot_suggestions(false); editor }); let editor_subscription = cx.subscribe( From 346103dfb40555a3315352559a7f8afc839432b2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 16:20:53 +0200 Subject: [PATCH 69/98] Do not run squawk tests outside of PR builds --- script/squawk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/script/squawk b/script/squawk index e4ade6fbed6e77dd7fc791ed33988c02020f0078..0fb3e5a3325e8005fe4f0667debc7626cab5c9bd 100755 --- a/script/squawk +++ b/script/squawk @@ -6,6 +6,11 @@ set -e +if [ -z "$GITHUB_BASE_REF" ]; then + echo 'Not a pull request, skipping squawk modified migrations linting' + return 0 +fi + SQUAWK_VERSION=0.26.0 SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION" SQUAWK_ARGS="--assume-in-transaction" From e52a2298cc517403c120a85f7ae313e460e9fa42 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:46:18 +0100 Subject: [PATCH 70/98] gpui: Pin to font-kit with improved OTC parsing performance. (#4047) Details are in https://github.com/zed-industries/font-kit/pull/1; We're not doing anything too fancy, really. Still, you should mostly see font loading times drop significantly for font collections Release Notes: - Improved loading performance of large font collections (e.g. Iosevka). Fixes https://github.com/zed-industries/community/issues/1745, https://github.com/zed-industries/community/issues/246 https://github.com/zed-industries/zed/assets/24362066/f70edbad-ded6-4c12-9c6d-7a487f330a1b --- Cargo.lock | 2 +- crates/gpui/Cargo.toml | 2 +- crates/gpui/src/platform/mac/open_type.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4bac6d7c500f4813e73402ec06f47051876788b..7c240ac8a1e2cfdb714ea9f26cb6ee603539aedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2610,7 +2610,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-kit" version = "0.11.0" -source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" +source = "git+https://github.com/zed-industries/font-kit?rev=d97147f#d97147ff11a9024b9707d9c9c7e3a0bdaba048ac" dependencies = [ "bitflags 1.3.2", "byteorder", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 118753aafea5981e0dd2119f9b4203c70b85695d..333f5a1649f3c73ec8a31664dfc7a74d88158cb3 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -78,7 +78,7 @@ cocoa = "0.24" core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" -font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" } +font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "d97147f" } foreign-types = "0.3" log.workspace = true metal = "0.21.0" diff --git a/crates/gpui/src/platform/mac/open_type.rs b/crates/gpui/src/platform/mac/open_type.rs index 50e93a866db6c569926b791e5c89d82be7f8199b..c9d7197c0d3aa87d33d2e10048b14aa6c77483ec 100644 --- a/crates/gpui/src/platform/mac/open_type.rs +++ b/crates/gpui/src/platform/mac/open_type.rs @@ -378,7 +378,7 @@ fn toggle_open_type_feature( new_descriptor.as_concrete_TypeRef(), ); let new_font = CTFont::wrap_under_create_rule(new_font); - *font = Font::from_native_font(new_font); + *font = Font::from_native_font(&new_font); } } } From 5000a53a6100e8fb7bfc5cfcbd09f082fcb93c17 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 15 Jan 2024 10:19:08 -0500 Subject: [PATCH 71/98] Prevent storybook dialog from swallowing terminal cursor when ctrl-c-ed --- Cargo.lock | 88 +++++++++++++++++++++++++++++++ crates/storybook/Cargo.toml | 1 + crates/storybook/src/storybook.rs | 12 +++-- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4bac6d7c500f4813e73402ec06f47051876788b..3abb269c857e865069fb6392cea9b7b7f2b79e42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,6 +1949,16 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "ctrlc" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +dependencies = [ + "nix 0.27.1", + "windows-sys 0.52.0", +] + [[package]] name = "curl" version = "0.4.44" @@ -4489,6 +4499,17 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "node_runtime" version = "0.1.0" @@ -7452,6 +7473,7 @@ dependencies = [ "chrono", "clap 4.4.4", "collab_ui", + "ctrlc", "dialoguer", "editor", "fuzzy", @@ -9289,6 +9311,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -9319,6 +9350,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -9331,6 +9377,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -9343,6 +9395,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -9355,6 +9413,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9367,6 +9431,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9379,6 +9449,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9391,6 +9467,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9403,6 +9485,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.15" diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 9f08556757b6b59d1aed6fa7361029a36664a40a..fec2d42e5980b9e8b2ebb93f865dd74070aa3bc1 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -35,6 +35,7 @@ menu = { path = "../menu" } ui = { path = "../ui", features = ["stories"] } util = { path = "../util" } picker = { path = "../picker" } +ctrlc = "3.4" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index ceab82e12b02d0080c2374517317bb2401e1556b..7da1d67b307e660963c34297fc1fcfcca0c4222b 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -44,11 +44,17 @@ fn main() { let story_selector = args.story.clone().unwrap_or_else(|| { let stories = ComponentStory::iter().collect::>(); - let selection = FuzzySelect::new() + ctrlc::set_handler(move || {}).unwrap(); + + let result = FuzzySelect::new() .with_prompt("Choose a story to run:") .items(&stories) - .interact() - .unwrap(); + .interact(); + + let Ok(selection) = result else { + dialoguer::console::Term::stderr().show_cursor().unwrap(); + std::process::exit(0); + }; StorySelector::Component(stories[selection]) }); From 0ff5603dc9623288154932143c1ea880564065ed Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 16:23:22 +0100 Subject: [PATCH 72/98] Rebuild shader header when cbindgen sources have changed Co-Authored-By: Thorsten --- crates/gpui/build.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 24e493cb812d6e7478b266cc621eea7cbc77b051..44228b2e75229d36907eabcc82409e1f3eb79cb7 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -70,13 +70,23 @@ fn generate_shader_bindings() -> PathBuf { ]); config.no_includes = true; config.enumeration.prefix_with_name = true; - cbindgen::Builder::new() - .with_src(crate_dir.join("src/scene.rs")) - .with_src(crate_dir.join("src/geometry.rs")) - .with_src(crate_dir.join("src/color.rs")) - .with_src(crate_dir.join("src/window.rs")) - .with_src(crate_dir.join("src/platform.rs")) - .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs")) + + let mut builder = cbindgen::Builder::new(); + + let src_paths = [ + crate_dir.join("src/scene.rs"), + crate_dir.join("src/geometry.rs"), + crate_dir.join("src/color.rs"), + crate_dir.join("src/window.rs"), + crate_dir.join("src/platform.rs"), + crate_dir.join("src/platform/mac/metal_renderer.rs"), + ]; + for src_path in src_paths { + println!("cargo:rerun-if-changed={}", src_path.display()); + builder = builder.with_src(src_path); + } + + builder .with_config(config) .generate() .expect("Unable to generate bindings") From b136d21ebf1332f918eac2e636030ee11c833d65 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 15 Jan 2024 10:43:03 -0500 Subject: [PATCH 73/98] Make tab close button square (#4052) This PR makes the close button for tabs square. `IconButton` now accepts a `shape`, and using `IconButtonShape::Square` will ensure the `IconButton` is square with respect to its contained icon. #### Before Screenshot 2024-01-15 at 10 32 40 AM #### After Screenshot 2024-01-15 at 10 32 24 AM Release Notes: - Changed the tab close button to be square. --- .../ui/src/components/button/button_like.rs | 9 ++++- .../ui/src/components/button/icon_button.rs | 39 ++++++++++++++----- crates/ui/src/components/stories/tab.rs | 3 +- crates/workspace/src/pane.rs | 5 ++- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 3e4b478a9a237c44b95e5de09158e35e40e55449..018d31dafdb4491bc88733117c8e42f66e789aa3 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -300,6 +300,7 @@ pub struct ButtonLike { pub(super) selected: bool, pub(super) selected_style: Option, pub(super) width: Option, + pub(super) height: Option, size: ButtonSize, rounding: Option, tooltip: Option AnyView>>, @@ -317,6 +318,7 @@ impl ButtonLike { selected: false, selected_style: None, width: None, + height: None, size: ButtonSize::Default, rounding: Some(ButtonLikeRounding::All), tooltip: None, @@ -325,6 +327,11 @@ impl ButtonLike { } } + pub(crate) fn height(mut self, height: DefiniteLength) -> Self { + self.height = Some(height); + self + } + pub(crate) fn rounding(mut self, rounding: impl Into>) -> Self { self.rounding = rounding.into(); self @@ -417,7 +424,7 @@ impl RenderOnce for ButtonLike { .id(self.id.clone()) .group("") .flex_none() - .h(self.size.height()) + .h(self.height.unwrap_or(self.size.height().into())) .when_some(self.width, |this, width| this.w(width).justify_center()) .when_some(self.rounding, |this, rounding| match rounding { ButtonLikeRounding::All => this.rounded_md(), diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index 7c5313497c77ff6532838966001d2cbd4a3688e0..1e37a872922b4473616b551352f8100bbd9b1327 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -5,9 +5,17 @@ use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSiz use super::button_icon::ButtonIcon; +/// The shape of an [`IconButton`]. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub enum IconButtonShape { + Square, + Wide, +} + #[derive(IntoElement)] pub struct IconButton { base: ButtonLike, + shape: IconButtonShape, icon: IconName, icon_size: IconSize, icon_color: Color, @@ -18,6 +26,7 @@ impl IconButton { pub fn new(id: impl Into, icon: IconName) -> Self { Self { base: ButtonLike::new(id), + shape: IconButtonShape::Wide, icon, icon_size: IconSize::default(), icon_color: Color::Default, @@ -25,6 +34,11 @@ impl IconButton { } } + pub fn shape(mut self, shape: IconButtonShape) -> Self { + self.shape = shape; + self + } + pub fn icon_size(mut self, icon_size: IconSize) -> Self { self.icon_size = icon_size; self @@ -118,14 +132,21 @@ impl RenderOnce for IconButton { let is_selected = self.base.selected; let selected_style = self.base.selected_style; - self.base.child( - ButtonIcon::new(self.icon) - .disabled(is_disabled) - .selected(is_selected) - .selected_icon(self.selected_icon) - .when_some(selected_style, |this, style| this.selected_style(style)) - .size(self.icon_size) - .color(self.icon_color), - ) + self.base + .map(|this| match self.shape { + IconButtonShape::Square => this + .width(self.icon_size.rems().into()) + .height(self.icon_size.rems().into()), + IconButtonShape::Wide => this, + }) + .child( + ButtonIcon::new(self.icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) + .when_some(selected_style, |this, style| this.selected_style(style)) + .size(self.icon_size) + .color(self.icon_color), + ) } } diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index bd7b602620938775b32be3d46a41b519f2639f63..9c5c694439f74862d93e7c37155a2c340354e95b 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use gpui::Render; use story::Story; -use crate::{prelude::*, TabPosition}; +use crate::{prelude::*, IconButtonShape, TabPosition}; use crate::{Indicator, Tab}; pub struct TabStory; @@ -28,6 +28,7 @@ impl Render for TabStory { Tab::new("tab_1") .end_slot( IconButton::new("close_button", IconName::Close) + .shape(IconButtonShape::Square) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1b95671398a613a01f41590bdce20e0a846592d0..90b27e094b612a5f461fe061cb0c2a77693464e4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -32,8 +32,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator, - Label, Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName, + IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -1341,6 +1341,7 @@ impl Pane { .start_slot::(indicator) .end_slot( IconButton::new("close tab", IconName::Close) + .shape(IconButtonShape::Square) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall) From 90f4c70a82196af4f545b619cf20096c54bc0897 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 15 Jan 2024 11:34:06 -0500 Subject: [PATCH 74/98] Rename `h_stack` and `v_stack` to `h_flex` and `v_flex`, respectively (#4053) This PR renames the `h_stack` and `v_stack` to `h_flex` and `v_flex`, respectively. We were previously using `h_stack` and `v_stack` to match SwiftUI, but `h_flex` and `v_flex` fit better with the web/flexbox terminology that the rest of GPUI uses. Additionally, we were already calling the utility functions used to implement `h_stack` and `v_stack` by the new names. Release Notes: - N/A --- .../src/activity_indicator.rs | 2 +- crates/assistant/src/assistant_panel.rs | 22 +++++----- crates/auto_update/src/update_notification.rs | 6 +-- crates/breadcrumbs/src/breadcrumbs.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 14 +++---- crates/collab_ui/src/collab_panel.rs | 40 +++++++++--------- .../src/collab_panel/channel_modal.rs | 14 +++---- .../src/collab_panel/contact_finder.rs | 6 +-- crates/collab_ui/src/collab_titlebar_item.rs | 14 +++---- crates/collab_ui/src/notification_panel.rs | 18 ++++---- .../src/notifications/collab_notification.rs | 6 +-- .../incoming_call_notification.rs | 2 +- .../stories/collab_notification.rs | 2 +- crates/command_palette/src/command_palette.rs | 6 +-- crates/copilot_ui/src/sign_in.rs | 10 ++--- crates/diagnostics/src/diagnostics.rs | 16 +++---- crates/diagnostics/src/items.rs | 14 +++---- crates/editor/src/editor.rs | 10 ++--- crates/editor/src/element.rs | 12 +++--- crates/editor/src/items.rs | 4 +- crates/feedback/src/feedback_modal.rs | 10 ++--- crates/file_finder/src/file_finder.rs | 4 +- crates/go_to_line/src/go_to_line.rs | 8 ++-- .../src/language_selector.rs | 2 +- crates/language_tools/src/lsp_log.rs | 4 +- crates/language_tools/src/syntax_tree_view.rs | 4 +- crates/outline/src/outline.rs | 2 +- crates/picker/src/picker.rs | 8 ++-- crates/project_panel/src/project_panel.rs | 6 +-- crates/project_symbols/src/project_symbols.rs | 4 +- .../quick_action_bar/src/quick_action_bar.rs | 2 +- crates/recent_projects/src/recent_projects.rs | 4 +- crates/search/src/buffer_search.rs | 16 +++---- crates/search/src/project_search.rs | 42 +++++++++---------- crates/story/src/story.rs | 8 ++-- .../storybook/src/stories/overflow_scroll.rs | 4 +- crates/storybook/src/stories/text.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 4 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/theme_selector/src/theme_selector.rs | 4 +- crates/ui/src/components/button/button.rs | 4 +- crates/ui/src/components/checkbox.rs | 2 +- crates/ui/src/components/context_menu.rs | 8 ++-- crates/ui/src/components/keybinding.rs | 6 +-- crates/ui/src/components/list/list.rs | 4 +- crates/ui/src/components/list/list_header.rs | 8 ++-- crates/ui/src/components/list/list_item.rs | 12 +++--- .../ui/src/components/list/list_sub_header.rs | 4 +- crates/ui/src/components/popover.rs | 6 +-- crates/ui/src/components/stack.rs | 4 +- crates/ui/src/components/stories/checkbox.rs | 6 +-- crates/ui/src/components/stories/list_item.rs | 2 +- crates/ui/src/components/stories/tab.rs | 14 +++---- crates/ui/src/components/stories/tab_bar.rs | 2 +- .../src/components/stories/toggle_button.rs | 4 +- crates/ui/src/components/tab.rs | 6 +-- crates/ui/src/components/tab_bar.rs | 6 +-- crates/ui/src/components/tooltip.rs | 6 +-- crates/ui/src/prelude.rs | 2 +- crates/vcs_menu/src/lib.rs | 10 ++--- crates/welcome/src/base_keymap_picker.rs | 2 +- crates/welcome/src/welcome.rs | 16 +++---- crates/workspace/src/dock.rs | 4 +- crates/workspace/src/modal_layer.rs | 6 +-- crates/workspace/src/notifications.rs | 6 +-- crates/workspace/src/pane.rs | 8 ++-- crates/workspace/src/shared_screen.rs | 4 +- crates/workspace/src/status_bar.rs | 6 +-- crates/workspace/src/toolbar.rs | 10 ++--- 69 files changed, 271 insertions(+), 271 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index d04a5ab319f4c116a319a0a6f4d7b51a1de51184..ac63592d6bc21bc6ba46649eb1924d1cc5566d89 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -295,7 +295,7 @@ impl Render for ActivityIndicator { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let content = self.content_to_render(cx); - let mut result = h_stack() + let mut result = h_flex() .id("activity-indicator") .on_action(cx.listener(Self::show_error_message)) .on_action(cx.listener(Self::dismiss_error_message)); diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 2b0a0941c0f014e306d522100477b1e9a9477399..7ae4955483f42358f1eeb67c5fad5b330d228204 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1090,7 +1090,7 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { - v_stack() + v_flex() .on_action(cx.listener(AssistantPanel::save_credentials)) .track_focus(&self.focus_handle) .child(Label::new( @@ -1115,26 +1115,26 @@ impl Render for AssistantPanel { } else { let header = TabBar::new("assistant_header") .start_child( - h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title), + h_flex().gap_1().child(Self::render_hamburger_button(cx)), // .children(title), ) .children(self.active_editor().map(|editor| { - h_stack() + h_flex() .h(rems(Tab::HEIGHT_IN_REMS)) .flex_1() .px_2() .child(Label::new(editor.read(cx).title(cx)).into_element()) })) .end_child(if self.focus_handle.contains_focused(cx) { - h_stack() + h_flex() .gap_2() - .child(h_stack().gap_1().children(self.render_editor_tools(cx))) + .child(h_flex().gap_1().children(self.render_editor_tools(cx))) .child( ui::Divider::vertical() .inset() .color(ui::DividerColor::Border), ) .child( - h_stack() + h_flex() .gap_1() .child(Self::render_plus_button(cx)) .child(self.render_zoom_button(cx)), @@ -1153,7 +1153,7 @@ impl Render for AssistantPanel { } else { div() }; - v_stack() + v_flex() .key_context("AssistantPanel") .size_full() .on_action(cx.listener(|this, _: &workspace::NewFile, cx| { @@ -2530,7 +2530,7 @@ impl Render for ConversationEditor { .child(self.editor.clone()), ) .child( - h_stack() + h_flex() .absolute() .gap_1() .top_3() @@ -2616,7 +2616,7 @@ impl EventEmitter for InlineAssistant {} impl Render for InlineAssistant { fn render(&mut self, cx: &mut ViewContext) -> impl Element { let measurements = self.measurements.get(); - h_stack() + h_flex() .w_full() .py_2() .border_y_1() @@ -2628,7 +2628,7 @@ impl Render for InlineAssistant { .on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_down)) .child( - h_stack() + h_flex() .justify_center() .w(measurements.gutter_width) .child( @@ -2676,7 +2676,7 @@ impl Render for InlineAssistant { }), ) .child( - h_stack() + h_flex() .w_full() .ml(measurements.anchor_x - measurements.gutter_width) .child(self.render_prompt_editor(cx)), diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index 65f786bca4c4e1a729974459fbdf8e549a9893cc..985972da56364c646850fe7146f3323d06f56015 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -4,7 +4,7 @@ use gpui::{ }; use menu::Cancel; use util::channel::ReleaseChannel; -use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt}; +use workspace::ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt}; pub struct UpdateNotification { version: SemanticVersion, @@ -16,12 +16,12 @@ impl Render for UpdateNotification { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { let app_name = cx.global::().display_name(); - v_stack() + v_flex() .on_action(cx.listener(UpdateNotification::dismiss)) .elevation_3(cx) .p_4() .child( - h_stack() + h_flex() .justify_between() .child(Label::new(format!( "Updated to {app_name} {}", diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index e41c0c06b180cab749f7e34bb1199bd28709201b..ca07a205ac971ce5d7fd206704f7cce3b0ae7d70 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -31,7 +31,7 @@ impl EventEmitter for Breadcrumbs {} impl Render for Breadcrumbs { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let element = h_stack().text_ui(); + let element = h_flex().text_ui(); let Some(active_item) = self.active_item.as_ref() else { return element; }; @@ -51,7 +51,7 @@ impl Render for Breadcrumbs { Label::new("›").color(Color::Muted).into_any_element() }); - let breadcrumbs_stack = h_stack().gap_1().children(breadcrumbs); + let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs); match active_item .downcast::() .map(|editor| editor.downgrade()) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index c919ceac9fd679f734e3a72b2596af72d0191b56..cc271a5f5e13bec9d083d65d96974decc71967d6 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -325,13 +325,13 @@ impl ChatPanel { }; let this = cx.view().clone(); - v_stack() + v_flex() .w_full() .relative() .overflow_hidden() .when(!is_continuation_from_previous, |this| { this.pt_3().child( - h_stack() + h_flex() .child( div().absolute().child( Avatar::new(message.sender.avatar_uri.clone()) @@ -358,7 +358,7 @@ impl ChatPanel { }) .when(is_continuation_from_previous, |this| this.pt_1()) .child( - v_stack() + v_flex() .w_full() .text_ui_sm() .id(element_id) @@ -514,14 +514,14 @@ impl ChatPanel { impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .track_focus(&self.focus_handle) .full() .on_action(cx.listener(Self::send)) .child( - h_stack().z_index(1).child( + h_flex().z_index(1).child( TabBar::new("chat_header").child( - h_stack() + h_flex() .w_full() .h(rems(ui::Tab::HEIGHT_IN_REMS)) .px_2() @@ -567,7 +567,7 @@ impl Render for ChatPanel { } })) .child( - h_stack() + h_flex() .when(!self.is_scrolled_to_bottom, |el| { el.border_t_1().border_color(cx.theme().colors().border) }) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index c9e1ae1bb8a9c28afeac1434e53cab55c1fc0041..9cb447153c1bc39b9d291a2e17020cae788b43ca 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -887,7 +887,7 @@ impl CollabPanel { .ok(); })) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(is_last, false, cx)) .child(IconButton::new(0, IconName::Folder)), @@ -908,7 +908,7 @@ impl CollabPanel { ListItem::new(("screen", id)) .selected(is_selected) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(is_last, false, cx)) .child(IconButton::new(0, IconName::Screen)), @@ -949,7 +949,7 @@ impl CollabPanel { this.open_channel_notes(channel_id, cx); })) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(false, true, cx)) .child(IconButton::new(0, IconName::File)), @@ -970,7 +970,7 @@ impl CollabPanel { this.join_channel_chat(channel_id, cx); })) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(false, false, cx)) .child(IconButton::new(0, IconName::MessageBubbles)), @@ -1726,12 +1726,12 @@ impl CollabPanel { fn render_signed_out(&mut self, cx: &mut ViewContext) -> Div { let collab_blurb = "Work with your team in realtime with collaborative editing, voice, shared notes and more."; - v_stack() + v_flex() .gap_6() .p_4() .child(Label::new(collab_blurb)) .child( - v_stack() + v_flex() .gap_2() .child( Button::new("sign_in", "Sign in") @@ -1832,14 +1832,14 @@ impl CollabPanel { } fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { - v_stack() + v_flex() .size_full() .child(list(self.list_state.clone()).full()) .child( - v_stack() + v_flex() .child(div().mx_2().border_primary(cx).border_t()) .child( - v_stack() + v_flex() .p_2() .child(self.render_filter_input(&self.filter_editor, cx)), ), @@ -1961,7 +1961,7 @@ impl CollabPanel { | Section::Offline => true, }; - h_stack() + h_flex() .w_full() .group("section-header") .child( @@ -2007,7 +2007,7 @@ impl CollabPanel { .selected(is_selected) .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(github_login.clone())) @@ -2105,11 +2105,11 @@ impl CollabPanel { .indent_step_size(px(20.)) .selected(is_selected) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(github_login.clone())) - .child(h_stack().children(controls)), + .child(h_flex().children(controls)), ) .start_slot(Avatar::new(user.avatar_uri.clone())) } @@ -2149,11 +2149,11 @@ impl CollabPanel { ListItem::new(("channel-invite", channel.id as usize)) .selected(is_selected) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(channel.name.clone())) - .child(h_stack().children(controls)), + .child(h_flex().children(controls)), ) .start_slot( Icon::new(IconName::Hash) @@ -2289,21 +2289,21 @@ impl CollabPanel { .color(Color::Muted), ) .child( - h_stack() + h_flex() .id(channel_id as usize) .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.render(cx))), ), ) .child( - h_stack() + h_flex() .absolute() .right(rems(0.)) .h_full() // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. .z_index(10) .child( - h_stack() + h_flex() .h_full() .gap_1() .px_1() @@ -2410,7 +2410,7 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) -> impl Render for CollabPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .key_context("CollabPanel") .on_action(cx.listener(CollabPanel::cancel)) .on_action(cx.listener(CollabPanel::select_next)) @@ -2603,7 +2603,7 @@ struct DraggedChannelView { impl Render for DraggedChannelView { fn render(&mut self, cx: &mut ViewContext) -> impl Element { let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - h_stack() + h_flex() .font(ui_font) .bg(cx.theme().colors().background) .w(self.width) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 8020613c1ae2a4cc2ac9e0ad61293541c451f0aa..c207e31bbeaa2b087578860c09ae176827074388 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -152,19 +152,19 @@ impl Render for ChannelModal { let visibility = channel.visibility; let mode = self.picker.read(cx).delegate.mode; - v_stack() + v_flex() .key_context("ChannelModal") .on_action(cx.listener(Self::toggle_mode)) .on_action(cx.listener(Self::dismiss)) .elevation_3(cx) .w(rems(34.)) .child( - v_stack() + v_flex() .px_2() .py_1() .gap_2() .child( - h_stack() + h_flex() .w_px() .flex_1() .gap_1() @@ -172,13 +172,13 @@ impl Render for ChannelModal { .child(Label::new(channel_name)), ) .child( - h_stack() + h_flex() .w_full() .h(rems(22. / 16.)) .justify_between() .line_height(rems(1.25)) .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( @@ -212,7 +212,7 @@ impl Render for ChannelModal { ), ) .child( - h_stack() + h_flex() .child( div() .id("manage-members") @@ -391,7 +391,7 @@ impl PickerDelegate for ChannelModalDelegate { .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) - .end_slot(h_stack().gap_2().map(|slot| { + .end_slot(h_flex().gap_2().map(|slot| { match self.mode { Mode::ManageMembers => slot .children( diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index b769ec7e7f394fb7a94c38f6220b507d43e77ba1..2c59df2eb53f7fa209bca449daefc838f9662e9c 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -36,17 +36,17 @@ impl ContactFinder { impl Render for ContactFinder { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .elevation_3(cx) .child( - v_stack() + v_flex() .px_2() .py_1() .bg(cx.theme().colors().element_background) // HACK: Prevent the background color from overflowing the parent container. .rounded_t(px(8.)) .child(Label::new("Contacts")) - .child(h_stack().child(Label::new("Invite new contacts"))), + .child(h_flex().child(Label::new("Invite new contacts"))), ) .child(self.picker.clone()) .w(rems(34.)) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 03dfd450704153b31c52ec3d0baad0d215389190..e4ab39698f92118102bad10b154f8b09b0c14bc6 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -14,7 +14,7 @@ use rpc::proto; use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ - h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, + h_flex, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip, }; use util::ResultExt; @@ -58,7 +58,7 @@ impl Render for CollabTitlebarItem { let client = self.client.clone(); let project_id = self.project.read(cx).remote_id(); - h_stack() + h_flex() .id("titlebar") .justify_between() .w_full() @@ -83,7 +83,7 @@ impl Render for CollabTitlebarItem { }) // left side .child( - h_stack() + h_flex() .gap_1() .children(self.render_project_host(cx)) .child(self.render_project_name(cx)) @@ -128,7 +128,7 @@ impl Render for CollabTitlebarItem { )?; Some( - v_stack() + v_flex() .id(("collaborator", collaborator.user.id)) .child(face_pile) .child(render_color_ribbon( @@ -160,7 +160,7 @@ impl Render for CollabTitlebarItem { ) // right side .child( - h_stack() + h_flex() .gap_1() .pr_1() .when_some(room, |this, room| { @@ -634,7 +634,7 @@ impl CollabTitlebarItem { .trigger( ButtonLike::new("user-menu") .child( - h_stack() + h_flex() .gap_0p5() .child(Avatar::new(user.avatar_uri.clone())) .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), @@ -657,7 +657,7 @@ impl CollabTitlebarItem { .trigger( ButtonLike::new("user-menu") .child( - h_stack() + h_flex() .gap_0p5() .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 95473044a3f4242cd497ce0087fe1e47e8865d6f..72c7d4fe6980a856b6d5a0745d86772c87feae48 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; -use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label}; +use ui::{h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -251,13 +251,13 @@ impl NotificationPanel { .rounded_full() })) .child( - v_stack() + v_flex() .gap_1() .size_full() .overflow_hidden() .child(Label::new(text.clone())) .child( - h_stack() + h_flex() .child( Label::new(format_timestamp( timestamp, @@ -276,7 +276,7 @@ impl NotificationPanel { ))) } else if needs_response { Some( - h_stack() + h_flex() .flex_grow() .justify_end() .child(Button::new("decline", "Decline").on_click({ @@ -541,10 +541,10 @@ impl NotificationPanel { impl Render for NotificationPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .size_full() .child( - h_stack() + h_flex() .justify_between() .px_2() .py_1() @@ -558,7 +558,7 @@ impl Render for NotificationPanel { .map(|this| { if self.client.user_id().is_none() { this.child( - v_stack() + v_flex() .gap_2() .p_4() .child( @@ -592,7 +592,7 @@ impl Render for NotificationPanel { ) } else if self.notification_list.item_count() == 0 { this.child( - v_stack().p_4().child( + v_flex().p_4().child( div().flex().w_full().items_center().child( Label::new("You have no notifications.") .color(Color::Muted) @@ -711,7 +711,7 @@ impl Render for NotificationToast { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let user = self.actor.clone(); - h_stack() + h_flex() .id("notification_panel_toast") .children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) .child(Label::new(self.text.clone())) diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs index fa0b0a1b14782b8bbe586348487228d75df743f7..8157bc1318dac52710732880d565e4914d01d8de 100644 --- a/crates/collab_ui/src/notifications/collab_notification.rs +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -33,7 +33,7 @@ impl ParentElement for CollabNotification { impl RenderOnce for CollabNotification { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .text_ui() .justify_between() .size_full() @@ -42,9 +42,9 @@ impl RenderOnce for CollabNotification { .p_2() .gap_2() .child(img(self.avatar_uri).w_12().h_12().rounded_full()) - .child(v_stack().overflow_hidden().children(self.children)) + .child(v_flex().overflow_hidden().children(self.children)) .child( - v_stack() + v_flex() .child(self.accept_button) .child(self.dismiss_button), ) diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 93df9a4be5445bd67072affaaf2093e7fd2a32a1..f66194c52a0006c46c37cba2514e505c4ee5ec9b 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -137,7 +137,7 @@ impl Render for IncomingCallNotification { move |_, cx| state.respond(false, cx) }), ) - .child(v_stack().overflow_hidden().child(Label::new(format!( + .child(v_flex().overflow_hidden().child(Label::new(format!( "{} is sharing a project in Zed", self.state.call.calling_user.github_login )))), diff --git a/crates/collab_ui/src/notifications/stories/collab_notification.rs b/crates/collab_ui/src/notifications/stories/collab_notification.rs index c43cac46d21352ac8375e33cf530891385c36da0..e67ce817b69db6c4c6e0c24c2093b8cbb708e153 100644 --- a/crates/collab_ui/src/notifications/stories/collab_notification.rs +++ b/crates/collab_ui/src/notifications/stories/collab_notification.rs @@ -24,7 +24,7 @@ impl Render for CollabNotificationStory { Button::new("decline", "Decline"), ) .child( - v_stack() + v_flex() .overflow_hidden() .child(Label::new("maxdeviant is sharing a project in Zed")), ), diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index bbc2cd412305aff870f712187c4b7e13d916303d..e077426bb845dbc54efb9fc06906084946d41843 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -11,7 +11,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; -use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; +use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; use util::{ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, ResultExt, @@ -84,7 +84,7 @@ impl FocusableView for CommandPalette { impl Render for CommandPalette { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } @@ -311,7 +311,7 @@ impl PickerDelegate for CommandPaletteDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(HighlightedLabel::new( diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index ae2085aaf3e2576b65ea2d9b94fd2a4b62208fb4..f78a82699dc3c70925accc3e70a3242e9aad5061 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -57,7 +57,7 @@ impl CopilotCodeVerification { .read_from_clipboard() .map(|item| item.text() == &data.user_code) .unwrap_or(false); - h_stack() + h_flex() .w_full() .p_1() .border() @@ -90,7 +90,7 @@ impl CopilotCodeVerification { } else { "Connect to Github" }; - v_stack() + v_flex() .flex_1() .gap_2() .items_center() @@ -118,7 +118,7 @@ impl CopilotCodeVerification { ) } fn render_enabled_modal(cx: &mut ViewContext) -> impl Element { - v_stack() + v_flex() .gap_2() .child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large)) .child(Label::new( @@ -132,7 +132,7 @@ impl CopilotCodeVerification { } fn render_unauthorized_modal() -> impl Element { - v_stack() + v_flex() .child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large)) .child(Label::new( @@ -163,7 +163,7 @@ impl Render for CopilotCodeVerification { _ => div().into_any_element(), }; - v_stack() + v_flex() .id("copilot code verification") .elevation_3(cx) .w_96() diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 844a44c54f8bcf6eeae17ab3e629b4dee6e4a04b..d88a8b7c23b07b7f22cbdc35bfd88c334303e6e4 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -36,7 +36,7 @@ use std::{ }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; -use ui::{h_stack, prelude::*, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, Icon, IconName, Label}; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -654,11 +654,11 @@ impl Item for ProjectDiagnosticsEditor { }) .into_any_element() } else { - h_stack() + h_flex() .gap_1() .when(self.summary.error_count > 0, |then| { then.child( - h_stack() + h_flex() .gap_1() .child(Icon::new(IconName::XCircle).color(Color::Error)) .child(Label::new(self.summary.error_count.to_string()).color( @@ -672,7 +672,7 @@ impl Item for ProjectDiagnosticsEditor { }) .when(self.summary.warning_count > 0, |then| { then.child( - h_stack() + h_flex() .gap_1() .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning)) .child(Label::new(self.summary.warning_count.to_string()).color( @@ -796,7 +796,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let message: SharedString = message.into(); Arc::new(move |cx| { let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into(); - h_stack() + h_flex() .id("diagnostic header") .py_2() .pl_10() @@ -805,7 +805,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .justify_between() .gap_2() .child( - h_stack() + h_flex() .gap_3() .map(|stack| { stack.child( @@ -824,7 +824,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { ) }) .child( - h_stack() + h_flex() .gap_1() .child( StyledText::new(message.clone()).with_highlights( @@ -844,7 +844,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { ), ) .child( - h_stack() + h_flex() .gap_1() .when_some(diagnostic.source.as_ref(), |stack, source| { stack.child( diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 035b84e1020048cd7c6d2cd107577b7c79786169..462718c0f345268131cd04867d2bff9f48a451a0 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -6,7 +6,7 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; +use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; use crate::{Deploy, ProjectDiagnosticsEditor}; @@ -23,14 +23,14 @@ pub struct DiagnosticIndicator { impl Render for DiagnosticIndicator { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { - (0, 0) => h_stack().map(|this| { + (0, 0) => h_flex().map(|this| { this.child( Icon::new(IconName::Check) .size(IconSize::Small) .color(Color::Default), ) }), - (0, warning_count) => h_stack() + (0, warning_count) => h_flex() .gap_1() .child( Icon::new(IconName::ExclamationTriangle) @@ -38,7 +38,7 @@ impl Render for DiagnosticIndicator { .color(Color::Warning), ) .child(Label::new(warning_count.to_string()).size(LabelSize::Small)), - (error_count, 0) => h_stack() + (error_count, 0) => h_flex() .gap_1() .child( Icon::new(IconName::XCircle) @@ -46,7 +46,7 @@ impl Render for DiagnosticIndicator { .color(Color::Error), ) .child(Label::new(error_count.to_string()).size(LabelSize::Small)), - (error_count, warning_count) => h_stack() + (error_count, warning_count) => h_flex() .gap_1() .child( Icon::new(IconName::XCircle) @@ -64,7 +64,7 @@ impl Render for DiagnosticIndicator { let status = if !self.in_progress_checks.is_empty() { Some( - h_stack() + h_flex() .gap_2() .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small)) .child( @@ -91,7 +91,7 @@ impl Render for DiagnosticIndicator { None }; - h_stack() + h_flex() .h(rems(1.375)) .gap_2() .child( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 56c5be0fbd69c64824ec9635199732d224a1bd6e..01de8c20b1ea8818bcb353d5f53227b09d2c2b73 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,8 @@ use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; use ui::{ - h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, - Popover, Tooltip, + h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover, + Tooltip, }; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; @@ -1264,7 +1264,7 @@ impl CompletionsMenu { None } else { Some( - h_stack().ml_4().child( + h_flex().ml_4().child( Label::new(text.clone()) .size(LabelSize::Small) .color(Color::Muted), @@ -1290,7 +1290,7 @@ impl CompletionsMenu { ) .map(|task| task.detach_and_log_err(cx)); })) - .child(h_stack().overflow_hidden().child(completion_label)) + .child(h_flex().overflow_hidden().child(completion_label)) .end_slot::

(documentation_label), ) }) @@ -9747,7 +9747,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren let group_id: SharedString = cx.block_id.to_string().into(); // 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 - h_stack() + h_flex() .id(cx.block_id) .group(group_id.clone()) .relative() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b2295b634a1a51ed123212c8f438d6f5ebd2135d..895df153406934f9d38dca0d25994baf6f69adb6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -53,7 +53,7 @@ use std::{ use sum_tree::Bias; use theme::{ActiveTheme, PlayerColor}; use ui::prelude::*; -use ui::{h_stack, ButtonLike, ButtonStyle, IconButton, Tooltip}; +use ui::{h_flex, ButtonLike, ButtonStyle, IconButton, Tooltip}; use util::ResultExt; use workspace::item::Item; @@ -2293,7 +2293,7 @@ impl EditorElement { .size_full() .p_1p5() .child( - h_stack() + h_flex() .id("path header block") .py_1p5() .pl_3() @@ -2306,8 +2306,8 @@ impl EditorElement { .justify_between() .hover(|style| style.bg(cx.theme().colors().element_hover)) .child( - h_stack().gap_3().child( - h_stack() + h_flex().gap_3().child( + h_flex() .gap_2() .child( filename @@ -2339,12 +2339,12 @@ impl EditorElement { }), ) } else { - h_stack() + h_flex() .id(("collapsed context", block_id)) .size_full() .gap(gutter_padding) .child( - h_stack() + h_flex() .justify_end() .flex_none() .w(gutter_width - gutter_padding) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index a8583d48afae6abfee508026a65d109f78aafc95..ec9105362901b1b5fdd24206047846ef6ef3af86 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -32,7 +32,7 @@ use std::{ }; use text::Selection; use theme::Theme; -use ui::{h_stack, prelude::*, Label}; +use ui::{h_flex, prelude::*, Label}; use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt}; use workspace::{ item::{BreadcrumbText, FollowEvent, FollowableItemHandle}, @@ -619,7 +619,7 @@ impl Item for Editor { Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)) }); - h_stack() + h_flex() .gap_2() .child(Label::new(self.title(cx).to_string()).color(label_color)) .when_some(description, |this, description| { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 38e06376cb6a46795181a3498a0d048b7a968e39..80722580b7afe9c37de4e1fb7edde97f3515db76 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -422,7 +422,7 @@ impl Render for FeedbackModal { let open_community_repo = cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo))); - v_stack() + v_flex() .elevation_3(cx) .key_context("GiveFeedback") .on_action(cx.listener(Self::cancel)) @@ -461,10 +461,10 @@ impl Render for FeedbackModal { .child(self.feedback_editor.clone()), ) .child( - v_stack() + v_flex() .gap_1() .child( - h_stack() + h_flex() .bg(cx.theme().colors().editor_background) .p_2() .border() @@ -483,7 +483,7 @@ impl Render for FeedbackModal { ), ) .child( - h_stack() + h_flex() .justify_between() .gap_1() .child( @@ -495,7 +495,7 @@ impl Render for FeedbackModal { .on_click(open_community_repo), ) .child( - h_stack() + h_flex() .gap_1() .child( Button::new("cancel_feedback", "Cancel") diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index ed3e57ba28a0bfbebbcac1f8aff3482c953be276..022860ea4718dbf39717cc86c631721d5e86621b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -119,7 +119,7 @@ impl FocusableView for FileFinder { } impl Render for FileFinder { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } @@ -786,7 +786,7 @@ impl PickerDelegate for FileFinderDelegate { .inset(true) .selected(selected) .child( - v_stack() + v_flex() .child(HighlightedLabel::new(file_name, file_name_positions)) .child(HighlightedLabel::new(full_path, full_path_positions)), ), diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index ec58cbdc60556742414cc3692ef79f0658fbc750..b7e3f27fac257bab08ac1e85401cf7cb5a383dfc 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -5,7 +5,7 @@ use gpui::{ }; use text::{Bias, Point}; use theme::ActiveTheme; -use ui::{h_stack, prelude::*, v_stack, Label}; +use ui::{h_flex, prelude::*, v_flex, Label}; use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::ModalView; @@ -160,12 +160,12 @@ impl Render for GoToLine { .on_action(cx.listener(Self::confirm)) .w_96() .child( - v_stack() + v_flex() .px_1() .pt_0p5() .gap_px() .child( - v_stack() + v_flex() .py_0p5() .px_1() .child(div().px_1().py_0p5().child(self.line_editor.clone())), @@ -177,7 +177,7 @@ impl Render for GoToLine { .bg(cx.theme().colors().element_background), ) .child( - h_stack() + h_flex() .justify_between() .px_2() .py_1() diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index 33b2e481263e9fea84e77993668de16f5735726f..00ff809fc4dcd499c6d11ce27726827213f64a67 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -68,7 +68,7 @@ impl LanguageSelector { impl Render for LanguageSelector { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 62aead0857c87e6f88482991b4d7f18dae34ca59..52a1d11e74f2be6cf33123972f5c847d6d9e563e 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -785,7 +785,7 @@ impl Render for LspLogToolbarItemView { { let log_toolbar_view = log_toolbar_view.clone(); move |cx| { - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(RPC_MESSAGES)) @@ -837,7 +837,7 @@ impl Render for LspLogToolbarItemView { .into() }); - h_stack().size_full().child(lsp_menu).child( + h_flex().size_full().child(lsp_menu).child( div() .child( Button::new("clear_log_button", "Clear").on_click(cx.listener( diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index b49e4eb649ae28fea72ad2d83402653961caf0b8..bfe2b2a03be70272d62a18733fe9b655834ea99b 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -9,7 +9,7 @@ use language::{Buffer, OwnedSyntaxLayerInfo}; use std::{mem, ops::Range}; use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; -use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; +use ui::{h_flex, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; use workspace::{ item::{Item, ItemHandle}, SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -239,7 +239,7 @@ impl SyntaxTreeView { fn render_node(cursor: &TreeCursor, depth: u32, selected: bool, cx: &AppContext) -> Div { let colors = cx.theme().colors(); - let mut row = h_stack(); + let mut row = h_flex(); if let Some(field_name) = cursor.field_name() { row = row.children([Label::new(field_name).color(Color::Info), Label::new(": ")]); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index a661a693b1f5733dd27218f27ad502ea0d763c78..1f2112003974aeb95ba55202af87ec20e7e04331 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -64,7 +64,7 @@ impl ModalView for OutlineView { impl Render for OutlineView { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index ad7552052055be100bcd3d1cad9e398416387aa6..0af4675c9803505a4848049233fb02b60b308632 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -5,7 +5,7 @@ use gpui::{ View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; -use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; +use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; use workspace::ModalView; pub struct Picker { @@ -236,7 +236,7 @@ impl ModalView for Picker {} impl Render for Picker { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let picker_editor = h_stack() + let picker_editor = h_flex() .overflow_hidden() .flex_none() .h_9() @@ -264,7 +264,7 @@ impl Render for Picker { .child(Divider::horizontal()) .when(self.delegate.match_count() > 0, |el| { el.child( - v_stack() + v_flex() .flex_grow() .py_2() .max_h(self.max_height.unwrap_or(rems(18.).into())) @@ -309,7 +309,7 @@ impl Render for Picker { }) .when(self.delegate.match_count() == 0, |el| { el.child( - v_stack().flex_grow().py_2().child( + v_flex().flex_grow().py_2().child( ListItem::new("empty_state") .inset(true) .spacing(ListItemSpacing::Sparse) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 93789a7206a43d49e67e1c2fb54f3bc9c91ace50..a7b9ad2404d77686ca5743a38d59507fa1c6d671 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -30,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ThemeSettings; -use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem}; +use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1541,7 +1541,7 @@ impl Render for ProjectPanel { .child(menu.clone()) })) } else { - v_stack() + v_flex() .id("empty-project_panel") .size_full() .p_4() @@ -1565,7 +1565,7 @@ impl Render for DraggedProjectEntryView { fn render(&mut self, cx: &mut ViewContext) -> impl Element { let settings = ProjectPanelSettings::get_global(cx); let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - h_stack() + h_flex() .font(ui_font) .bg(cx.theme().colors().background) .w(self.width) diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 23dfd21b8256574bf122215cd6d34e36d8059f1f..68a0721b4c54386f31176aef44fb6a0733b02524 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -11,7 +11,7 @@ use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use theme::ActiveTheme; use util::ResultExt; use workspace::{ - ui::{v_stack, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable}, + ui::{v_flex, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable}, Workspace, }; @@ -242,7 +242,7 @@ impl PickerDelegate for ProjectSymbolsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - v_stack() + v_flex() .child( LabelLike::new().child( StyledText::new(label) diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index cf4941bcec66cdef77f3f4453a6b3eb25d8b1321..865632142a48978f33a446a49fe65c20183cf832 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -93,7 +93,7 @@ impl Render for QuickActionBar { }, ); - h_stack() + h_flex() .id("quick action bar") .gap_2() .children(inlay_hints_button) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index c3b2c21d522ed3efdbf208433e6070b2b49dcf44..6208635e22d969bfa9219d7eb4d1466a35a0999d 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -104,7 +104,7 @@ impl FocusableView for RecentProjects { impl Render for RecentProjects { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .w(rems(self.rem_width)) .child(self.picker.clone()) .on_mouse_down_out(cx.listener(|this, _, cx| { @@ -236,7 +236,7 @@ impl PickerDelegate for RecentProjectsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - v_stack() + v_flex() .child(highlighted_location.names) .when(self.render_paths, |this| { this.children(highlighted_location.paths) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 9cbe49d99ea65414a89649be17fb3c5dc196cd83..e217a7ab73cd2fd1aaa540f1b56cf13b7ec1c84b 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -21,7 +21,7 @@ use settings::Settings; use std::{any::Any, sync::Arc}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; +use ui::{h_flex, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; use util::ResultExt; use workspace::{ item::ItemHandle, @@ -186,7 +186,7 @@ impl Render for BufferSearchBar { } else { cx.theme().colors().border }; - h_stack() + h_flex() .w_full() .gap_2() .key_context(key_context) @@ -216,7 +216,7 @@ impl Render for BufferSearchBar { this.on_action(cx.listener(Self::toggle_whole_word)) }) .child( - h_stack() + h_flex() .flex_1() .px_2() .py_1() @@ -243,11 +243,11 @@ impl Render for BufferSearchBar { })), ) .child( - h_stack() + h_flex() .gap_2() .flex_none() .child( - h_stack() + h_flex() .child( ToggleButton::new("search-mode-text", SearchMode::Text.label()) .style(ButtonStyle::Filled) @@ -303,12 +303,12 @@ impl Render for BufferSearchBar { }), ) .child( - h_stack() + h_flex() .gap_0p5() .flex_1() .when(self.replace_enabled, |this| { this.child( - h_stack() + h_flex() .flex_1() // We're giving this a fixed height to match the height of the search input, // which has an icon inside that is increasing its height. @@ -346,7 +346,7 @@ impl Render for BufferSearchBar { }), ) .child( - h_stack() + h_flex() .gap_0p5() .flex_none() .child( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8897ae4bcfcd2ec2f14266dac81d97e8cf161985..d0679ed0ee2b5c929dca4911c5c2b7f1f679dfb0 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -38,7 +38,7 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, + h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, Selectable, ToggleButton, Tooltip, }; use util::{paths::PathMatcher, ResultExt as _}; @@ -360,19 +360,19 @@ impl Render for ProjectSearchView { .max_w_96() .child(Label::new(text).size(LabelSize::Small)) }); - v_stack() + v_flex() .flex_1() .size_full() .justify_center() .bg(cx.theme().colors().editor_background) .track_focus(&self.focus_handle) .child( - h_stack() + h_flex() .size_full() .justify_center() - .child(h_stack().flex_1()) - .child(v_stack().child(major_text).children(minor_text)) - .child(h_stack().flex_1()), + .child(h_flex().flex_1()) + .child(v_flex().child(major_text).children(minor_text)) + .child(h_flex().flex_1()), ) } } @@ -431,7 +431,7 @@ impl Item for ProjectSearchView { let tab_name = last_query .filter(|query| !query.is_empty()) .unwrap_or_else(|| "Project search".into()); - h_stack() + h_flex() .gap_2() .child(Icon::new(IconName::MagnifyingGlass).color(if selected { Color::Default @@ -1601,8 +1601,8 @@ impl Render for ProjectSearchBar { let search = search.read(cx); let semantic_is_available = SemanticIndex::enabled(cx); - let query_column = v_stack().child( - h_stack() + let query_column = v_flex().child( + h_flex() .min_w(rems(512. / 16.)) .px_2() .py_1() @@ -1617,7 +1617,7 @@ impl Render for ProjectSearchBar { .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&search.query_editor, cx)) .child( - h_stack() + h_flex() .child( IconButton::new("project-search-filter-button", IconName::Filter) .tooltip(|cx| { @@ -1674,11 +1674,11 @@ impl Render for ProjectSearchBar { ), ); - let mode_column = v_stack().items_start().justify_start().child( - h_stack() + let mode_column = v_flex().items_start().justify_start().child( + h_flex() .gap_2() .child( - h_stack() + h_flex() .child( ToggleButton::new("project-search-text-button", "Text") .style(ButtonStyle::Filled) @@ -1744,7 +1744,7 @@ impl Render for ProjectSearchBar { ), ); let replace_column = if search.replace_enabled { - h_stack() + h_flex() .flex_1() .h_full() .gap_2() @@ -1757,9 +1757,9 @@ impl Render for ProjectSearchBar { .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() + h_flex().flex_1() }; - let actions_column = h_stack() + let actions_column = h_flex() .when(search.replace_enabled, |this| { this.child( IconButton::new("project-search-replace-next", IconName::ReplaceNext) @@ -1820,7 +1820,7 @@ impl Render for ProjectSearchBar { .tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)), ); - v_stack() + v_flex() .key_context(key_context) .flex_grow() .gap_2() @@ -1880,7 +1880,7 @@ impl Render for ProjectSearchBar { }) }) .child( - h_stack() + h_flex() .justify_between() .gap_2() .child(query_column) @@ -1890,12 +1890,12 @@ impl Render for ProjectSearchBar { ) .when(search.filters_enabled, |this| { this.child( - h_stack() + h_flex() .flex_1() .gap_2() .justify_between() .child( - h_stack() + h_flex() .flex_1() .h_full() .px_2() @@ -1921,7 +1921,7 @@ impl Render for ProjectSearchBar { }), ) .child( - h_stack() + h_flex() .flex_1() .h_full() .px_2() diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index 65bebfc423f3812f28a31c6c42d9d6307189fcf2..f5448831cb168c240d4e8d8c038cd3f02a0bd2dd 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -255,8 +255,8 @@ impl Story { .child(label.into()) } - /// Note: Not ui::v_stack() as the story crate doesn't depend on the ui crate. - pub fn v_stack() -> Div { + /// Note: Not `ui::v_flex` as the `story` crate doesn't depend on the `ui` crate. + pub fn v_flex() -> Div { div().flex().flex_col().gap_1() } } @@ -298,7 +298,7 @@ impl RenderOnce for StoryItem { .gap_4() .w_full() .child( - Story::v_stack() + Story::v_flex() .px_2() .w_1_2() .min_h_px() @@ -319,7 +319,7 @@ impl RenderOnce for StoryItem { }), ) .child( - Story::v_stack() + Story::v_flex() .px_2() .flex_none() .w_1_2() diff --git a/crates/storybook/src/stories/overflow_scroll.rs b/crates/storybook/src/stories/overflow_scroll.rs index 32ab940be5d68fdf31681c5ae787e6a27a466a7f..ee2ea12c00e0dc9c2af38eeaa9592a378102e5d9 100644 --- a/crates/storybook/src/stories/overflow_scroll.rs +++ b/crates/storybook/src/stories/overflow_scroll.rs @@ -11,7 +11,7 @@ impl Render for OverflowScrollStory { .child(Story::title("Overflow Scroll")) .child(Story::label("`overflow_x_scroll`")) .child( - h_stack() + h_flex() .id("overflow_x_scroll") .gap_2() .overflow_x_scroll() @@ -24,7 +24,7 @@ impl Render for OverflowScrollStory { ) .child(Story::label("`overflow_y_scroll`")) .child( - v_stack() + v_flex() .id("overflow_y_scroll") .gap_2() .overflow_y_scroll() diff --git a/crates/storybook/src/stories/text.rs b/crates/storybook/src/stories/text.rs index 1c302cb48fae2d284c9f1667bde221c0ecfd3693..065b5bf795ba89fa1747c40fc1387ff00d4cb9c0 100644 --- a/crates/storybook/src/stories/text.rs +++ b/crates/storybook/src/stories/text.rs @@ -117,7 +117,7 @@ impl Render for TextStory { // type Element = Div; // fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { -// v_stack() +// v_flex() // .bg(blue()) // .child( // div() diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index dee18ea73b55f7d17c9cc742de179ed580f3d596..8954e70e8fc18d3d78775ba4eb56b11ec251c0de 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -13,7 +13,7 @@ use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::Settings; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; -use ui::{h_stack, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip}; +use ui::{h_flex, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -68,7 +68,7 @@ impl TerminalPanel { pane.display_nav_history_buttons(false); pane.set_render_tab_bar_buttons(cx, move |pane, cx| { let terminal_panel = terminal_panel.clone(); - h_stack() + h_flex() .gap_2() .child( IconButton::new("plus", IconName::Plus) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 98a04eb5f5598edab5010125da60d6a171994422..b786877608bddf1de48cdb5b0df61123d637869f 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -20,7 +20,7 @@ use terminal::{ Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, }; use terminal_element::TerminalElement; -use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -697,7 +697,7 @@ impl Item for TerminalView { cx: &WindowContext, ) -> AnyElement { let title = self.terminal().read(cx).title(true); - h_stack() + h_flex() .gap_2() .child(Icon::new(IconName::Terminal)) .child(Label::new(title).color(if selected { diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 2bb8c6648cab0ff1ef79f7f79cf5ee85da8af07c..df66c746de6a16fa51dec22086d765e6d2bfa7ff 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -10,7 +10,7 @@ use picker::{Picker, PickerDelegate}; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; -use ui::{prelude::*, v_stack, ListItem, ListItemSpacing}; +use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; @@ -70,7 +70,7 @@ impl FocusableView for ThemeSelector { impl Render for ThemeSelector { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index fcc30e633815fd764909c32bb7bbc34b8c5e0624..3ca6d2867222200e44916c3b4adf86e8ecbb9022 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -362,7 +362,7 @@ impl RenderOnce for Button { }; self.base.child( - h_stack() + h_flex() .gap_1() .when(self.icon_position == Some(IconPosition::Start), |this| { this.children(self.icon.map(|icon| { @@ -375,7 +375,7 @@ impl RenderOnce for Button { })) }) .child( - h_stack() + h_flex() .gap_2() .justify_between() .child( diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 2180e0061773f8626292224e8fb3a2b97ab4cc65..4b66c7bbeec3c898cb84f968241e92c3b0467ea7 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -103,7 +103,7 @@ impl RenderOnce for Checkbox { ), }; - h_stack() + h_flex() .id(self.id) // Rather than adding `px_1()` to add some space around the checkbox, // we use a larger parent element to create a slightly larger diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 098c54f33cb98d831c0806a2b4ec9f5b0a18ad07..5c4f110a415b2e1e8ef0ac2d36b79d7d8bc2b4be 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -1,5 +1,5 @@ use crate::{ - h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, + h_flex, prelude::*, v_flex, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader, }; use gpui::{ @@ -234,7 +234,7 @@ impl ContextMenuItem { impl Render for ContextMenu { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { div().elevation_2(cx).flex().flex_row().child( - v_stack() + v_flex() .min_w(px(200.)) .track_focus(&self.focus_handle) .on_mouse_down_out(cx.listener(|this, _, cx| this.cancel(&menu::Cancel, cx))) @@ -277,7 +277,7 @@ impl Render for ContextMenu { let menu = cx.view().downgrade(); let label_element = if let Some(icon) = icon { - h_stack() + h_flex() .gap_1() .child(Label::new(label.clone())) .child(Icon::new(*icon)) @@ -298,7 +298,7 @@ impl Render for ContextMenu { .ok(); }) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(label_element) diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index e0e0583b7cb25e4966c183ae54d9f4742c66935d..d8077f0ffca2d27cf9b8b9660fcef8bfda62173d 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Icon, IconName, IconSize}; +use crate::{h_flex, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; #[derive(IntoElement, Clone)] @@ -12,13 +12,13 @@ pub struct KeyBinding { impl RenderOnce for KeyBinding { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .flex_none() .gap_2() .children(self.key_binding.keystrokes().iter().map(|keystroke| { let key_icon = Self::icon_for_key(&keystroke); - h_stack() + h_flex() .flex_none() .gap_0p5() .p_0p5() diff --git a/crates/ui/src/components/list/list.rs b/crates/ui/src/components/list/list.rs index 8c657fdd92a4d35891e2cdae30193ac147741907..436f3e034d17a757e9598fff0584d7239d6f4d65 100644 --- a/crates/ui/src/components/list/list.rs +++ b/crates/ui/src/components/list/list.rs @@ -1,7 +1,7 @@ use gpui::AnyElement; use smallvec::SmallVec; -use crate::{prelude::*, v_stack, Label, ListHeader}; +use crate::{prelude::*, v_flex, Label, ListHeader}; #[derive(IntoElement)] pub struct List { @@ -47,7 +47,7 @@ impl ParentElement for List { impl RenderOnce for List { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - v_stack().w_full().py_1().children(self.header).map(|this| { + v_flex().w_full().py_1().children(self.header).map(|this| { match (self.children.is_empty(), self.toggle) { (false, _) => this.children(self.children), (true, Some(false)) => this, diff --git a/crates/ui/src/components/list/list_header.rs b/crates/ui/src/components/list/list_header.rs index 1bed11601512348c7227b7e048468946145ffe41..7d47f4d3934fd7c7b68ae3728b3b76c6abeb8971 100644 --- a/crates/ui/src/components/list/list_header.rs +++ b/crates/ui/src/components/list/list_header.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Disclosure, Label}; +use crate::{h_flex, prelude::*, Disclosure, Label}; use gpui::{AnyElement, ClickEvent}; #[derive(IntoElement)] @@ -76,7 +76,7 @@ impl Selectable for ListHeader { impl RenderOnce for ListHeader { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .id(self.label.clone()) .w_full() .relative() @@ -95,7 +95,7 @@ impl RenderOnce for ListHeader { .w_full() .gap_1() .child( - h_stack() + h_flex() .gap_1() .children(self.toggle.map(|is_open| { Disclosure::new("toggle", is_open).on_toggle(self.on_toggle) @@ -109,7 +109,7 @@ impl RenderOnce for ListHeader { .child(Label::new(self.label.clone()).color(Color::Muted)), ), ) - .child(h_stack().children(self.end_slot)) + .child(h_flex().children(self.end_slot)) .when_some(self.end_hover_slot, |this, end_hover_slot| { this.child( div() diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index d43de18f9317e9f17d303294f1285d2d1c954b42..804e5191abedb4f2b4f6fa79a739023554817f12 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -146,7 +146,7 @@ impl ParentElement for ListItem { impl RenderOnce for ListItem { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .id(self.id) .w_full() .relative() @@ -169,7 +169,7 @@ impl RenderOnce for ListItem { }) }) .child( - h_stack() + h_flex() .id("inner_list_item") .w_full() .relative() @@ -219,9 +219,9 @@ impl RenderOnce for ListItem { .child(Disclosure::new("toggle", is_open).on_toggle(self.on_toggle)) })) .child( - h_stack() + h_flex() // HACK: We need to set *any* width value here in order for this container to size correctly. - // Without this the `h_stack` will overflow the parent `inner_list_item`. + // Without this the `h_flex` will overflow the parent `inner_list_item`. .w_px() .flex_1() .gap_1() @@ -230,7 +230,7 @@ impl RenderOnce for ListItem { ) .when_some(self.end_slot, |this, end_slot| { this.justify_between().child( - h_stack() + h_flex() .when(self.end_hover_slot.is_some(), |this| { this.visible() .group_hover("list_item", |this| this.invisible()) @@ -240,7 +240,7 @@ impl RenderOnce for ListItem { }) .when_some(self.end_hover_slot, |this, end_hover_slot| { this.child( - h_stack() + h_flex() .h_full() .absolute() .right_2() diff --git a/crates/ui/src/components/list/list_sub_header.rs b/crates/ui/src/components/list/list_sub_header.rs index fc9f35e175c0d42a1517fc1227a4befc7dfdb2da..e607dcaaa896d87e36594178166922a4d92c26d5 100644 --- a/crates/ui/src/components/list/list_sub_header.rs +++ b/crates/ui/src/components/list/list_sub_header.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::{h_stack, Icon, IconName, IconSize, Label}; +use crate::{h_flex, Icon, IconName, IconSize, Label}; #[derive(IntoElement)] pub struct ListSubHeader { @@ -25,7 +25,7 @@ impl ListSubHeader { impl RenderOnce for ListSubHeader { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - h_stack().flex_1().w_full().relative().py_1().child( + h_flex().flex_1().w_full().relative().py_1().child( div() .h_6() .when(self.inset, |this| this.px_2()) diff --git a/crates/ui/src/components/popover.rs b/crates/ui/src/components/popover.rs index acab1e2087667092f20035740d868604528a3935..2e0c5bfec87b84f820bf3bdb512053460e90360f 100644 --- a/crates/ui/src/components/popover.rs +++ b/crates/ui/src/components/popover.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::v_stack; +use crate::v_flex; use gpui::{ div, AnyElement, Element, IntoElement, ParentElement, RenderOnce, Styled, WindowContext, }; @@ -43,10 +43,10 @@ impl RenderOnce for Popover { div() .flex() .gap_1() - .child(v_stack().elevation_2(cx).px_1().children(self.children)) + .child(v_flex().elevation_2(cx).px_1().children(self.children)) .when_some(self.aside, |this, aside| { this.child( - v_stack() + v_flex() .elevation_2(cx) .bg(cx.theme().colors().surface_background) .px_1() diff --git a/crates/ui/src/components/stack.rs b/crates/ui/src/components/stack.rs index 76f08de911e8e1e60a67c1d9311355f62d55914b..74a5e80575bfe0ebe28334f4533f12b91b1fcfb2 100644 --- a/crates/ui/src/components/stack.rs +++ b/crates/ui/src/components/stack.rs @@ -4,12 +4,12 @@ use crate::StyledExt; /// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()` #[track_caller] -pub fn h_stack() -> Div { +pub fn h_flex() -> Div { div().h_flex() } /// Vertically stacks elements. Sets `flex()`, `flex_col()` #[track_caller] -pub fn v_stack() -> Div { +pub fn v_flex() -> Div { div().v_flex() } diff --git a/crates/ui/src/components/stories/checkbox.rs b/crates/ui/src/components/stories/checkbox.rs index a064c673935a31bc74878833aca22eaff611dfde..b4a966b67e3ae287c604068ec3854c5cf8373a49 100644 --- a/crates/ui/src/components/stories/checkbox.rs +++ b/crates/ui/src/components/stories/checkbox.rs @@ -2,7 +2,7 @@ use gpui::{Render, ViewContext}; use story::Story; use crate::prelude::*; -use crate::{h_stack, Checkbox}; +use crate::{h_flex, Checkbox}; pub struct CheckboxStory; @@ -12,7 +12,7 @@ impl Render for CheckboxStory { .child(Story::title_for::()) .child(Story::label("Default")) .child( - h_stack() + h_flex() .p_2() .gap_2() .rounded_md() @@ -27,7 +27,7 @@ impl Render for CheckboxStory { ) .child(Story::label("Disabled")) .child( - h_stack() + h_flex() .p_2() .gap_2() .rounded_md() diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index a25b07df849aeb67a185e3984ab11ec341b07045..f2af011db8e8554b07434e699b61c8492040ede8 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -60,7 +60,7 @@ impl Render for ListItemStory { ListItem::new("with_end_hover_slot") .child("Hello, world!") .end_slot( - h_stack() + h_flex() .gap_2() .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index 9c5c694439f74862d93e7c37155a2c340354e95b..541af75ba4762ef60890d1f21e4ca101680b59a1 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -13,10 +13,10 @@ impl Render for TabStory { Story::container() .child(Story::title_for::()) .child(Story::label("Default")) - .child(h_stack().child(Tab::new("tab_1").child("Tab 1"))) + .child(h_flex().child(Tab::new("tab_1").child("Tab 1"))) .child(Story::label("With indicator")) .child( - h_stack().child( + h_flex().child( Tab::new("tab_1") .start_slot(Indicator::dot().color(Color::Warning)) .child("Tab 1"), @@ -24,7 +24,7 @@ impl Render for TabStory { ) .child(Story::label("With close button")) .child( - h_stack().child( + h_flex().child( Tab::new("tab_1") .end_slot( IconButton::new("close_button", IconName::Close) @@ -38,13 +38,13 @@ impl Render for TabStory { ) .child(Story::label("List of tabs")) .child( - h_stack() + h_flex() .child(Tab::new("tab_1").child("Tab 1")) .child(Tab::new("tab_2").child("Tab 2")), ) .child(Story::label("List of tabs with first tab selected")) .child( - h_stack() + h_flex() .child( Tab::new("tab_1") .selected(true) @@ -65,7 +65,7 @@ impl Render for TabStory { ) .child(Story::label("List of tabs with last tab selected")) .child( - h_stack() + h_flex() .child( Tab::new("tab_1") .position(TabPosition::First) @@ -90,7 +90,7 @@ impl Render for TabStory { ) .child(Story::label("List of tabs with second tab selected")) .child( - h_stack() + h_flex() .child( Tab::new("tab_1") .position(TabPosition::First) diff --git a/crates/ui/src/components/stories/tab_bar.rs b/crates/ui/src/components/stories/tab_bar.rs index 289ceff9a6f576739daacd02b57e260b295a7ae8..d6d42fa5e0f3c3ee5540bbd6cdbf4c21ec7edda4 100644 --- a/crates/ui/src/components/stories/tab_bar.rs +++ b/crates/ui/src/components/stories/tab_bar.rs @@ -35,7 +35,7 @@ impl Render for TabBarStory { .child(Story::title_for::()) .child(Story::label("Default")) .child( - h_stack().child( + h_flex().child( TabBar::new("tab_bar_1") .start_child( IconButton::new("navigate_backward", IconName::ArrowLeft) diff --git a/crates/ui/src/components/stories/toggle_button.rs b/crates/ui/src/components/stories/toggle_button.rs index 518165345ca8b483f7c299a22aae4cc91566b839..da2a2512c44a7705d817a12a25b6355f7f792c04 100644 --- a/crates/ui/src/components/stories/toggle_button.rs +++ b/crates/ui/src/components/stories/toggle_button.rs @@ -25,7 +25,7 @@ impl Render for ToggleButtonStory { StorySection::new().child( StoryItem::new( "Toggle button group", - h_stack() + h_flex() .child( ToggleButton::new(1, "Apple") .style(ButtonStyle::Filled) @@ -59,7 +59,7 @@ impl Render for ToggleButtonStory { StorySection::new().child( StoryItem::new( "Toggle button group with selection", - h_stack() + h_flex() .child( ToggleButton::new(1, "Apple") .style(ButtonStyle::Filled) diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 351c851bb9088db8907e7defff71fe21d79ffe75..7538d809ef3a9ead6a3facef51f6cba675a118cc 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -135,7 +135,7 @@ impl RenderOnce for Tab { }) .cursor_pointer() .child( - h_stack() + h_flex() .group("") .relative() .h_full() @@ -145,7 +145,7 @@ impl RenderOnce for Tab { // .hover(|style| style.bg(tab_hover_bg)) // .active(|style| style.bg(tab_active_bg)) .child( - h_stack() + h_flex() .w_3() .h_3() .justify_center() @@ -157,7 +157,7 @@ impl RenderOnce for Tab { .children(self.start_slot), ) .child( - h_stack() + h_flex() .w_3() .h_3() .justify_center() diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 0a86f1ae0ce52713bde0761340a86c6d8676a0ec..1f7179c393b686f0a7c7c4a6a49179ea2a609f87 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -102,7 +102,7 @@ impl RenderOnce for TabBar { .bg(cx.theme().colors().tab_bar_background) .when(!self.start_children.is_empty(), |this| { this.child( - h_stack() + h_flex() .flex_none() .gap_1() .px_1() @@ -129,7 +129,7 @@ impl RenderOnce for TabBar { .border_color(cx.theme().colors().border), ) .child( - h_stack() + h_flex() .id("tabs") .z_index(2) .flex_grow() @@ -142,7 +142,7 @@ impl RenderOnce for TabBar { ) .when(!self.end_children.is_empty(), |this| { this.child( - h_stack() + h_flex() .flex_none() .gap_1() .px_1() diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index 77fd8d6c0b109cac05bd559fce71529436b7a825..f76085daa35ccfa37131b44bca2d83b9532cffb5 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -3,7 +3,7 @@ use settings::Settings; use theme::ThemeSettings; use crate::prelude::*; -use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt}; +use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt}; pub struct Tooltip { title: SharedString, @@ -73,7 +73,7 @@ impl Render for Tooltip { overlay().child( // padding to avoid mouse cursor div().pl_2().pt_2p5().child( - v_stack() + v_flex() .elevation_2(cx) .font(ui_font) .text_ui() @@ -81,7 +81,7 @@ impl Render for Tooltip { .py_1() .px_2() .child( - h_stack() + h_flex() .gap_4() .child(self.title.clone()) .when_some(self.key_binding.clone(), |this, key_binding| { diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 69d1d0583d70e8176f577ce7d7fa651845df82f6..837d93db2d20017742a618202cd41eb89075ce6d 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -13,7 +13,7 @@ pub use crate::fixed::*; pub use crate::selectable::*; pub use crate::styles::{vh, vw}; pub use crate::visible_on_hover::*; -pub use crate::{h_stack, v_stack}; +pub use crate::{h_flex, v_flex}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{Headline, HeadlineSize}; diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 0774c6f5755324de77b16f691ee0dde11ed38f16..67a12a852bdaeeae08f5fbcdc41ab11a91572605 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -9,7 +9,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use std::{ops::Not, sync::Arc}; use ui::{ - h_stack, v_stack, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon, + h_flex, v_flex, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon, LabelSize, ListItem, ListItemSpacing, Selectable, }; use util::ResultExt; @@ -65,7 +65,7 @@ impl FocusableView for BranchList { impl Render for BranchList { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .w(rems(self.rem_width)) .child(self.picker.clone()) .on_mouse_down_out(cx.listener(|this, _, cx| { @@ -290,7 +290,7 @@ impl PickerDelegate for BranchListDelegate { } fn render_header(&self, _: &mut ViewContext>) -> Option { let label = if self.last_query.is_empty() { - h_stack() + h_flex() .ml_3() .child(Label::new("Recent branches").size(LabelSize::Small)) } else { @@ -298,7 +298,7 @@ impl PickerDelegate for BranchListDelegate { let suffix = if self.matches.len() == 1 { "" } else { "es" }; Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small) }); - h_stack() + h_flex() .px_3() .h_full() .justify_between() @@ -313,7 +313,7 @@ impl PickerDelegate for BranchListDelegate { } Some( - h_stack().mr_3().pb_2().child(h_stack().w_full()).child( + h_flex().mr_3().pb_2().child(h_flex().w_full()).child( Button::new("branch-picker-create-branch-button", "Create branch").on_click( cx.listener(|_, _, cx| { cx.spawn(|picker, mut cx| async move { diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index e22c89cef882d6e1977e2e349f49f5f4d98c14a1..7913e4df37a39140764722c5f9554a247aef55ec 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -62,7 +62,7 @@ impl BaseKeymapSelector { impl Render for BaseKeymapSelector { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 76988fadb06b9124f1b197178cb0c89106670f7a..cefedeb73f52d010e9002b644662696fa479abf2 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -60,8 +60,8 @@ pub struct WelcomePage { impl Render for WelcomePage { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { - h_stack().full().track_focus(&self.focus_handle).child( - v_stack() + h_flex().full().track_focus(&self.focus_handle).child( + v_flex() .w_96() .gap_4() .mx_auto() @@ -74,12 +74,12 @@ impl Render for WelcomePage { .mx_auto(), ) .child( - h_stack() + h_flex() .justify_center() .child(Label::new("Code at the speed of thought")), ) .child( - v_stack() + v_flex() .gap_2() .child( Button::new("choose-theme", "Choose a theme") @@ -129,7 +129,7 @@ impl Render for WelcomePage { ), ) .child( - v_stack() + v_flex() .p_3() .gap_2() .bg(cx.theme().colors().elevated_surface_background) @@ -137,7 +137,7 @@ impl Render for WelcomePage { .border_color(cx.theme().colors().border) .rounded_md() .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( @@ -163,7 +163,7 @@ impl Render for WelcomePage { .child(Label::new("Enable vim mode")), ) .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( @@ -201,7 +201,7 @@ impl Render for WelcomePage { .child(Label::new("Send anonymous usage data")), ) .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index faf69f396d7807376f84a97d10e4697f56fb1ab1..4ae408993572e06e97635f8fb579fbee1395238b 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,7 +9,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::SettingsStore; use std::sync::Arc; -use ui::{h_stack, ContextMenu, IconButton, Tooltip}; +use ui::{h_flex, ContextMenu, IconButton, Tooltip}; use ui::{prelude::*, right_click_menu}; const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.); @@ -682,7 +682,7 @@ impl Render for PanelButtons { ) }); - h_stack().gap_0p5().children(buttons) + h_flex().gap_0p5().children(buttons) } } diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index 627581c4760c0209de3379d5a2cbf0ead7cbbc62..d940f1d16842a712bb8aaef23281c5e8ab06217f 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -2,7 +2,7 @@ use gpui::{ div, prelude::*, px, AnyView, DismissEvent, FocusHandle, ManagedView, Render, Subscription, View, ViewContext, WindowContext, }; -use ui::{h_stack, v_stack}; +use ui::{h_flex, v_flex}; pub trait ModalView: ManagedView { fn on_before_dismiss(&mut self, _: &mut ViewContext) -> bool { @@ -120,7 +120,7 @@ impl Render for ModalLayer { .left_0() .z_index(169) .child( - v_stack() + v_flex() .h(px(0.0)) .top_20() .flex() @@ -128,7 +128,7 @@ impl Render for ModalLayer { .items_center() .track_focus(&active_modal.focus_handle) .child( - h_stack() + h_flex() .on_mouse_down_out(cx.listener(|this, _, cx| { this.hide_modal(cx); })) diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index cc2450587d7304a8491aa59137cac9ae758da5ee..6e7590c7d3886493f00d2b43728326549e5665f8 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -173,7 +173,7 @@ pub mod simple_message_notification { }; use std::sync::Arc; use ui::prelude::*; - use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt}; + use ui::{h_flex, v_flex, Button, Icon, IconName, Label, StyledExt}; pub struct MessageNotification { message: SharedString, @@ -218,11 +218,11 @@ pub mod simple_message_notification { impl Render for MessageNotification { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .elevation_3(cx) .p_4() .child( - h_stack() + h_flex() .justify_between() .child(div().max_w_80().child(Label::new(self.message.clone()))) .child( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 90b27e094b612a5f461fe061cb0c2a77693464e4..70cf84811ebff65245e975052b989b809f18b1f1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -35,7 +35,7 @@ use ui::{ prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName, IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip, }; -use ui::{v_stack, ContextMenu}; +use ui::{v_flex, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -271,7 +271,7 @@ impl Pane { custom_drop_handle: None, can_split: true, render_tab_bar_buttons: Rc::new(move |pane, cx| { - h_stack() + h_flex() .gap_2() .child( IconButton::new("plus", IconName::Plus) @@ -1444,7 +1444,7 @@ impl Pane { .track_scroll(self.tab_bar_scroll_handle.clone()) .when(self.display_nav_history_buttons, |tab_bar| { tab_bar.start_child( - h_stack() + h_flex() .gap_2() .child( IconButton::new("navigate_backward", IconName::ArrowLeft) @@ -1718,7 +1718,7 @@ impl FocusableView for Pane { impl Render for Pane { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .key_context("Pane") .track_focus(&self.focus_handle) .size_full() diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 5b1ca6477ee99b47ad7abc08e64e0421444c5dfd..6fb156c22f8846857494ea2a52d53c638b0f3ab3 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,7 @@ use gpui::{ WindowContext, }; use std::sync::{Arc, Weak}; -use ui::{h_stack, prelude::*, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, Icon, IconName, Label}; pub enum Event { Close, @@ -98,7 +98,7 @@ impl Item for SharedScreen { selected: bool, _: &WindowContext<'_>, ) -> gpui::AnyElement { - h_stack() + h_flex() .gap_1() .child(Icon::new(IconName::Screen)) .child( diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index bfa1a8f8ba0f39e382b02aa9735720719fdcfbfb..34a1412533bf14829e193df680085bc50c1166c2 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -4,7 +4,7 @@ use gpui::{ WindowContext, }; use std::any::TypeId; -use ui::{h_stack, prelude::*}; +use ui::{h_flex, prelude::*}; use util::ResultExt; pub trait StatusItemView: Render { @@ -50,14 +50,14 @@ impl Render for StatusBar { impl StatusBar { fn render_left_tools(&self, _: &mut ViewContext) -> impl IntoElement { - h_stack() + h_flex() .items_center() .gap_2() .children(self.left_items.iter().map(|item| item.to_any())) } fn render_right_tools(&self, _: &mut ViewContext) -> impl IntoElement { - h_stack() + h_flex() .items_center() .gap_2() .children(self.right_items.iter().rev().map(|item| item.to_any())) diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index cc072b08b9eac0cf13039dd45216483018d5701a..3d5df3294e1fc84faab10c69c46c7ce424682aad 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -4,7 +4,7 @@ use gpui::{ WindowContext, }; use ui::prelude::*; -use ui::{h_stack, v_stack}; +use ui::{h_flex, v_flex}; pub enum ToolbarItemEvent { ChangeLocation(ToolbarItemLocation), @@ -103,18 +103,18 @@ impl Render for Toolbar { let has_left_items = self.left_items().count() > 0; let has_right_items = self.right_items().count() > 0; - v_stack() + v_flex() .p_2() .when(has_left_items || has_right_items, |this| this.gap_2()) .border_b() .border_color(cx.theme().colors().border_variant) .bg(cx.theme().colors().toolbar_background) .child( - h_stack() + h_flex() .justify_between() .when(has_left_items, |this| { this.child( - h_stack() + h_flex() .flex_1() .justify_start() .children(self.left_items().map(|item| item.to_any())), @@ -122,7 +122,7 @@ impl Render for Toolbar { }) .when(has_right_items, |this| { this.child( - h_stack() + h_flex() .flex_1() .justify_end() .children(self.right_items().map(|item| item.to_any())), From b9be2147e8846d7abc630b54c1300cd1f75a4c57 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 15 Jan 2024 18:11:01 +0100 Subject: [PATCH 75/98] Fix unaligned close button in tab bar Co-authored-by: Marshall --- crates/assistant/src/assistant_panel.rs | 2 +- crates/collab_ui/src/chat_panel.rs | 2 +- crates/collab_ui/src/notification_panel.rs | 2 +- crates/ui/src/components/tab.rs | 8 +++++--- crates/ui/src/components/tab_bar.rs | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7ae4955483f42358f1eeb67c5fad5b330d228204..df3dc3754f66aff8d83a6fcd3b92edd38c7c4e45 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1119,7 +1119,7 @@ impl Render for AssistantPanel { ) .children(self.active_editor().map(|editor| { h_flex() - .h(rems(Tab::HEIGHT_IN_REMS)) + .h(rems(Tab::CONTAINER_HEIGHT_IN_REMS)) .flex_1() .px_2() .child(Label::new(editor.read(cx).title(cx)).into_element()) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index cc271a5f5e13bec9d083d65d96974decc71967d6..921e3388a65637fad51f9a093f8538500965923a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -523,7 +523,7 @@ impl Render for ChatPanel { TabBar::new("chat_header").child( h_flex() .w_full() - .h(rems(ui::Tab::HEIGHT_IN_REMS)) + .h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS)) .px_2() .child(Label::new( self.active_chat diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 72c7d4fe6980a856b6d5a0745d86772c87feae48..b30f8d15f035b5bc49e08449d20efd21b0e5b8c9 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -549,7 +549,7 @@ impl Render for NotificationPanel { .px_2() .py_1() // Match the height of the tab bar so they line up. - .h(rems(ui::Tab::HEIGHT_IN_REMS)) + .h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS)) .border_b_1() .border_color(cx.theme().colors().border) .child(Label::new("Notifications")) diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 7538d809ef3a9ead6a3facef51f6cba675a118cc..ade939fdaabcddfcdc6ac0a22b0222ff4f2b4170 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -48,7 +48,9 @@ impl Tab { } } - pub const HEIGHT_IN_REMS: f32 = 30. / 16.; + pub const CONTAINER_HEIGHT_IN_REMS: f32 = 29. / 16.; + + const CONTENT_HEIGHT_IN_REMS: f32 = 28. / 16.; pub fn position(mut self, position: TabPosition) -> Self { self.position = position; @@ -111,7 +113,7 @@ impl RenderOnce for Tab { }; self.div - .h(rems(Self::HEIGHT_IN_REMS)) + .h(rems(Self::CONTAINER_HEIGHT_IN_REMS)) .bg(tab_bg) .border_color(cx.theme().colors().border) .map(|this| match self.position { @@ -138,7 +140,7 @@ impl RenderOnce for Tab { h_flex() .group("") .relative() - .h_full() + .h(rems(Self::CONTENT_HEIGHT_IN_REMS)) .px_5() .gap_1() .text_color(text_color) diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 1f7179c393b686f0a7c7c4a6a49179ea2a609f87..adee8389e47a8db3f343e01cc9d81a9c1d08ead4 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -90,7 +90,7 @@ impl ParentElement for TabBar { impl RenderOnce for TabBar { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - const HEIGHT_IN_REMS: f32 = 30. / 16.; + const HEIGHT_IN_REMS: f32 = 29. / 16.; div() .id(self.id) From 69bbcba99a627d3d3067e20a164367d9bf58129f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 19:19:27 +0100 Subject: [PATCH 76/98] Preserve tooltips requested by cached views Co-Authored-By: Nathan Co-Authored-By: Max --- crates/gpui/src/app.rs | 6 ++-- crates/gpui/src/elements/div.rs | 4 +-- crates/gpui/src/key_dispatch.rs | 2 +- crates/gpui/src/window.rs | 56 +++++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 108ad28d24a16191b6a419d09b9d56bf212c4050..17f92efb581f3aed55becda1100316f041bdd308 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -196,7 +196,6 @@ pub struct AppContext { pending_updates: usize, pub(crate) actions: Rc, pub(crate) active_drag: Option, - pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: FxHashMap>, pub(crate) frame_consumers: FxHashMap>, pub(crate) background_executor: BackgroundExecutor, @@ -258,7 +257,6 @@ impl AppContext { flushing_effects: false, pending_updates: 0, active_drag: None, - active_tooltip: None, next_frame_callbacks: FxHashMap::default(), frame_consumers: FxHashMap::default(), background_executor: executor, @@ -1268,8 +1266,10 @@ pub struct AnyDrag { pub cursor_offset: Point, } +/// Contains state associated with a tooltip. You'll only need this struct if you're implementing +/// tooltip behavior on a custom element. Otherwise, use [Div::tooltip]. #[derive(Clone)] -pub(crate) struct AnyTooltip { +pub struct AnyTooltip { pub view: AnyView, pub cursor_offset: Point, } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 082f88b8e6775382993922cc6fc72e4f14a2f13d..74000da0512ffac35e4cf87c122bdecf08d67aed 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1433,8 +1433,8 @@ impl Interactivity { .borrow() .as_ref() { - if active_tooltip.tooltip.is_some() { - cx.active_tooltip = active_tooltip.tooltip.clone() + if let Some(tooltip) = active_tooltip.tooltip.clone() { + cx.set_tooltip(tooltip); } } } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 06d502d7780c9c59dc180d3508d7e4074009dbb6..85a67168e5e1d89588b6d58088a8f381a195e0eb 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -112,7 +112,7 @@ impl DispatchTree { target.action_listeners = mem::take(&mut source.action_listeners); } - pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { + pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { let view_source_node_id = source .view_node_ids .get(&view_id) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8e38992251f5da196038f2718060d4f12a279496..2a88a4f3976646845589b5127e90c78bde6eb8c0 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,10 +1,10 @@ #![deny(missing_docs)] use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, - AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, - DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + px, size, transparent_black, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Arena, + AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, + DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, + Entity, EntityId, 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, @@ -300,6 +300,11 @@ struct RequestedInputHandler { handler: Option>, } +struct TooltipRequest { + view_id: EntityId, + tooltip: AnyTooltip, +} + pub(crate) struct Frame { focus: Option, window_active: bool, @@ -313,6 +318,7 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, + tooltip_request: Option, cursor_styles: FxHashMap, requested_cursor_style: Option, pub(crate) view_stack: Vec, @@ -328,12 +334,13 @@ impl Frame { mouse_listeners: FxHashMap::default(), dispatch_tree, scene: Scene::default(), + depth_map: Vec::new(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, - depth_map: Vec::new(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, + tooltip_request: None, cursor_styles: FxHashMap::default(), requested_cursor_style: None, view_stack: Vec::new(), @@ -350,6 +357,7 @@ impl Frame { self.reused_views.clear(); self.scene.clear(); self.requested_input_handler.take(); + self.tooltip_request.take(); self.cursor_styles.clear(); self.requested_cursor_style.take(); debug_assert_eq!(self.view_stack.len(), 0); @@ -1052,6 +1060,12 @@ impl<'a> WindowContext<'a> { self.window.next_frame.requested_cursor_style = Some(style); } + /// Set a tooltip to be rendered for the upcoming frame + pub fn set_tooltip(&mut self, tooltip: AnyTooltip) { + let view_id = self.parent_view_id(); + self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip }); + } + /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); @@ -1432,12 +1446,11 @@ impl<'a> WindowContext<'a> { .window .next_frame .dispatch_tree - .graft(view_id, &mut self.window.rendered_frame.dispatch_tree); + .reuse_view(view_id, &mut self.window.rendered_frame.dispatch_tree); for view_id in grafted_view_ids { assert!(self.window.next_frame.reused_views.insert(view_id)); - // Reuse the previous input handler if it was associated with one of - // the views grafted from the tree in the previous frame. + // Reuse the previous input handler requested during painting of the reused view. if self .window .rendered_frame @@ -1449,6 +1462,19 @@ impl<'a> WindowContext<'a> { self.window.rendered_frame.requested_input_handler.take(); } + // Reuse the tooltip previously requested during painting of the reused view. + if self + .window + .rendered_frame + .tooltip_request + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.tooltip_request = + self.window.rendered_frame.tooltip_request.take(); + } + + // Reuse the cursor styles previously requested during painting of the reused view. if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { self.window.next_frame.cursor_styles.insert(view_id, style); self.window.next_frame.requested_cursor_style = Some(style); @@ -1498,13 +1524,16 @@ impl<'a> WindowContext<'a> { active_drag.view.draw(offset, available_space, cx); }); self.active_drag = Some(active_drag); - } else if let Some(active_tooltip) = self.app.active_tooltip.take() { + } else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() { self.with_z_index(1, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_tooltip - .view - .draw(active_tooltip.cursor_offset, available_space, cx); + tooltip_request.tooltip.view.draw( + tooltip_request.tooltip.cursor_offset, + available_space, + cx, + ); }); + self.window.next_frame.tooltip_request = Some(tooltip_request); } self.window.dirty_views.clear(); @@ -2145,7 +2174,8 @@ impl<'a> WindowContext<'a> { /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the /// platform to receive textual input with proper integration with concerns such - /// as IME interactions. + /// as IME interactions. This handler will be active for the upcoming frame until the following frame is + /// rendered. /// /// [element_input_handler]: crate::ElementInputHandler pub fn handle_input( From a56265e6073f35cba7b7a3add5e4c8ecabf0e32a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 19:27:18 +0100 Subject: [PATCH 77/98] Avoid retrieving layout bounds inside of right click menu event handler Co-Authored-By: Nathan Co-Authored-By: Max --- crates/ui/src/components/right_click_menu.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 2404368f250db3329613e0337c577c3a9512076f..55cdd93a5bee9d4be4c3f293a262b868b6b7825a 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -134,6 +134,7 @@ impl Element for RightClickMenu { let position = element_state.position.clone(); let attach = self.attach.clone(); let child_layout_id = element_state.child_layout_id.clone(); + let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble @@ -161,9 +162,7 @@ impl Element for RightClickMenu { *menu.borrow_mut() = Some(new_menu); *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { - attach - .unwrap() - .corner(cx.layout_bounds(child_layout_id.unwrap())) + attach.unwrap().corner(child_bounds) } else { cx.mouse_position() }; From e60117dc541cd5d191c8ac00e01a0a50894fdc3b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 19:46:34 +0100 Subject: [PATCH 78/98] Avoid panicking when closing a dragged tab Co-Authored-By: Max Co-Authored-By: Nathan --- crates/workspace/src/pane.rs | 9 ++++----- crates/workspace/src/workspace.rs | 13 ++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 70cf84811ebff65245e975052b989b809f18b1f1..655acc29c004e5750a7d949ab1f01088ee04ad58 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -219,8 +219,8 @@ pub struct NavigationEntry { #[derive(Clone)] pub struct DraggedTab { pub pane: View, + pub item: Box, pub ix: usize, - pub item_id: EntityId, pub detail: usize, pub is_active: bool, } @@ -1310,9 +1310,9 @@ impl Pane { ) .on_drag( DraggedTab { + item: item.boxed_clone(), pane: cx.view().clone(), detail, - item_id, is_active, ix, }, @@ -1603,7 +1603,7 @@ impl Pane { } let mut to_pane = cx.view().clone(); let split_direction = self.drag_split_direction; - let item_id = dragged_tab.item_id; + let item_id = dragged_tab.item.item_id(); let from_pane = dragged_tab.pane.clone(); self.workspace .update(cx, |_, cx| { @@ -2603,8 +2603,7 @@ mod tests { impl Render for DraggedTab { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - let item = &self.pane.read(cx).items[self.ix]; - let label = item.tab_content(Some(self.detail), false, cx); + let label = self.item.tab_content(Some(self.detail), false, cx); Tab::new("") .selected(self.is_active) .child(label) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index efd2c52989edb51cff2559382f0ec62a2ce2702e..0e7a635cd22bca4f86a4467d8736571f5ed9053d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2250,17 +2250,16 @@ impl Workspace { destination_index: usize, cx: &mut ViewContext, ) { - let item_to_move = source + let Some((item_ix, item_handle)) = source .read(cx) .items() .enumerate() - .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move); - - if item_to_move.is_none() { - log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); + .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move) + else { + // Tab was closed during drag return; - } - let (item_ix, item_handle) = item_to_move.unwrap(); + }; + let item_handle = item_handle.clone(); if source != destination { From 92add99260ffa1d35f4994dacb9a58f5baa3f8e4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 22:05:49 +0200 Subject: [PATCH 79/98] Add LSP logs into the end of the editor, not after its caret Also prevent tabs from being added in readonly editors --- crates/editor/src/editor.rs | 2 +- crates/language_tools/src/lsp_log.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 01de8c20b1ea8818bcb353d5f53227b09d2c2b73..288d25f9cd22e6245d3d743db8ddff1d1433a335 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4513,7 +4513,7 @@ impl Editor { } pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { - if self.move_to_next_snippet_tabstop(cx) { + if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) { return; } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 52a1d11e74f2be6cf33123972f5c847d6d9e563e..0720c53cbc57b36c10e4d853716ec84daaf0a0d8 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -405,8 +405,14 @@ impl LspLogView { { log_view.editor.update(cx, |editor, cx| { editor.set_read_only(false); - editor.handle_input(entry.trim(), cx); - editor.handle_input("\n", cx); + let last_point = editor.buffer().read(cx).len(cx); + editor.edit( + vec![ + (last_point..last_point, entry.trim()), + (last_point..last_point, "\n"), + ], + cx, + ); editor.set_read_only(true); }); } From 9a70a8947713a571df950614f159e60038d879a9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:23:16 +0100 Subject: [PATCH 80/98] Fix project panel being toggled on workspace startup. (#4059) A sequence of events: Launch Zed -> Quit Zed -> Launch Zed would leave you with a project panel in a a different state on each open (e.g. if it is open on 1st one, 2nd run will have it closed). We were essentially not tracking whether the deserialization took place. Release Notes: - Fixed project panel being toggled on/off on startup due to incorrect tracking of serialization state (solves https://github.com/zed-industries/community/issues/2406) --- crates/project_panel/src/project_panel.rs | 6 ++++++ crates/zed/src/zed.rs | 24 +++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a7b9ad2404d77686ca5743a38d59507fa1c6d671..4301b6e392df1af4d2f411fc6e51b50564ffb16f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -58,6 +58,7 @@ pub struct ProjectPanel { workspace: WeakView, width: Option, pending_serialization: Task>, + was_deserialized: bool, } #[derive(Copy, Clone, Debug)] @@ -243,6 +244,7 @@ impl ProjectPanel { workspace: workspace.weak_handle(), width: None, pending_serialization: Task::ready(None), + was_deserialized: false, }; this.update_visible_entries(None, cx); @@ -322,6 +324,7 @@ impl ProjectPanel { if let Some(serialized_panel) = serialized_panel { panel.update(cx, |panel, cx| { panel.width = serialized_panel.width; + panel.was_deserialized = true; cx.notify(); }); } @@ -1465,6 +1468,9 @@ impl ProjectPanel { cx.notify(); } } + pub fn was_deserialized(&self) -> bool { + self.was_deserialized + } } impl Render for ProjectPanel { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d7686c425ad6a40663aeb6552e790d00aab50f77..bbe5e781094e33e8a53845d699e50ecd21552871 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -178,7 +178,10 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { )?; workspace_handle.update(&mut cx, |workspace, cx| { - let position = project_panel.read(cx).position(cx); + let (position, was_deserialized) = { + let project_panel = project_panel.read(cx); + (project_panel.position(cx), project_panel.was_deserialized()) + }; workspace.add_panel(project_panel, cx); workspace.add_panel(terminal_panel, cx); workspace.add_panel(assistant_panel, cx); @@ -186,15 +189,16 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { workspace.add_panel(chat_panel, cx); workspace.add_panel(notification_panel, cx); - if workspace - .project() - .read(cx) - .visible_worktrees(cx) - .any(|tree| { - tree.read(cx) - .root_entry() - .map_or(false, |entry| entry.is_dir()) - }) + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) { workspace.toggle_dock(position, cx); } From ba9a9f4f172d0ed803bf266f081c8867a35105ab Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:26:04 -0500 Subject: [PATCH 81/98] Add more open events project search diagnostics welcome page --- crates/client/src/telemetry.rs | 48 ++++++++++++------- crates/collab_ui/src/channel_view.rs | 4 ++ crates/diagnostics/src/diagnostics.rs | 4 ++ crates/editor/src/items.rs | 4 ++ crates/language_tools/src/lsp_log.rs | 4 ++ crates/language_tools/src/syntax_tree_view.rs | 4 ++ crates/search/src/project_search.rs | 4 ++ crates/terminal_view/src/terminal_view.rs | 4 ++ crates/welcome/src/welcome.rs | 27 +++++++---- crates/workspace/src/item.rs | 11 +++++ crates/workspace/src/shared_screen.rs | 4 ++ crates/workspace/src/workspace.rs | 12 ++++- crates/zed/src/main.rs | 11 +++-- 13 files changed, 110 insertions(+), 31 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ca717c9d6a6463b0351c0aa312f8aeeefab8c8ac..203840ac1b22dc39574fc4f4063df1d449f82eb5 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -116,7 +116,7 @@ pub enum Event { milliseconds_since_first_event: i64, }, App { - operation: &'static str, + operation: String, milliseconds_since_first_event: i64, }, Setting { @@ -129,6 +129,10 @@ pub enum Event { environment: &'static str, milliseconds_since_first_event: i64, }, + Button { + operation: &'static str, + milliseconds_since_first_event: i64, + }, } #[cfg(debug_assertions)] @@ -219,7 +223,7 @@ impl Telemetry { // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings #[cfg(not(any(test, feature = "test-support")))] fn shutdown_telemetry(self: &Arc) -> impl Future { - self.report_app_event("close"); + self.report_app_event("close".to_string()); // TODO: close final edit period and make sure it's sent Task::ready(()) } @@ -385,7 +389,7 @@ impl Telemetry { self.report_event(event) } - pub fn report_app_event(self: &Arc, operation: &'static str) { + pub fn report_app_event(self: &Arc, operation: String) { let event = Event::App { operation, milliseconds_since_first_event: self.milliseconds_since_first_event(), @@ -404,20 +408,6 @@ impl Telemetry { self.report_event(event) } - fn milliseconds_since_first_event(&self) -> i64 { - let mut state = self.state.lock(); - match state.first_event_datetime { - Some(first_event_datetime) => { - let now: DateTime = Utc::now(); - now.timestamp_millis() - first_event_datetime.timestamp_millis() - } - None => { - state.first_event_datetime = Some(Utc::now()); - 0 - } - } - } - pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); let period_data = state.event_coalescer.log_event(environment); @@ -434,6 +424,30 @@ impl Telemetry { } } + pub fn report_button_event(self: &Arc, operation: &'static str) { + let event = Event::Button { + operation, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_event(event) + } + + fn milliseconds_since_first_event(&self) -> i64 { + let mut state = self.state.lock(); + + match state.first_event_datetime { + Some(first_event_datetime) => { + let now: DateTime = Utc::now(); + now.timestamp_millis() - first_event_datetime.timestamp_millis() + } + None => { + state.first_event_datetime = Some(Utc::now()); + 0 + } + } + } + fn report_event(self: &Arc, event: Event) { let mut state = self.state.lock(); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index ce68acfbd83379e77c298152aa95b51280883e0c..033889f771d84bafc13dae5005957096e61cb3c6 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -266,6 +266,10 @@ impl Item for ChannelView { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext) -> Option> { Some(cx.new_view(|cx| { Self::new( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d88a8b7c23b07b7f22cbdc35bfd88c334303e6e4..ca701e626e7f02284e22f553b507ca55a09524a5 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -688,6 +688,10 @@ impl Item for ProjectDiagnosticsEditor { } } + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("project diagnostics") + } + fn for_each_project_item( &self, cx: &AppContext, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec9105362901b1b5fdd24206047846ef6ef3af86..36a48b293788ab22f509f941c7b2a66591614d9a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -578,6 +578,10 @@ impl Item for Editor { Some(file_path.into()) } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option { let path = path_for_buffer(&self.buffer, detail, true, cx)?; Some(path.to_string_lossy().to_string().into()) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 0720c53cbc57b36c10e4d853716ec84daaf0a0d8..75b4305b58329a48cd19563fc659be6a27f09af0 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -631,6 +631,10 @@ impl Item for LspLogView { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn as_searchable(&self, handle: &View) -> Option> { Some(Box::new(handle.clone())) } diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index bfe2b2a03be70272d62a18733fe9b655834ea99b..5acc6bff7fb1472697bd95c363c2914470457ea3 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -397,6 +397,10 @@ impl Item for SyntaxTreeView { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn clone_on_split( &self, _: workspace::WorkspaceId, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index d0679ed0ee2b5c929dca4911c5c2b7f1f679dfb0..3827b46c67fc29c96c5b361440fb66143c96db1a 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -446,6 +446,10 @@ impl Item for ProjectSearchView { .into_any() } + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("project search") + } + fn for_each_project_item( &self, cx: &AppContext, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b786877608bddf1de48cdb5b0df61123d637869f..db4b21627f13a3e050888ca1cf18a06488484b1a 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -708,6 +708,10 @@ impl Item for TerminalView { .into_any() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn clone_on_split( &self, _workspace_id: WorkspaceId, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index cefedeb73f52d010e9002b644662696fa479abf2..677b57a0225059430b141ca23573d42433f52b77 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -86,7 +86,7 @@ impl Render for WelcomePage { .full_width() .on_click(cx.listener(|this, _, cx| { this.telemetry - .report_app_event("welcome page: change theme"); + .report_app_event("welcome page: change theme".to_string()); this.workspace .update(cx, |workspace, cx| { theme_selector::toggle( @@ -102,8 +102,9 @@ impl Render for WelcomePage { Button::new("choose-keymap", "Choose a keymap") .full_width() .on_click(cx.listener(|this, _, cx| { - this.telemetry - .report_app_event("welcome page: change keymap"); + this.telemetry.report_app_event( + "welcome page: change keymap".to_string(), + ); this.workspace .update(cx, |workspace, cx| { base_keymap_picker::toggle( @@ -119,7 +120,8 @@ impl Render for WelcomePage { Button::new("install-cli", "Install the CLI") .full_width() .on_click(cx.listener(|this, _, cx| { - this.telemetry.report_app_event("welcome page: install cli"); + this.telemetry + .report_app_event("welcome page: install cli".to_string()); cx.app_mut() .spawn( |cx| async move { install_cli::install_cli(&cx).await }, @@ -150,8 +152,9 @@ impl Render for WelcomePage { ) .on_click(cx.listener( move |this, selection, cx| { - this.telemetry - .report_app_event("welcome page: toggle vim"); + this.telemetry.report_app_event( + "welcome page: toggle vim".to_string(), + ); this.update_settings::( selection, cx, @@ -177,7 +180,7 @@ impl Render for WelcomePage { .on_click(cx.listener( move |this, selection, cx| { this.telemetry.report_app_event( - "welcome page: toggle metric telemetry", + "welcome page: toggle metric telemetry".to_string(), ); this.update_settings::( selection, @@ -215,7 +218,8 @@ impl Render for WelcomePage { .on_click(cx.listener( move |this, selection, cx| { this.telemetry.report_app_event( - "welcome page: toggle diagnostic telemetry", + "welcome page: toggle diagnostic telemetry" + .to_string(), ); this.update_settings::( selection, @@ -247,7 +251,8 @@ impl WelcomePage { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> View { let this = cx.new_view(|cx| { cx.on_release(|this: &mut Self, _, _| { - this.telemetry.report_app_event("welcome page: close"); + this.telemetry + .report_app_event("welcome page: close".to_string()); }) .detach(); @@ -306,6 +311,10 @@ impl Item for WelcomePage { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("welcome page") + } + fn show_toolbar(&self) -> bool { false } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index fb4ed05f6c006d8118304418a76dd6dcffc67576..4f696e4a335040eebc10396262382f60c8f2821f 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -114,6 +114,8 @@ pub trait Item: FocusableView + EventEmitter { } fn tab_content(&self, detail: Option, selected: bool, cx: &WindowContext) -> AnyElement; + fn telemetry_event_text(&self) -> Option<&'static str>; + /// (model id, Item) fn for_each_project_item( &self, @@ -225,6 +227,7 @@ pub trait ItemHandle: 'static + Send { fn tab_tooltip_text(&self, cx: &AppContext) -> Option; fn tab_description(&self, detail: usize, cx: &AppContext) -> Option; fn tab_content(&self, detail: Option, selected: bool, cx: &WindowContext) -> AnyElement; + fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str>; fn dragged_tab_content(&self, detail: Option, cx: &WindowContext) -> AnyElement; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; @@ -313,6 +316,10 @@ impl ItemHandle for View { self.read(cx).tab_tooltip_text(cx) } + fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str> { + self.read(cx).telemetry_event_text() + } + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option { self.read(cx).tab_description(detail, cx) } @@ -922,6 +929,10 @@ pub mod test { }) } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn tab_content( &self, detail: Option, diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 6fb156c22f8846857494ea2a52d53c638b0f3ab3..bfc16022749c10e5862933dd82d777e3adcbd5f2 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -111,6 +111,10 @@ impl Item for SharedScreen { .into_any() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { self.nav_history = Some(history); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index efd2c52989edb51cff2559382f0ec62a2ce2702e..2bbcfa80a26b60f0db7b85199b6259545edc481d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1271,7 +1271,9 @@ impl Workspace { } pub fn open(&mut self, _: &Open, cx: &mut ViewContext) { - self.client().telemetry().report_app_event("open project"); + self.client() + .telemetry() + .report_app_event("open project".to_string()); let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, @@ -1776,6 +1778,14 @@ impl Workspace { } pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { + if let Some(text) = item.telemetry_event_text(cx) { + dbg!("workspace"); + dbg!(&text); + self.client() + .telemetry() + .report_app_event(format!("{}: open", text)); + } + self.active_pane .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e10c52a175c8633fa3b3bebdb09223f3505587ad..821668001c4fa42f757eea55b5e80361e0456e6d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -176,10 +176,13 @@ fn main() { telemetry.start(installation_id, session_id, cx); telemetry.report_setting_event("theme", cx.theme().name.to_string()); telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string()); - telemetry.report_app_event(match existing_installation_id_found { - Some(false) => "first open", - _ => "open", - }); + telemetry.report_app_event( + match existing_installation_id_found { + Some(false) => "first open", + _ => "open", + } + .to_string(), + ); telemetry.flush_events(); let app_state = Arc::new(AppState { From 148c294c02f04b70ce6e463ca957cf1d19623b5d Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:26:56 -0500 Subject: [PATCH 82/98] Removed button event --- crates/client/src/telemetry.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 203840ac1b22dc39574fc4f4063df1d449f82eb5..0b363bd336e76ada87a8dd7f396febb80bec14da 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -129,10 +129,6 @@ pub enum Event { environment: &'static str, milliseconds_since_first_event: i64, }, - Button { - operation: &'static str, - milliseconds_since_first_event: i64, - }, } #[cfg(debug_assertions)] From 355d1fca82c8543183ec230537cd009d61466089 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:28:28 -0500 Subject: [PATCH 83/98] Remove button event function --- crates/client/src/telemetry.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 0b363bd336e76ada87a8dd7f396febb80bec14da..6276548e4cf3a3cd380dbf9b3a19430fd10922f9 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -420,15 +420,6 @@ impl Telemetry { } } - pub fn report_button_event(self: &Arc, operation: &'static str) { - let event = Event::Button { - operation, - milliseconds_since_first_event: self.milliseconds_since_first_event(), - }; - - self.report_event(event) - } - fn milliseconds_since_first_event(&self) -> i64 { let mut state = self.state.lock(); From 24db41fcdd4f6a23342e9c8338f8ca006ae96911 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:29:58 -0500 Subject: [PATCH 84/98] Remove debugs --- crates/workspace/src/workspace.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2bbcfa80a26b60f0db7b85199b6259545edc481d..9b394608aeab133c324c34bb41edfe0bb0a5dcec 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1779,8 +1779,6 @@ impl Workspace { pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { if let Some(text) = item.telemetry_event_text(cx) { - dbg!("workspace"); - dbg!(&text); self.client() .telemetry() .report_app_event(format!("{}: open", text)); From f0ed80cd8e0820373b6aefc0588fd235adbcfb1c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Jan 2024 14:32:48 -0700 Subject: [PATCH 85/98] Fix fallback font As this is used if you mis-spell "buffer_font_family", it should be monospace. Also treat "Zed Mono" and "Zed Sans" as valid fonts --- crates/gpui/src/text_system.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 3444c05fc115414370e35c85a03d41ab93bc00e2..24438d8c819527a3e8dd869f9e1dac179d9dd618 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -59,14 +59,22 @@ impl TextSystem { fallback_font_stack: smallvec![ // TODO: This is currently Zed-specific. // We should allow GPUI users to provide their own fallback font stack. - font("Zed Sans"), + font("Zed Mono"), font("Helvetica") ], } } pub fn all_font_families(&self) -> Vec { - self.platform_text_system.all_font_families() + let mut families = self.platform_text_system.all_font_families(); + families.append( + &mut self + .fallback_font_stack + .iter() + .map(|font| font.family.to_string()) + .collect(), + ); + families } pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { self.platform_text_system.add_fonts(fonts) From 55671eac40465eada4d21fa086141deadebae517 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 23:02:15 +0200 Subject: [PATCH 86/98] Select next/previous word for multiple carets if possible --- crates/editor/src/editor.rs | 198 +++++++++++++++++-------- crates/editor/src/scroll/autoscroll.rs | 4 +- 2 files changed, 141 insertions(+), 61 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 288d25f9cd22e6245d3d743db8ddff1d1433a335..55c31cfa8074734343dd62bb6e138129d77b0d9b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6467,40 +6467,77 @@ impl Editor { } self.select_next_state = Some(select_next_state); - } else if selections.len() == 1 { - let selection = selections.last_mut().unwrap(); - if selection.start == selection.end { - let word_range = movement::surrounding_word( - &display_map, - selection.start.to_display_point(&display_map), - ); - selection.start = word_range.start.to_offset(&display_map, Bias::Left); - selection.end = word_range.end.to_offset(&display_map, Bias::Left); - selection.goal = SelectionGoal::None; - selection.reversed = false; + } else { + let mut only_carets = true; + let mut same_letters_selected = true; + let mut selection_query = None; + + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + if selection.start != selection.end { + only_carets = false; + } - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); + if same_letters_selected { + if selection_query.is_none() { + selection_query = + Some(buffer.text_for_range(selection.range()).collect::()); + } - let is_empty = query.is_empty(); - let select_state = SelectNextState { - query: AhoCorasick::new(&[query])?, - wordwise: true, - done: is_empty, - }; - select_next_match_ranges( - self, - selection.start..selection.end, - replace_newest, - autoscroll, - cx, - ); - self.select_next_state = Some(select_state); - } else { - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); + if let Some(next_selection) = selections_iter.peek() { + if next_selection.range().len() == selection.range().len() { + let next_query = buffer + .text_for_range(next_selection.range()) + .collect::(); + if Some(next_query) != selection_query { + same_letters_selected = false; + selection_query = None; + } + } else { + same_letters_selected = false; + selection_query = None; + } + } + } + } + + if only_carets { + for selection in &mut selections { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + select_next_match_ranges( + self, + selection.start..selection.end, + replace_newest, + autoscroll, + cx, + ); + } + + if selections.len() == 1 { + let selection = selections + .last() + .expect("ensured that there's only one selection"); + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let is_empty = query.is_empty(); + let select_state = SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: true, + done: is_empty, + }; + self.select_next_state = Some(select_state); + } else { + self.select_next_state = None; + } + } else if let Some(query) = selection_query { self.select_next_state = Some(SelectNextState { query: AhoCorasick::new(&[query])?, wordwise: false, @@ -6548,6 +6585,7 @@ impl Editor { Ok(()) } + // TODO kb test both select_next and select_previous pub fn select_previous( &mut self, action: &SelectPrevious, @@ -6606,37 +6644,79 @@ impl Editor { } self.select_prev_state = Some(select_prev_state); - } else if selections.len() == 1 { - let selection = selections.last_mut().unwrap(); - if selection.start == selection.end { - let word_range = movement::surrounding_word( - &display_map, - selection.start.to_display_point(&display_map), + } else { + let mut only_carets = true; + let mut same_letters_selected = true; + let mut selection_query = None; + + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + if selection.start != selection.end { + only_carets = false; + } + + if same_letters_selected { + if selection_query.is_none() { + selection_query = + Some(buffer.text_for_range(selection.range()).collect::()); + } + + if let Some(next_selection) = selections_iter.peek() { + if next_selection.range().len() == selection.range().len() { + let next_query = buffer + .text_for_range(next_selection.range()) + .collect::(); + if Some(next_query) != selection_query { + same_letters_selected = false; + selection_query = None; + } + } else { + same_letters_selected = false; + selection_query = None; + } + } + } + } + + if only_carets { + for selection in &mut selections { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + } + if selections.len() == 1 { + let selection = selections + .last() + .expect("ensured that there's only one selection"); + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let is_empty = query.is_empty(); + let select_state = SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: true, + done: is_empty, + }; + self.select_prev_state = Some(select_state); + } else { + self.select_prev_state = None; + } + + self.unfold_ranges( + selections.iter().map(|s| s.range()).collect::>(), + false, + true, + cx, ); - selection.start = word_range.start.to_offset(&display_map, Bias::Left); - selection.end = word_range.end.to_offset(&display_map, Bias::Left); - selection.goal = SelectionGoal::None; - selection.reversed = false; - - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); - let query = query.chars().rev().collect::(); - let select_state = SelectNextState { - query: AhoCorasick::new(&[query])?, - wordwise: true, - done: false, - }; - self.unfold_ranges([selection.start..selection.end], false, true, cx); self.change_selections(Some(Autoscroll::newest()), cx, |s| { s.select(selections); }); - self.select_prev_state = Some(select_state); - } else { - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); - let query = query.chars().rev().collect::(); + } else if let Some(query) = selection_query { self.select_prev_state = Some(SelectNextState { query: AhoCorasick::new(&[query])?, wordwise: false, diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index ba70739942c429e6b5eb11139a395b98db38475a..2a5ac568b79bb9df45489bf5e6b37f37ffcba25b 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -5,7 +5,7 @@ use language::Point; use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub enum Autoscroll { Next, Strategy(AutoscrollStrategy), @@ -25,7 +25,7 @@ impl Autoscroll { } } -#[derive(PartialEq, Eq, Default)] +#[derive(PartialEq, Eq, Default, Clone, Copy)] pub enum AutoscrollStrategy { Fit, Newest, From e90794d3ecff8923b0f52e50b04cee55bb73712c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 14:03:38 -0800 Subject: [PATCH 87/98] Add and enhance tests for muting/deafening, fix exposed logic errors --- crates/call/src/room.rs | 16 +- .../collab/src/tests/channel_guest_tests.rs | 14 +- crates/collab/src/tests/integration_tests.rs | 180 +++++++++++++++ crates/live_kit_client/src/test.rs | 207 +++++++++++++++--- 4 files changed, 375 insertions(+), 42 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 0979ad8bb93092ad5e5d75506bff4872f723da08..ed807ef8c5cf4f64207b7c33ac2c8ffc671c1380 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -151,11 +151,14 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; this.update(&mut cx, |this, cx| { - if this.read_only() || this.is_muted() { - Task::ready(Ok(())) - } else { - this.share_microphone(cx) + if !this.read_only() { + if let Some(live_kit) = &this.live_kit { + if !live_kit.muted_by_user && !live_kit.deafened { + return this.share_microphone(cx); + } + } } + Task::ready(Ok(())) })? .await }) @@ -1477,7 +1480,10 @@ impl Room { if let Some(live_kit) = self.live_kit.as_mut() { // When unmuting, undeafen if the user was deafened before. let was_deafened = live_kit.deafened; - if live_kit.muted_by_user || live_kit.deafened { + if live_kit.muted_by_user + || live_kit.deafened + || matches!(live_kit.microphone_track, LocalTrack::None) + { live_kit.muted_by_user = false; live_kit.deafened = false; } else { diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index d5933235926fa1da8c1e8538dedd2a7506504cd8..f3326cd6922b7482f6c9d953a5a57a89676b717d 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -57,7 +57,7 @@ async fn test_channel_guests( }) .await .is_err()); - assert!(room_b.read_with(cx_b, |room, _| !room.is_sharing_mic())); + assert!(room_b.read_with(cx_b, |room, _| room.is_muted())); } #[gpui::test] @@ -104,6 +104,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(room_b.read_with(cx_b, |room, _| room.read_only())); assert!(room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await @@ -127,10 +128,13 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test // project and buffers are now editable assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); - room_b - .update(cx_b, |room, cx| room.share_microphone(cx)) - .await - .unwrap(); + + // B sees themselves as muted, and can unmute. + assert!(room_b.read_with(cx_b, |room, _| !room.read_only())); + room_b.read_with(cx_b, |room, _| assert!(room.is_muted())); + room_b.update(cx_b, |room, cx| room.toggle_mute(cx)); + cx_a.run_until_parked(); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); // B is demoted active_call_a diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index cedc841527ff5c4c40d2630dc9c15166ccec46d8..e68fd10d8d16b29300c3fa658596468df06be880 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1876,6 +1876,186 @@ fn active_call_events(cx: &mut TestAppContext) -> Rc>> events } +#[gpui::test] +async fn test_mute_deafen( + executor: BackgroundExecutor, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + + server + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) + .await; + + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + + // User A calls user B, B answers. + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_b.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + executor.run_until_parked(); + active_call_b + .update(cx_b, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + executor.run_until_parked(); + + let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone()); + let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone()); + + room_a.read_with(cx_a, |room, _| assert!(!room.is_muted())); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); + + // Users A and B are both muted. + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + }] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + }] + ); + + // User A mutes + room_a.update(cx_a, |room, cx| room.toggle_mute(cx)); + executor.run_until_parked(); + + // User A hears user B, but B doesn't hear A. + room_a.read_with(cx_a, |room, _| assert!(room.is_muted())); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + }] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: true, + audio_tracks_playing: vec![true], + }] + ); + + // User A deafens + room_a.update(cx_a, |room, cx| room.toggle_deafen(cx)); + executor.run_until_parked(); + + // User A does not hear user B. + room_a.read_with(cx_a, |room, _| assert!(room.is_muted())); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![false], + }] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: true, + audio_tracks_playing: vec![true], + }] + ); + + // User B calls user C, C joins. + active_call_b + .update(cx_b, |call, cx| { + call.invite(client_c.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + executor.run_until_parked(); + active_call_c + .update(cx_c, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + executor.run_until_parked(); + + // User A does not hear users B or C. + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ + ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![false], + }, + ParticipantAudioState { + user_id: client_c.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![false], + } + ] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ + ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: true, + audio_tracks_playing: vec![true], + }, + ParticipantAudioState { + user_id: client_c.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + } + ] + ); + + #[derive(PartialEq, Eq, Debug)] + struct ParticipantAudioState { + user_id: u64, + is_muted: bool, + audio_tracks_playing: Vec, + } + + fn participant_audio_state( + room: &Model, + cx: &TestAppContext, + ) -> Vec { + room.read_with(cx, |room, _| { + room.remote_participants() + .iter() + .map(|(user_id, participant)| ParticipantAudioState { + user_id: *user_id, + is_muted: participant.muted, + audio_tracks_playing: participant + .audio_tracks + .values() + .map(|track| track.is_playing()) + .collect(), + }) + .collect::>() + }) + } +} + #[gpui::test(iterations = 10)] async fn test_room_location( executor: BackgroundExecutor, diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 1b7fd20bc257ca5559a37b13ed70b05fe8eb90f7..96ca2b90dcd5de645b927358e2ff2e2779592003 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -1,7 +1,7 @@ use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use collections::{BTreeMap, HashMap}; +use collections::{BTreeMap, HashMap, HashSet}; use futures::Stream; use gpui::BackgroundExecutor; use live_kit_server::{proto, token}; @@ -13,7 +13,7 @@ use std::{ mem, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, - Arc, + Arc, Weak, }, }; @@ -113,7 +113,25 @@ impl TestServer { .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( + RemoteVideoTrack { + server_track: track.clone(), + }, + ))) + .unwrap(); + } + for track in &room.audio_tracks { + client_room + .0 + .lock() + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }), + Arc::new(RemoteTrackPublication), + )) .unwrap(); } room.client_rooms.insert(identity, client_room); @@ -210,7 +228,7 @@ impl TestServer { } let sid = nanoid::nanoid!(17); - let track = Arc::new(RemoteVideoTrack { + let track = Arc::new(TestServerVideoTrack { sid: sid.clone(), publisher_id: identity.clone(), frames_rx: local_track.frames_rx.clone(), @@ -224,7 +242,11 @@ impl TestServer { .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( + RemoteVideoTrack { + server_track: track.clone(), + }, + ))) .unwrap(); } } @@ -259,10 +281,10 @@ impl TestServer { } let sid = nanoid::nanoid!(17); - let track = Arc::new(RemoteAudioTrack { + let track = Arc::new(TestServerAudioTrack { sid: sid.clone(), publisher_id: identity.clone(), - running: AtomicBool::new(true), + muted: AtomicBool::new(false), }); let publication = Arc::new(RemoteTrackPublication); @@ -276,7 +298,10 @@ impl TestServer { .lock() .updates_tx .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( - track.clone(), + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }), publication.clone(), )) .unwrap(); @@ -286,37 +311,123 @@ impl TestServer { Ok(sid) } + fn set_track_muted(&self, token: &str, track_sid: &str, muted: bool) -> Result<()> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + let identity = claims.sub.unwrap(); + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + if let Some(track) = room + .audio_tracks + .iter_mut() + .find(|track| track.sid == track_sid) + { + track.muted.store(muted, SeqCst); + for (id, client_room) in room.client_rooms.iter() { + if *id != identity { + client_room + .0 + .lock() + .updates_tx + .try_broadcast(RoomUpdate::RemoteAudioTrackMuteChanged { + track_id: track_sid.to_string(), + muted, + }) + .unwrap(); + } + } + } + Ok(()) + } + + fn is_track_muted(&self, token: &str, track_sid: &str) -> Option { + let claims = live_kit_server::token::validate(&token, &self.secret_key).ok()?; + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms.get_mut(&*room_name)?; + room.audio_tracks.iter().find_map(|track| { + if track.sid == track_sid { + Some(track.muted.load(SeqCst)) + } else { + None + } + }) + } + fn video_tracks(&self, token: String) -> Result>> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - Ok(room.video_tracks.clone()) + room.client_rooms + .get(identity.as_ref()) + .ok_or_else(|| anyhow!("not a participant in room"))?; + Ok(room + .video_tracks + .iter() + .map(|track| { + Arc::new(RemoteVideoTrack { + server_track: track.clone(), + }) + }) + .collect()) } fn audio_tracks(&self, token: String) -> Result>> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - Ok(room.audio_tracks.clone()) + let client_room = room + .client_rooms + .get(identity.as_ref()) + .ok_or_else(|| anyhow!("not a participant in room"))?; + Ok(room + .audio_tracks + .iter() + .map(|track| { + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }) + }) + .collect()) } } #[derive(Default)] struct TestServerRoom { client_rooms: HashMap>, - video_tracks: Vec>, - audio_tracks: Vec>, + video_tracks: Vec>, + audio_tracks: Vec>, participant_permissions: HashMap, } +#[derive(Debug)] +struct TestServerVideoTrack { + sid: Sid, + publisher_id: Sid, + frames_rx: async_broadcast::Receiver, +} + +#[derive(Debug)] +struct TestServerAudioTrack { + sid: Sid, + publisher_id: Sid, + muted: AtomicBool, +} + impl TestServerRoom {} pub struct TestApiClient { @@ -387,6 +498,7 @@ struct RoomState { watch::Receiver, ), display_sources: Vec, + paused_audio_tracks: HashSet, updates_tx: async_broadcast::Sender, updates_rx: async_broadcast::Receiver, } @@ -399,6 +511,7 @@ impl Room { Arc::new(Self(Mutex::new(RoomState { connection: watch::channel_with(ConnectionState::Disconnected), display_sources: Default::default(), + paused_audio_tracks: Default::default(), updates_tx, updates_rx, }))) @@ -444,11 +557,12 @@ impl Room { .publish_video_track(this.token(), track) .await?; Ok(LocalTrackPublication { - muted: Default::default(), + room: Arc::downgrade(&this), sid, }) } } + pub fn publish_audio_track( self: &Arc, track: LocalAudioTrack, @@ -461,7 +575,7 @@ impl Room { .publish_audio_track(this.token(), &track) .await?; Ok(LocalTrackPublication { - muted: Default::default(), + room: Arc::downgrade(&this), sid, }) } @@ -561,20 +675,31 @@ impl Drop for Room { #[derive(Clone)] pub struct LocalTrackPublication { sid: String, - muted: Arc, + room: Weak, } impl LocalTrackPublication { pub fn set_mute(&self, mute: bool) -> impl Future> { - let muted = self.muted.clone(); + let sid = self.sid.clone(); + let room = self.room.clone(); async move { - muted.store(mute, SeqCst); - Ok(()) + if let Some(room) = room.upgrade() { + room.test_server() + .set_track_muted(&room.token(), &sid, mute) + } else { + Err(anyhow!("no such room")) + } } } pub fn is_muted(&self) -> bool { - self.muted.load(SeqCst) + if let Some(room) = self.room.upgrade() { + room.test_server() + .is_track_muted(&room.token(), &self.sid) + .unwrap_or(false) + } else { + false + } } pub fn sid(&self) -> String { @@ -622,47 +747,65 @@ impl LocalAudioTrack { #[derive(Debug)] pub struct RemoteVideoTrack { - sid: Sid, - publisher_id: Sid, - frames_rx: async_broadcast::Receiver, + server_track: Arc, } impl RemoteVideoTrack { pub fn sid(&self) -> &str { - &self.sid + &self.server_track.sid } pub fn publisher_id(&self) -> &str { - &self.publisher_id + &self.server_track.publisher_id } pub fn frames(&self) -> async_broadcast::Receiver { - self.frames_rx.clone() + self.server_track.frames_rx.clone() } } #[derive(Debug)] pub struct RemoteAudioTrack { - sid: Sid, - publisher_id: Sid, - running: AtomicBool, + server_track: Arc, + room: Weak, } impl RemoteAudioTrack { pub fn sid(&self) -> &str { - &self.sid + &self.server_track.sid } pub fn publisher_id(&self) -> &str { - &self.publisher_id + &self.server_track.publisher_id } pub fn start(&self) { - self.running.store(true, SeqCst); + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .remove(&self.server_track.sid); + } } pub fn stop(&self) { - self.running.store(false, SeqCst); + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .insert(self.server_track.sid.clone()); + } + } + + pub fn is_playing(&self) -> bool { + !self + .room + .upgrade() + .unwrap() + .0 + .lock() + .paused_audio_tracks + .contains(&self.server_track.sid) } } From 8f262892a04fedb1585f1d06785f9f699678eb41 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 14:53:24 -0800 Subject: [PATCH 88/98] Notify editors on buffer font size changes --- crates/editor/src/editor.rs | 7 ++++++- crates/theme/src/settings.rs | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 288d25f9cd22e6245d3d743db8ddff1d1433a335..ce9c4215cc356c4f3128ff04cc6d44b8e306b576 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -97,7 +97,10 @@ use std::{ pub use sum_tree::Bias; use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; -use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; +use theme::{ + observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, + ThemeColors, ThemeSettings, +}; use ui::{ h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover, Tooltip, @@ -1812,6 +1815,7 @@ impl Editor { cx.observe(&display_map, Self::on_display_map_changed), cx.observe(&blink_manager, |_, _, cx| cx.notify()), cx.observe_global::(Self::settings_changed), + observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()), cx.observe_window_activation(|editor, cx| { let active = cx.is_window_active(); editor.blink_manager.update(cx, |blink_manager, cx| { @@ -8741,6 +8745,7 @@ impl Editor { )), cx, ); + cx.notify(); } pub fn set_searchable(&mut self, searchable: bool) { diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index e51ff81b012aceec95aa296e4b8d1a2b58642288..efc62ed59c429b97420fc276d1f2e2eca6c841cc 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -1,7 +1,9 @@ use crate::one_themes::one_dark; use crate::{Theme, ThemeRegistry}; use anyhow::Result; -use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; +use gpui::{ + px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels, Subscription, ViewContext, +}; use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, @@ -80,6 +82,13 @@ impl ThemeSettings { } } +pub fn observe_buffer_font_size_adjustment( + cx: &mut ViewContext, + f: impl 'static + Fn(&mut V, &mut ViewContext), +) -> Subscription { + cx.observe_global::(f) +} + pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels { if let Some(AdjustedBufferFontSize(adjusted_size)) = cx.try_global::() { let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; From 8f1633e7980495ffaa52bd606c118a64031071cb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 16:49:06 -0800 Subject: [PATCH 89/98] Iterate from leaf to root when marking views dirty in notify --- crates/gpui/src/window.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2a88a4f3976646845589b5127e90c78bde6eb8c0..869d6b18268cc64bc18f9187dd34e8032237d28b 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2761,6 +2761,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { .rendered_frame .dispatch_tree .view_path(self.view.entity_id()) + .into_iter() + .rev() { if !self.window.dirty_views.insert(view_id) { break; From 1e755aa00f73b3d7897a1c4e6f4030ac31db0006 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 17:02:20 -0800 Subject: [PATCH 90/98] Notify global observers when removing a global --- crates/gpui/src/app.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 17f92efb581f3aed55becda1100316f041bdd308..41519f0ae4d3623a5b3e57c06a265c5f0a754b23 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -843,6 +843,7 @@ impl AppContext { /// Remove the global of the given type from the app context. Does not notify global observers. pub fn remove_global(&mut self) -> G { let global_type = TypeId::of::(); + self.push_effect(Effect::NotifyGlobalObservers { global_type }); *self .globals_by_type .remove(&global_type) From d84785f7e8519b30e474385e5a72f5726cf4783e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 17:03:02 -0800 Subject: [PATCH 91/98] Put back logic for resetting buffer font adjustment on settings change. --- crates/theme/src/theme.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f8d90b7bdc823b0b52348fe94908454002616347..3b158ab9bb7f558f21ae72a64437b2b9f0909f9d 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -20,7 +20,7 @@ mod user_theme; use std::sync::Arc; -use ::settings::Settings; +use ::settings::{Settings, SettingsStore}; pub use default_colors::*; pub use default_theme::*; pub use registry::*; @@ -62,13 +62,22 @@ pub enum LoadThemes { pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { cx.set_global(ThemeRegistry::default()); + ThemeSettings::register(cx); + + let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; + cx.observe_global::(move |cx| { + let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; + if buffer_font_size != prev_buffer_font_size { + prev_buffer_font_size = buffer_font_size; + reset_font_size(cx); + } + }) + .detach(); match themes_to_load { LoadThemes::JustBase => (), LoadThemes::All => cx.global_mut::().load_user_themes(), } - - ThemeSettings::register(cx); } pub trait ActiveTheme { From 268d156fad72d410f479632f68a98bf8ec101748 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 20:22:47 -0500 Subject: [PATCH 92/98] Add command palette action events --- Cargo.lock | 1 + crates/client/src/telemetry.rs | 15 +++++++++++ crates/command_palette/Cargo.toml | 3 ++- crates/command_palette/src/command_palette.rs | 27 ++++++++++++++++--- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 056fab49cd576915fca5443922b0f094591237e1..090980ac3eb0ebba8d4e12ad9b65c9aff5b1682e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,6 +1607,7 @@ name = "command_palette" version = "0.1.0" dependencies = [ "anyhow", + "client", "collections", "ctor", "editor", diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 6276548e4cf3a3cd380dbf9b3a19430fd10922f9..32cf9efba230da83f68d568818d20a52bfc862dd 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -129,6 +129,11 @@ pub enum Event { environment: &'static str, milliseconds_since_first_event: i64, }, + Action { + source: &'static str, + action: String, + milliseconds_since_first_event: i64, + }, } #[cfg(debug_assertions)] @@ -420,6 +425,16 @@ impl Telemetry { } } + pub fn report_action_event(self: &Arc, source: &'static str, action: String) { + let event = Event::Action { + source, + action, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_event(event) + } + fn milliseconds_since_first_event(&self) -> i64 { let mut state = self.state.lock(); diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 39ed4fd95e183db7306bbcbfca616af9bc7a6b96..c762af7c487e1603b4c3b38f7887f38a56e99276 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -9,6 +9,7 @@ path = "src/command_palette.rs" doctest = false [dependencies] +client = { path = "../client" } collections = { path = "../collections" } editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } @@ -16,9 +17,9 @@ gpui = { path = "../gpui" } picker = { path = "../picker" } project = { path = "../project" } settings = { path = "../settings" } +theme = { path = "../theme" } ui = { path = "../ui" } util = { path = "../util" } -theme = { path = "../theme" } workspace = { path = "../workspace" } zed_actions = { path = "../zed_actions" } anyhow.workspace = true diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index e077426bb845dbc54efb9fc06906084946d41843..c90e44886568b6def4e7c66cced834553cf5bb96 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -3,6 +3,7 @@ use std::{ sync::Arc, }; +use client::telemetry::Telemetry; use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ @@ -39,11 +40,18 @@ impl CommandPalette { let Some(previous_focus_handle) = cx.focused() else { return; }; - workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx)); + let telemetry = workspace.client().telemetry().clone(); + workspace.toggle_modal(cx, move |cx| { + CommandPalette::new(previous_focus_handle, telemetry, cx) + }); }); } - fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext) -> Self { + fn new( + previous_focus_handle: FocusHandle, + telemetry: Arc, + cx: &mut ViewContext, + ) -> Self { let filter = cx.try_global::(); let commands = cx @@ -66,8 +74,12 @@ impl CommandPalette { }) .collect(); - let delegate = - CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle); + let delegate = CommandPaletteDelegate::new( + cx.view().downgrade(), + commands, + telemetry, + previous_focus_handle, + ); let picker = cx.new_view(|cx| Picker::new(delegate, cx)); Self { picker } @@ -103,6 +115,7 @@ pub struct CommandPaletteDelegate { commands: Vec, matches: Vec, selected_ix: usize, + telemetry: Arc, previous_focus_handle: FocusHandle, } @@ -130,6 +143,7 @@ impl CommandPaletteDelegate { fn new( command_palette: WeakView, commands: Vec, + telemetry: Arc, previous_focus_handle: FocusHandle, ) -> Self { Self { @@ -138,6 +152,7 @@ impl CommandPaletteDelegate { matches: vec![], commands, selected_ix: 0, + telemetry, previous_focus_handle, } } @@ -284,6 +299,10 @@ impl PickerDelegate for CommandPaletteDelegate { } let action_ix = self.matches[self.selected_ix].candidate_id; let command = self.commands.swap_remove(action_ix); + + self.telemetry + .report_action_event("command palette", command.name.clone()); + self.matches.clear(); self.commands.clear(); cx.update_global(|hit_counts: &mut HitCounts, _| { From fbb363e83a066a516c7d03a0c75c7b5840673e4f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 Jan 2024 10:07:31 +0200 Subject: [PATCH 93/98] Add tests --- crates/editor/src/editor_tests.rs | 157 ++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 520c3714d3d529dbcd2df4d4cc4d750db2a7a53c..8add11b963b269b11aedcfb8af4d6e063d18e240 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3821,62 +3821,133 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_select_previous(cx: &mut gpui::TestAppContext) { +async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - { - // `Select previous` without a selection (selects wordwise) - let mut cx = EditorTestContext::new(cx).await; - cx.set_state("abc\nˇabc abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + let mut cx = EditorTestContext::new(cx).await; + cx.set_state( + r#"let foo = 2; +lˇet foo = 2; +let fooˇ = 2; +let foo = 2; +let foo = ˇ2;"#, + ); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); + cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); - cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); - cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + // noop for multiple selections with different contents + cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); +} - cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); - cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); +#[gpui::test] +async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»"); + let mut cx = EditorTestContext::new(cx).await; + cx.set_state("abc\nˇabc abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); - } - { - // `Select previous` with a selection - let mut cx = EditorTestContext::new(cx).await; - cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); + cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); +} - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); - } +#[gpui::test] +async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + cx.set_state( + r#"let foo = 2; +lˇet foo = 2; +let fooˇ = 2; +let foo = 2; +let foo = ˇ2;"#, + ); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); + + // noop for multiple selections with different contents + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); +} + +#[gpui::test] +async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + + cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + + cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); } #[gpui::test] From 25abe8f981f20b480bc43e8c705db9640ed83662 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 Jan 2024 10:56:57 +0200 Subject: [PATCH 94/98] Fix the tests --- crates/editor/src/editor.rs | 55 +++++++++++++++---------------- crates/editor/src/editor_tests.rs | 6 +++- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 55c31cfa8074734343dd62bb6e138129d77b0d9b..4856170b321fcf6dcdb5907a40c5e578a5d6e5c6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6469,8 +6469,8 @@ impl Editor { self.select_next_state = Some(select_next_state); } else { let mut only_carets = true; - let mut same_letters_selected = true; - let mut selection_query = None; + let mut same_text_selected = true; + let mut selected_text = None; let mut selections_iter = selections.iter().peekable(); while let Some(selection) = selections_iter.next() { @@ -6478,24 +6478,24 @@ impl Editor { only_carets = false; } - if same_letters_selected { - if selection_query.is_none() { - selection_query = + if same_text_selected { + if selected_text.is_none() { + selected_text = Some(buffer.text_for_range(selection.range()).collect::()); } if let Some(next_selection) = selections_iter.peek() { if next_selection.range().len() == selection.range().len() { - let next_query = buffer + let next_selected_text = buffer .text_for_range(next_selection.range()) .collect::(); - if Some(next_query) != selection_query { - same_letters_selected = false; - selection_query = None; + if Some(next_selected_text) != selected_text { + same_text_selected = false; + selected_text = None; } } else { - same_letters_selected = false; - selection_query = None; + same_text_selected = false; + selected_text = None; } } } @@ -6537,9 +6537,9 @@ impl Editor { } else { self.select_next_state = None; } - } else if let Some(query) = selection_query { + } else if let Some(selected_text) = selected_text { self.select_next_state = Some(SelectNextState { - query: AhoCorasick::new(&[query])?, + query: AhoCorasick::new(&[selected_text])?, wordwise: false, done: false, }); @@ -6585,7 +6585,6 @@ impl Editor { Ok(()) } - // TODO kb test both select_next and select_previous pub fn select_previous( &mut self, action: &SelectPrevious, @@ -6646,8 +6645,8 @@ impl Editor { self.select_prev_state = Some(select_prev_state); } else { let mut only_carets = true; - let mut same_letters_selected = true; - let mut selection_query = None; + let mut same_text_selected = true; + let mut selected_text = None; let mut selections_iter = selections.iter().peekable(); while let Some(selection) = selections_iter.next() { @@ -6655,24 +6654,24 @@ impl Editor { only_carets = false; } - if same_letters_selected { - if selection_query.is_none() { - selection_query = + if same_text_selected { + if selected_text.is_none() { + selected_text = Some(buffer.text_for_range(selection.range()).collect::()); } if let Some(next_selection) = selections_iter.peek() { if next_selection.range().len() == selection.range().len() { - let next_query = buffer + let next_selected_text = buffer .text_for_range(next_selection.range()) .collect::(); - if Some(next_query) != selection_query { - same_letters_selected = false; - selection_query = None; + if Some(next_selected_text) != selected_text { + same_text_selected = false; + selected_text = None; } } else { - same_letters_selected = false; - selection_query = None; + same_text_selected = false; + selected_text = None; } } } @@ -6698,7 +6697,7 @@ impl Editor { .collect::(); let is_empty = query.is_empty(); let select_state = SelectNextState { - query: AhoCorasick::new(&[query])?, + query: AhoCorasick::new(&[query.chars().rev().collect::()])?, wordwise: true, done: is_empty, }; @@ -6716,9 +6715,9 @@ impl Editor { self.change_selections(Some(Autoscroll::newest()), cx, |s| { s.select(selections); }); - } else if let Some(query) = selection_query { + } else if let Some(selected_text) = selected_text { self.select_prev_state = Some(SelectNextState { - query: AhoCorasick::new(&[query])?, + query: AhoCorasick::new(&[selected_text.chars().rev().collect::()])?, wordwise: false, done: false, }); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 8add11b963b269b11aedcfb8af4d6e063d18e240..a6e3d19995c2126e57b698178e8d0cac1bc264c5 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3882,7 +3882,11 @@ async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) { cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); } #[gpui::test] From 074966427d1983d655626e5e8df5d719b652f5ae Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 Jan 2024 11:23:34 +0200 Subject: [PATCH 95/98] Fix previous theme not being applied on startup --- crates/theme/src/theme.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3b158ab9bb7f558f21ae72a64437b2b9f0909f9d..92a5877f514ead1143b89b4d6d217accc153d767 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -62,6 +62,10 @@ pub enum LoadThemes { pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { cx.set_global(ThemeRegistry::default()); + match themes_to_load { + LoadThemes::JustBase => (), + LoadThemes::All => cx.global_mut::().load_user_themes(), + } ThemeSettings::register(cx); let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; @@ -73,11 +77,6 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { } }) .detach(); - - match themes_to_load { - LoadThemes::JustBase => (), - LoadThemes::All => cx.global_mut::().load_user_themes(), - } } pub trait ActiveTheme { From 03bfe3ef80eb2f2af6932f6f0f9117a430328ecd Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 16 Jan 2024 12:01:54 +0100 Subject: [PATCH 96/98] Call CGGetActiveDisplayList once to avoid panic Previously we called CGGetActiveDisplayList twice: once to get the number of displays and then to get the displays. We saw a panic due to no displays being returned here. As a first attempt to fix the panic, we're reducing the amount of calls to CGGetActiveDisplayList and just do one with the trade-off being that we pre-allocate 32 pointers in a Vec. --- crates/gpui/src/platform/mac/display.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index 25e0921fee28efd25c0c1ddf88d6df84318a19aa..ba15fbd90d8c05284049023f515f2539a5f8d8db 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -33,17 +33,20 @@ impl MacDisplay { /// Obtains an iterator over all currently active system displays. pub fn all() -> impl Iterator { unsafe { - let mut display_count: u32 = 0; - let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count); + // We're assuming there aren't more than 32 displays connected to the system. + let mut displays = Vec::with_capacity(32); + let mut display_count = 0; + let result = CGGetActiveDisplayList( + displays.capacity() as u32, + displays.as_mut_ptr(), + &mut display_count, + ); if result == 0 { - let mut displays = Vec::with_capacity(display_count as usize); - CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count); displays.set_len(display_count as usize); - displays.into_iter().map(MacDisplay) } else { - panic!("Failed to get active display list"); + panic!("Failed to get active display list. Result: {result}"); } } } From f938bae0a2122b5244a19bfef5d3b3b4c1a5335b Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 16 Jan 2024 14:23:10 +0100 Subject: [PATCH 97/98] Use NSScreen to fetch primary display According to Chromium source, `NSScreen::screens` should always get us one display. We made this change because we ran into panics caused by the previous `unwrap()` when `CGGetActiveDisplayList` might return an empty list. --- crates/gpui/src/platform/mac/display.rs | 23 ++++++++++++++++++++++- crates/gpui/src/platform/mac/window.rs | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index ba15fbd90d8c05284049023f515f2539a5f8d8db..2b72c335c80e80f700d6caa25fd64d5c8bf4f414 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -1,10 +1,16 @@ use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; use anyhow::Result; +use cocoa::{ + appkit::NSScreen, + base::{id, nil}, + foundation::{NSDictionary, NSString}, +}; use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}; use core_graphics::{ display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList}, geometry::{CGPoint, CGRect, CGSize}, }; +use objc::{msg_send, sel, sel_impl}; use std::any::Any; use uuid::Uuid; @@ -27,7 +33,22 @@ impl MacDisplay { /// Get the primary screen - the one with the menu bar, and whose bottom left /// corner is at the origin of the AppKit coordinate system. pub fn primary() -> Self { - Self::all().next().unwrap() + // Instead of iterating through all active systems displays via `all()` we use the first + // NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList` + // will always return a list of active displays (machine might be sleeping). + // + // The following is what Chromium does too: + // + // https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56 + unsafe { + let screens = NSScreen::screens(nil); + let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0); + let device_description = NSScreen::deviceDescription(screen); + let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber"); + let screen_number = device_description.objectForKey_(screen_number_key); + let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue]; + Self(screen_number) + } } /// Obtains an iterator over all currently active system displays. diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 4c71141cdce41b1492b48bd00048255d5763e535..c364021281a3690635713bd756c520b1d5f3558b 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -484,7 +484,7 @@ impl MacWindow { let display = options .display_id - .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id)) + .and_then(MacDisplay::find_by_id) .unwrap_or_else(MacDisplay::primary); let mut target_screen = nil; From 4e0c8dcea99ad6a71210760539ddfcc6fef3a94a Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 Jan 2024 10:44:24 -0500 Subject: [PATCH 98/98] Revert "Use taffy to retrieve the parent for a given layout node" This reverts commit 5904bcf1c20638d63b244a1b2b038ec9a664ba1c. Co-Authored-By: Antonio Scandurra --- Cargo.lock | 7 +++--- crates/gpui/Cargo.toml | 2 +- crates/gpui/src/taffy.rs | 46 ++++++++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 090980ac3eb0ebba8d4e12ad9b65c9aff5b1682e..7b1fe5144c5b95c0adc0c02a2c28dd3cec44fa09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3116,9 +3116,9 @@ dependencies = [ [[package]] name = "grid" -version = "0.13.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" +checksum = "1df00eed8d1f0db937f6be10e46e8072b0671accb504cf0f959c5c52c679f5b9" [[package]] name = "h2" @@ -7703,12 +7703,11 @@ dependencies = [ [[package]] name = "taffy" version = "0.3.11" -source = "git+https://github.com/zed-industries/taffy?rev=5e6c2d23e70e9f2156911d11050cb686362ba277#5e6c2d23e70e9f2156911d11050cb686362ba277" +source = "git+https://github.com/DioxusLabs/taffy?rev=1876f72bee5e376023eaa518aa7b8a34c769bd1b#1876f72bee5e376023eaa518aa7b8a34c769bd1b" dependencies = [ "arrayvec 0.7.4", "grid", "num-traits", - "serde", "slotmap", ] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 333f5a1649f3c73ec8a31664dfc7a74d88158cb3..ee7549287378f537de71b08abf59bc6af2ab4a98 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -46,7 +46,7 @@ serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true smol.workspace = true -taffy = { git = "https://github.com/zed-industries/taffy", rev = "5e6c2d23e70e9f2156911d11050cb686362ba277" } +taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" } thiserror.workspace = true time.workspace = true tiny-skia = "0.5" diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 40098b090bc6d2156fbaaf0d1fee7ff6e210b8e7..26d5a2e69ea2dcc8e1664b4fa8ef885f84e8feeb 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -6,13 +6,16 @@ use collections::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ - AvailableSpace as TaffyAvailableSpace, NodeId, Point as TaffyPoint, Rect as TaffyRect, - Size as TaffySize, TaffyTree, TraversePartialTree, + geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, + style::AvailableSpace as TaffyAvailableSpace, + tree::NodeId, + Taffy, }; pub struct TaffyLayoutEngine { - tree: TaffyTree, + taffy: Taffy, styles: FxHashMap, + children_to_parents: FxHashMap, absolute_layout_bounds: FxHashMap>, computed_layouts: FxHashSet, nodes_to_measure: FxHashMap< @@ -32,8 +35,9 @@ static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by constructi impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { - tree: TaffyTree::new(), + taffy: Taffy::new(), styles: FxHashMap::default(), + children_to_parents: FxHashMap::default(), absolute_layout_bounds: FxHashMap::default(), computed_layouts: FxHashSet::default(), nodes_to_measure: FxHashMap::default(), @@ -41,7 +45,8 @@ impl TaffyLayoutEngine { } pub fn clear(&mut self) { - self.tree.clear(); + self.taffy.clear(); + self.children_to_parents.clear(); self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); self.nodes_to_measure.clear(); @@ -60,16 +65,21 @@ impl TaffyLayoutEngine { ) -> LayoutId { let taffy_style = style.to_taffy(rem_size); let layout_id = if children.is_empty() { - self.tree + self.taffy .new_leaf(taffy_style) .expect(EXPECT_MESSAGE) .into() } else { - self.tree + let parent_id = self + .taffy // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. .new_with_children(taffy_style, unsafe { std::mem::transmute(children) }) .expect(EXPECT_MESSAGE) - .into() + .into(); + for child_id in children { + self.children_to_parents.insert(*child_id, parent_id); + } + parent_id }; self.styles.insert(layout_id, style.clone()); layout_id @@ -86,7 +96,7 @@ impl TaffyLayoutEngine { let taffy_style = style.to_taffy(rem_size); let layout_id = self - .tree + .taffy .new_leaf_with_context(taffy_style, ()) .expect(EXPECT_MESSAGE) .into(); @@ -100,7 +110,7 @@ impl TaffyLayoutEngine { fn count_all_children(&self, parent: LayoutId) -> anyhow::Result { let mut count = 0; - for child in self.tree.children(parent.0)? { + for child in self.taffy.children(parent.0)? { // Count this child. count += 1; @@ -116,12 +126,12 @@ impl TaffyLayoutEngine { fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result { println!( "{parent:?} at depth {depth} has {} children", - self.tree.child_count(parent.0) + self.taffy.child_count(parent.0)? ); let mut max_child_depth = 0; - for child in self.tree.children(parent.0)? { + for child in self.taffy.children(parent.0)? { max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?); } @@ -133,7 +143,7 @@ impl TaffyLayoutEngine { fn get_edges(&self, parent: LayoutId) -> anyhow::Result> { let mut edges = Vec::new(); - for child in self.tree.children(parent.0)? { + for child in self.taffy.children(parent.0)? { edges.push((parent, LayoutId(child))); edges.extend(self.get_edges(LayoutId(child))?); @@ -166,7 +176,7 @@ impl TaffyLayoutEngine { while let Some(id) = stack.pop() { self.absolute_layout_bounds.remove(&id); stack.extend( - self.tree + self.taffy .children(id.into()) .expect(EXPECT_MESSAGE) .into_iter() @@ -176,7 +186,7 @@ impl TaffyLayoutEngine { } // let started_at = std::time::Instant::now(); - self.tree + self.taffy .compute_layout_with_measure( id.into(), available_space.into(), @@ -203,14 +213,14 @@ impl TaffyLayoutEngine { return layout; } - let layout = self.tree.layout(id.into()).expect(EXPECT_MESSAGE); + let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE); let mut bounds = Bounds { origin: layout.location.into(), size: layout.size.into(), }; - if let Some(parent_id) = self.tree.parent(id.0) { - let parent_bounds = self.layout_bounds(parent_id.into()); + if let Some(parent_id) = self.children_to_parents.get(&id).copied() { + let parent_bounds = self.layout_bounds(parent_id); bounds.origin += parent_bounds.origin; } self.absolute_layout_bounds.insert(id, bounds);