diff --git a/Cargo.lock b/Cargo.lock index ded64052c8e165e4b4ca3955e3382a38874d88bf..ed06172fd8f495bf04e0b09340b7557d5e1ef739 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2781,6 +2781,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-rust", "tree-sitter-typescript", + "ui2", "unindent", "util", "workspace2", diff --git a/crates/editor2/Cargo.toml b/crates/editor2/Cargo.toml index b897110966709ecef886caf8adef36f1973cc4bd..493f10006f734f5c0fd907935981d2cb9a17b9e1 100644 --- a/crates/editor2/Cargo.toml +++ b/crates/editor2/Cargo.toml @@ -44,6 +44,7 @@ snippet = { path = "../snippet" } sum_tree = { path = "../sum_tree" } text = { package="text2", path = "../text2" } theme = { package="theme2", path = "../theme2" } +ui2 = { package = "ui2", path = "../ui2" } util = { path = "../util" } sqlez = { path = "../sqlez" } workspace = { package = "workspace2", path = "../workspace2" } diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 51cd549923827ce21f9220e153f1f983bf5aa95d..752696bcaef3752a8acf0df0516c1ed742978a9e 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -40,9 +40,9 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ action, actions, point, px, relative, rems, size, AnyElement, AppContext, BackgroundExecutor, - Bounds, ClipboardItem, Context, DispatchContext, EventEmitter, FocusHandle, FontFeatures, - FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render, Subscription, - Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, + Bounds, ClipboardItem, Component, Context, DispatchContext, EventEmitter, FocusHandle, + FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render, + Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -95,6 +95,7 @@ use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; +use ui2::IconButton; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ item::ItemEvent, searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, @@ -3846,44 +3847,44 @@ impl Editor { // })) // } - // pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { - // let mut context_menu = self.context_menu.write(); - // if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { - // *context_menu = None; - // cx.notify(); - // return; - // } - // drop(context_menu); - - // let deployed_from_indicator = action.deployed_from_indicator; - // let mut task = self.code_actions_task.take(); - // cx.spawn(|this, mut cx| async move { - // while let Some(prev_task) = task { - // prev_task.await; - // task = this.update(&mut cx, |this, _| this.code_actions_task.take())?; - // } + pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { + let mut context_menu = self.context_menu.write(); + if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { + *context_menu = None; + cx.notify(); + return; + } + drop(context_menu); - // this.update(&mut cx, |this, cx| { - // if this.focused { - // if let Some((buffer, actions)) = this.available_code_actions.clone() { - // this.completion_tasks.clear(); - // this.discard_copilot_suggestion(cx); - // *this.context_menu.write() = - // Some(ContextMenu::CodeActions(CodeActionsMenu { - // buffer, - // actions, - // selected_item: Default::default(), - // list: Default::default(), - // deployed_from_indicator, - // })); - // } - // } - // })?; + let deployed_from_indicator = action.deployed_from_indicator; + let mut task = self.code_actions_task.take(); + cx.spawn(|this, mut cx| async move { + while let Some(prev_task) = task { + prev_task.await; + task = this.update(&mut cx, |this, _| this.code_actions_task.take())?; + } - // Ok::<_, anyhow::Error>(()) - // }) - // .detach_and_log_err(cx); - // } + this.update(&mut cx, |this, cx| { + if this.focus_handle.is_focused(cx) { + if let Some((buffer, actions)) = this.available_code_actions.clone() { + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + *this.context_menu.write() = + Some(ContextMenu::CodeActions(CodeActionsMenu { + buffer, + actions, + selected_item: Default::default(), + list: Default::default(), + deployed_from_indicator, + })); + } + } + })?; + + Ok::<_, anyhow::Error>(()) + }) + .detach_and_log_err(cx); + } // pub fn confirm_code_action( // workspace: &mut Workspace, @@ -4390,41 +4391,29 @@ impl Editor { self.discard_copilot_suggestion(cx); } - // pub fn render_code_actions_indicator( - // &self, - // style: &EditorStyle, - // is_active: bool, - // cx: &mut ViewContext, - // ) -> Option> { - // if self.available_code_actions.is_some() { - // enum CodeActions {} - // Some( - // MouseEventHandler::new::(0, cx, |state, _| { - // Svg::new("icons/bolt.svg").with_color( - // style - // .code_actions - // .indicator - // .in_state(is_active) - // .style_for(state) - // .color, - // ) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .with_padding(Padding::uniform(3.)) - // .on_down(MouseButton::Left, |_, this, cx| { - // this.toggle_code_actions( - // &ToggleCodeActions { - // deployed_from_indicator: true, - // }, - // cx, - // ); - // }) - // .into_any(), - // ) - // } else { - // None - // } - // } + pub fn render_code_actions_indicator( + &self, + style: &EditorStyle, + is_active: bool, + cx: &mut ViewContext, + ) -> Option> { + if self.available_code_actions.is_some() { + Some( + IconButton::new("code_actions", ui2::Icon::Bolt) + .on_click(|editor: &mut Editor, cx| { + editor.toggle_code_actions( + &ToggleCodeActions { + deployed_from_indicator: true, + }, + cx, + ); + }) + .render(), + ) + } else { + None + } + } // pub fn render_fold_indicators( // &self, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 3e77a66936443aa872fc6f196fb71257bfede82f..a447b5647c48eefd9b9c10f9e130d7d8e4d705a6 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -15,7 +15,7 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, + black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton, @@ -447,7 +447,7 @@ impl EditorElement { fn paint_gutter( &mut self, bounds: Bounds, - layout: &LayoutState, + layout: &mut LayoutState, editor: &mut Editor, cx: &mut ViewContext, ) { @@ -495,14 +495,21 @@ impl EditorElement { // } // } - // todo!("code actions indicator") - // if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { - // let mut x = 0.; - // let mut y = *row as f32 * line_height - scroll_top; - // x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x) / 2.; - // y += (line_height - indicator.size().y) / 2.; - // indicator.paint(bounds.origin + point(x, y), visible_bounds, editor, cx); - // } + if let Some(indicator) = layout.code_actions_indicator.as_mut() { + let available_space = size( + AvailableSpace::MinContent, + AvailableSpace::Definite(line_height), + ); + let indicator_size = indicator.element.measure(available_space, editor, cx); + let mut x = Pixels::ZERO; + let mut y = indicator.row as f32 * line_height - scroll_top; + // Center indicator. + x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; + y += (line_height - indicator_size.height) / 2.; + indicator + .element + .draw(bounds.origin + point(x, y), available_space, editor, cx); + } } fn paint_diff_hunks( @@ -1776,24 +1783,27 @@ impl EditorElement { // todo!("context menu") // let mut context_menu = None; - // let mut code_actions_indicator = None; - // if let Some(newest_selection_head) = newest_selection_head { - // if (start_row..end_row).contains(&newest_selection_head.row()) { - // if editor.context_menu_visible() { - // context_menu = - // editor.render_context_menu(newest_selection_head, style.clone(), cx); - // } - - // let active = matches!( - // editor.context_menu.read().as_ref(), - // Some(crate::ContextMenu::CodeActions(_)) - // ); + let mut code_actions_indicator = None; + if let Some(newest_selection_head) = newest_selection_head { + if (start_row..end_row).contains(&newest_selection_head.row()) { + // if editor.context_menu_visible() { + // context_menu = + // editor.render_context_menu(newest_selection_head, style.clone(), cx); + // } + + let active = matches!( + editor.context_menu.read().as_ref(), + Some(crate::ContextMenu::CodeActions(_)) + ); - // code_actions_indicator = editor - // .render_code_actions_indicator(&style, active, cx) - // .map(|indicator| (newest_selection_head.row(), indicator)); - // } - // } + code_actions_indicator = editor + .render_code_actions_indicator(&style, active, cx) + .map(|element| CodeActionsIndicator { + row: newest_selection_head.row(), + element, + }); + } + } let visible_rows = start_row..start_row + line_layouts.len() as u32; // todo!("hover") @@ -1831,18 +1841,6 @@ impl EditorElement { // ); // } - // todo!("code actions") - // if let Some((_, indicator)) = code_actions_indicator.as_mut() { - // indicator.layout( - // SizeConstraint::strict_along( - // Axis::Vertical, - // line_height * style.code_actions.vertical_scale, - // ), - // editor, - // cx, - // ); - // } - // todo!("fold indicators") // for fold_indicator in fold_indicators.iter_mut() { // if let Some(indicator) = fold_indicator.as_mut() { @@ -1942,7 +1940,7 @@ impl EditorElement { // blocks, selections, // context_menu, - // code_actions_indicator, + code_actions_indicator, // fold_indicators, tab_invisible, space_invisible, @@ -2493,7 +2491,7 @@ impl Element for EditorElement { element_state: &mut Self::ElementState, cx: &mut gpui::ViewContext, ) { - let layout = self.compute_layout(editor, cx, bounds); + let mut layout = self.compute_layout(editor, cx, bounds); let gutter_bounds = Bounds { origin: bounds.origin, size: layout.gutter_size, @@ -2513,7 +2511,7 @@ impl Element for EditorElement { ); self.paint_background(gutter_bounds, text_bounds, &layout, cx); if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &layout, editor, cx); + self.paint_gutter(gutter_bounds, &mut layout, editor, cx); } self.paint_text(text_bounds, &layout, editor, cx); let input_handler = ElementInputHandler::new(bounds, cx); @@ -3144,13 +3142,18 @@ pub struct LayoutState { is_singleton: bool, max_row: u32, // context_menu: Option<(DisplayPoint, AnyElement)>, - // code_actions_indicator: Option<(u32, AnyElement)>, + code_actions_indicator: Option, // hover_popovers: Option<(DisplayPoint, Vec>)>, // fold_indicators: Vec>>, tab_invisible: Line, space_invisible: Line, } +struct CodeActionsIndicator { + row: u32, + element: AnyElement, +} + struct PositionMap { size: Size, line_height: Pixels, diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 775b3b8a895215c49476e06d34913b3aa311c695..e7526dfa3a74d444bf6747d51a8b98a1de85d9cf 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -63,6 +63,19 @@ trait ElementObject { fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext); fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext) -> LayoutId; fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext); + fn measure( + &mut self, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) -> Size; + fn draw( + &mut self, + origin: Point, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ); } struct RenderedElement> { @@ -81,6 +94,11 @@ enum ElementRenderPhase { layout_id: LayoutId, frame_state: Option, }, + LayoutComputed { + layout_id: LayoutId, + available_space: Size, + frame_state: Option, + }, Painted, } @@ -137,7 +155,9 @@ where } } ElementRenderPhase::Start => panic!("must call initialize before layout"), - ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => { + ElementRenderPhase::LayoutRequested { .. } + | ElementRenderPhase::LayoutComputed { .. } + | ElementRenderPhase::Painted => { panic!("element rendered twice") } }; @@ -154,6 +174,11 @@ where ElementRenderPhase::LayoutRequested { layout_id, mut frame_state, + } + | ElementRenderPhase::LayoutComputed { + layout_id, + mut frame_state, + .. } => { let bounds = cx.layout_bounds(layout_id); if let Some(id) = self.element.id() { @@ -173,6 +198,62 @@ where _ => panic!("must call layout before paint"), }; } + + fn measure( + &mut self, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) -> Size { + if matches!(&self.phase, ElementRenderPhase::Start) { + self.initialize(view_state, cx); + } + + if matches!(&self.phase, ElementRenderPhase::Initialized { .. }) { + self.layout(view_state, cx); + } + + let layout_id = match &mut self.phase { + ElementRenderPhase::LayoutRequested { + layout_id, + frame_state, + } => { + cx.compute_layout(*layout_id, available_space); + let layout_id = *layout_id; + self.phase = ElementRenderPhase::LayoutComputed { + layout_id, + available_space, + frame_state: frame_state.take(), + }; + layout_id + } + ElementRenderPhase::LayoutComputed { + layout_id, + available_space: prev_available_space, + .. + } => { + if available_space != *prev_available_space { + cx.compute_layout(*layout_id, available_space); + *prev_available_space = available_space; + } + *layout_id + } + _ => panic!("cannot measure after painting"), + }; + + cx.layout_bounds(layout_id).size + } + + fn draw( + &mut self, + origin: Point, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) { + self.measure(available_space, view_state, cx); + cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx)) + } } pub struct AnyElement(Box>); @@ -206,10 +287,7 @@ impl AnyElement { view_state: &mut V, cx: &mut ViewContext, ) -> Size { - self.initialize(view_state, cx); - let layout_id = self.layout(view_state, cx); - cx.compute_layout(layout_id, available_space); - cx.layout_bounds(layout_id).size + self.0.measure(available_space, view_state, cx) } /// Initializes this element and performs layout in the available space, then paints it at the given origin. @@ -220,10 +298,7 @@ impl AnyElement { view_state: &mut V, cx: &mut ViewContext, ) { - self.initialize(view_state, cx); - let layout_id = self.layout(view_state, cx); - cx.compute_layout(layout_id, available_space); - cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx)) + self.0.draw(origin, available_space, view_state, cx) } } diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 9724179eed4735b7959f93865bd234bc3d246823..ea87f73872cd445ee37e530d973d5e0e054a76fd 100644 --- a/crates/gpui2/src/taffy.rs +++ b/crates/gpui2/src/taffy.rs @@ -1,5 +1,6 @@ use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style}; -use collections::HashMap; +use collections::{HashMap, HashSet}; +use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, @@ -12,6 +13,7 @@ pub struct TaffyLayoutEngine { taffy: Taffy, children_to_parents: HashMap, absolute_layout_bounds: HashMap>, + computed_layouts: HashSet, } static EXPECT_MESSAGE: &'static str = @@ -23,9 +25,17 @@ impl TaffyLayoutEngine { taffy: Taffy::new(), children_to_parents: HashMap::default(), absolute_layout_bounds: HashMap::default(), + computed_layouts: HashSet::default(), } } + pub fn clear(&mut self) { + self.taffy.clear(); + self.children_to_parents.clear(); + self.absolute_layout_bounds.clear(); + self.computed_layouts.clear(); + } + pub fn request_layout( &mut self, style: &Style, @@ -115,6 +125,7 @@ impl TaffyLayoutEngine { } pub fn compute_layout(&mut self, id: LayoutId, available_space: Size) { + // Leaving this here until we have a better instrumentation approach. // println!("Laying out {} children", self.count_all_children(id)?); // println!("Max layout depth: {}", self.max_depth(0, id)?); @@ -124,6 +135,22 @@ impl TaffyLayoutEngine { // println!("N{} --> N{}", u64::from(a), u64::from(b)); // } // println!(""); + // + + if !self.computed_layouts.insert(id) { + let mut stack = SmallVec::<[LayoutId; 64]>::new(); + stack.push(id); + while let Some(id) = stack.pop() { + self.absolute_layout_bounds.remove(&id); + stack.extend( + self.taffy + .children(id.into()) + .expect(EXPECT_MESSAGE) + .into_iter() + .map(Into::into), + ); + } + } // let started_at = std::time::Instant::now(); self.taffy @@ -397,7 +424,7 @@ where } } -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)] pub enum AvailableSpace { /// The amount of space available is the specified number of pixels Definite(Pixels), diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index ac7dcf02569e4db6c3c8f92658d95088dcdc4ba5..7b5ce5b7fc418d612a90f64c23ebf44ecc6a1b10 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1060,6 +1060,8 @@ impl<'a> WindowContext<'a> { self.text_system().start_frame(); let window = &mut *self.window; + window.layout_engine.clear(); + mem::swap(&mut window.previous_frame, &mut window.current_frame); let frame = &mut window.current_frame; frame.element_states.clear(); diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index f0dc85b445c326f801d5579d5f4002690e617b75..91653ea8cdd0158d88294886b826c680b5cbf9b3 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -98,6 +98,7 @@ impl IconButton { if let Some(click_handler) = self.handlers.click.clone() { button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| { + cx.stop_propagation(); click_handler(state, cx); }); }