Cargo.lock 🔗
@@ -2781,6 +2781,7 @@ dependencies = [
"tree-sitter-html",
"tree-sitter-rust",
"tree-sitter-typescript",
+ "ui2",
"unindent",
"util",
"workspace2",
Antonio Scandurra and Nathan created
Co-Authored-By: Nathan <nathan@zed.dev>
Cargo.lock | 1
crates/editor2/Cargo.toml | 1
crates/editor2/src/editor.rs | 137 +++++++++++--------------
crates/editor2/src/element.rs | 89 ++++++++--------
crates/gpui2/src/element.rs | 93 +++++++++++++++-
crates/gpui2/src/taffy.rs | 31 +++++
crates/gpui2/src/window.rs | 2
crates/ui2/src/components/icon_button.rs | 1
8 files changed, 227 insertions(+), 128 deletions(-)
@@ -2781,6 +2781,7 @@ dependencies = [
"tree-sitter-html",
"tree-sitter-rust",
"tree-sitter-typescript",
+ "ui2",
"unindent",
"util",
"workspace2",
@@ -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" }
@@ -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<Self>) {
- // 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<Self>) {
+ 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<Self>,
- // ) -> Option<AnyElement<Self>> {
- // if self.available_code_actions.is_some() {
- // enum CodeActions {}
- // Some(
- // MouseEventHandler::new::<CodeActions, _>(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<Self>,
+ ) -> Option<AnyElement<Self>> {
+ 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,
@@ -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<Pixels>,
- layout: &LayoutState,
+ layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
@@ -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<Editor> for EditorElement {
element_state: &mut Self::ElementState,
cx: &mut gpui::ViewContext<Editor>,
) {
- 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<Editor> 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<Editor>)>,
- // code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
+ code_actions_indicator: Option<CodeActionsIndicator>,
// hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
// fold_indicators: Vec<Option<AnyElement<Editor>>>,
tab_invisible: Line,
space_invisible: Line,
}
+struct CodeActionsIndicator {
+ row: u32,
+ element: AnyElement<Editor>,
+}
+
struct PositionMap {
size: Size<Pixels>,
line_height: Pixels,
@@ -63,6 +63,19 @@ trait ElementObject<V> {
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
+ fn measure(
+ &mut self,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Size<Pixels>;
+ fn draw(
+ &mut self,
+ origin: Point<Pixels>,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ );
}
struct RenderedElement<V: 'static, E: Element<V>> {
@@ -81,6 +94,11 @@ enum ElementRenderPhase<V> {
layout_id: LayoutId,
frame_state: Option<V>,
},
+ LayoutComputed {
+ layout_id: LayoutId,
+ available_space: Size<AvailableSpace>,
+ frame_state: Option<V>,
+ },
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<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Size<Pixels> {
+ 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<Pixels>,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ self.measure(available_space, view_state, cx);
+ cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx))
+ }
}
pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
@@ -206,10 +287,7 @@ impl<V> AnyElement<V> {
view_state: &mut V,
cx: &mut ViewContext<V>,
) -> Size<Pixels> {
- 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<V> AnyElement<V> {
view_state: &mut V,
cx: &mut ViewContext<V>,
) {
- 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)
}
}
@@ -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<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
+ computed_layouts: HashSet<LayoutId>,
}
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<AvailableSpace>) {
+ // 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),
@@ -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();
@@ -98,6 +98,7 @@ impl<V: 'static> IconButton<V> {
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);
});
}