Render code actions indicator

Co-Authored-By: Nathan <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2023-11-09 18:43:26 +01:00
parent cfee1401ed
commit 5d15886675
8 changed files with 225 additions and 126 deletions

1
Cargo.lock generated
View file

@ -2781,6 +2781,7 @@ dependencies = [
"tree-sitter-html", "tree-sitter-html",
"tree-sitter-rust", "tree-sitter-rust",
"tree-sitter-typescript", "tree-sitter-typescript",
"ui2",
"unindent", "unindent",
"util", "util",
"workspace2", "workspace2",

View file

@ -44,6 +44,7 @@ snippet = { path = "../snippet" }
sum_tree = { path = "../sum_tree" } sum_tree = { path = "../sum_tree" }
text = { package="text2", path = "../text2" } text = { package="text2", path = "../text2" }
theme = { package="theme2", path = "../theme2" } theme = { package="theme2", path = "../theme2" }
ui2 = { package = "ui2", path = "../ui2" }
util = { path = "../util" } util = { path = "../util" }
sqlez = { path = "../sqlez" } sqlez = { path = "../sqlez" }
workspace = { package = "workspace2", path = "../workspace2" } workspace = { package = "workspace2", path = "../workspace2" }

View file

@ -40,9 +40,9 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use git::diff_hunk_to_display; use git::diff_hunk_to_display;
use gpui::{ use gpui::{
action, actions, point, px, relative, rems, size, AnyElement, AppContext, BackgroundExecutor, action, actions, point, px, relative, rems, size, AnyElement, AppContext, BackgroundExecutor,
Bounds, ClipboardItem, Context, DispatchContext, EventEmitter, FocusHandle, FontFeatures, Bounds, ClipboardItem, Component, Context, DispatchContext, EventEmitter, FocusHandle,
FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render, Subscription, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render,
Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@ -95,6 +95,7 @@ use text::{OffsetUtf16, Rope};
use theme::{ use theme::{
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
}; };
use ui2::IconButton;
use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{ use workspace::{
item::ItemEvent, searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, 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>) { pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
// let mut context_menu = self.context_menu.write(); let mut context_menu = self.context_menu.write();
// if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
// *context_menu = None; *context_menu = None;
// cx.notify(); cx.notify();
// return; return;
// } }
// drop(context_menu); drop(context_menu);
// let deployed_from_indicator = action.deployed_from_indicator; let deployed_from_indicator = action.deployed_from_indicator;
// let mut task = self.code_actions_task.take(); let mut task = self.code_actions_task.take();
// cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
// while let Some(prev_task) = task { while let Some(prev_task) = task {
// prev_task.await; prev_task.await;
// task = this.update(&mut cx, |this, _| this.code_actions_task.take())?; task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
// } }
// this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
// if this.focused { if this.focus_handle.is_focused(cx) {
// if let Some((buffer, actions)) = this.available_code_actions.clone() { if let Some((buffer, actions)) = this.available_code_actions.clone() {
// this.completion_tasks.clear(); this.completion_tasks.clear();
// this.discard_copilot_suggestion(cx); this.discard_copilot_suggestion(cx);
// *this.context_menu.write() = *this.context_menu.write() =
// Some(ContextMenu::CodeActions(CodeActionsMenu { Some(ContextMenu::CodeActions(CodeActionsMenu {
// buffer, buffer,
// actions, actions,
// selected_item: Default::default(), selected_item: Default::default(),
// list: Default::default(), list: Default::default(),
// deployed_from_indicator, deployed_from_indicator,
// })); }));
// } }
// } }
// })?; })?;
// Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
// }) })
// .detach_and_log_err(cx); .detach_and_log_err(cx);
// } }
// pub fn confirm_code_action( // pub fn confirm_code_action(
// workspace: &mut Workspace, // workspace: &mut Workspace,
@ -4390,41 +4391,29 @@ impl Editor {
self.discard_copilot_suggestion(cx); self.discard_copilot_suggestion(cx);
} }
// pub fn render_code_actions_indicator( pub fn render_code_actions_indicator(
// &self, &self,
// style: &EditorStyle, style: &EditorStyle,
// is_active: bool, is_active: bool,
// cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
// ) -> Option<AnyElement<Self>> { ) -> Option<AnyElement<Self>> {
// if self.available_code_actions.is_some() { if self.available_code_actions.is_some() {
// enum CodeActions {} Some(
// Some( IconButton::new("code_actions", ui2::Icon::Bolt)
// MouseEventHandler::new::<CodeActions, _>(0, cx, |state, _| { .on_click(|editor: &mut Editor, cx| {
// Svg::new("icons/bolt.svg").with_color( editor.toggle_code_actions(
// style &ToggleCodeActions {
// .code_actions deployed_from_indicator: true,
// .indicator },
// .in_state(is_active) cx,
// .style_for(state) );
// .color, })
// ) .render(),
// }) )
// .with_cursor_style(CursorStyle::PointingHand) } else {
// .with_padding(Padding::uniform(3.)) None
// .on_down(MouseButton::Left, |_, this, cx| { }
// this.toggle_code_actions( }
// &ToggleCodeActions {
// deployed_from_indicator: true,
// },
// cx,
// );
// })
// .into_any(),
// )
// } else {
// None
// }
// }
// pub fn render_fold_indicators( // pub fn render_fold_indicators(
// &self, // &self,

View file

@ -15,7 +15,7 @@ use crate::{
use anyhow::Result; use anyhow::Result;
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap};
use gpui::{ 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, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton, InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
@ -447,7 +447,7 @@ impl EditorElement {
fn paint_gutter( fn paint_gutter(
&mut self, &mut self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
layout: &LayoutState, layout: &mut LayoutState,
editor: &mut Editor, editor: &mut Editor,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
@ -495,14 +495,21 @@ impl EditorElement {
// } // }
// } // }
// todo!("code actions indicator") if let Some(indicator) = layout.code_actions_indicator.as_mut() {
// if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { let available_space = size(
// let mut x = 0.; AvailableSpace::MinContent,
// let mut y = *row as f32 * line_height - scroll_top; AvailableSpace::Definite(line_height),
// x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x) / 2.; );
// y += (line_height - indicator.size().y) / 2.; let indicator_size = indicator.element.measure(available_space, editor, cx);
// indicator.paint(bounds.origin + point(x, y), visible_bounds, 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( fn paint_diff_hunks(
@ -1776,24 +1783,27 @@ impl EditorElement {
// todo!("context menu") // todo!("context menu")
// let mut context_menu = None; // let mut context_menu = None;
// let mut code_actions_indicator = None; let mut code_actions_indicator = None;
// if let Some(newest_selection_head) = newest_selection_head { if let Some(newest_selection_head) = newest_selection_head {
// if (start_row..end_row).contains(&newest_selection_head.row()) { if (start_row..end_row).contains(&newest_selection_head.row()) {
// if editor.context_menu_visible() { // if editor.context_menu_visible() {
// context_menu = // context_menu =
// editor.render_context_menu(newest_selection_head, style.clone(), cx); // editor.render_context_menu(newest_selection_head, style.clone(), cx);
// } // }
// let active = matches!( let active = matches!(
// editor.context_menu.read().as_ref(), editor.context_menu.read().as_ref(),
// Some(crate::ContextMenu::CodeActions(_)) Some(crate::ContextMenu::CodeActions(_))
// ); );
// code_actions_indicator = editor code_actions_indicator = editor
// .render_code_actions_indicator(&style, active, cx) .render_code_actions_indicator(&style, active, cx)
// .map(|indicator| (newest_selection_head.row(), indicator)); .map(|element| CodeActionsIndicator {
// } row: newest_selection_head.row(),
// } element,
});
}
}
let visible_rows = start_row..start_row + line_layouts.len() as u32; let visible_rows = start_row..start_row + line_layouts.len() as u32;
// todo!("hover") // 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") // todo!("fold indicators")
// for fold_indicator in fold_indicators.iter_mut() { // for fold_indicator in fold_indicators.iter_mut() {
// if let Some(indicator) = fold_indicator.as_mut() { // if let Some(indicator) = fold_indicator.as_mut() {
@ -1942,7 +1940,7 @@ impl EditorElement {
// blocks, // blocks,
selections, selections,
// context_menu, // context_menu,
// code_actions_indicator, code_actions_indicator,
// fold_indicators, // fold_indicators,
tab_invisible, tab_invisible,
space_invisible, space_invisible,
@ -2493,7 +2491,7 @@ impl Element<Editor> for EditorElement {
element_state: &mut Self::ElementState, element_state: &mut Self::ElementState,
cx: &mut gpui::ViewContext<Editor>, 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 { let gutter_bounds = Bounds {
origin: bounds.origin, origin: bounds.origin,
size: layout.gutter_size, size: layout.gutter_size,
@ -2513,7 +2511,7 @@ impl Element<Editor> for EditorElement {
); );
self.paint_background(gutter_bounds, text_bounds, &layout, cx); self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO { 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); self.paint_text(text_bounds, &layout, editor, cx);
let input_handler = ElementInputHandler::new(bounds, cx); let input_handler = ElementInputHandler::new(bounds, cx);
@ -3144,13 +3142,18 @@ pub struct LayoutState {
is_singleton: bool, is_singleton: bool,
max_row: u32, max_row: u32,
// context_menu: Option<(DisplayPoint, AnyElement<Editor>)>, // 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>>)>, // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
// fold_indicators: Vec<Option<AnyElement<Editor>>>, // fold_indicators: Vec<Option<AnyElement<Editor>>>,
tab_invisible: Line, tab_invisible: Line,
space_invisible: Line, space_invisible: Line,
} }
struct CodeActionsIndicator {
row: u32,
element: AnyElement<Editor>,
}
struct PositionMap { struct PositionMap {
size: Size<Pixels>, size: Size<Pixels>,
line_height: Pixels, line_height: Pixels,

View file

@ -63,6 +63,19 @@ trait ElementObject<V> {
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<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 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 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>> { struct RenderedElement<V: 'static, E: Element<V>> {
@ -81,6 +94,11 @@ enum ElementRenderPhase<V> {
layout_id: LayoutId, layout_id: LayoutId,
frame_state: Option<V>, frame_state: Option<V>,
}, },
LayoutComputed {
layout_id: LayoutId,
available_space: Size<AvailableSpace>,
frame_state: Option<V>,
},
Painted, Painted,
} }
@ -137,7 +155,9 @@ where
} }
} }
ElementRenderPhase::Start => panic!("must call initialize before layout"), ElementRenderPhase::Start => panic!("must call initialize before layout"),
ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => { ElementRenderPhase::LayoutRequested { .. }
| ElementRenderPhase::LayoutComputed { .. }
| ElementRenderPhase::Painted => {
panic!("element rendered twice") panic!("element rendered twice")
} }
}; };
@ -154,6 +174,11 @@ where
ElementRenderPhase::LayoutRequested { ElementRenderPhase::LayoutRequested {
layout_id, layout_id,
mut frame_state, mut frame_state,
}
| ElementRenderPhase::LayoutComputed {
layout_id,
mut frame_state,
..
} => { } => {
let bounds = cx.layout_bounds(layout_id); let bounds = cx.layout_bounds(layout_id);
if let Some(id) = self.element.id() { if let Some(id) = self.element.id() {
@ -173,6 +198,62 @@ where
_ => panic!("must call layout before paint"), _ => 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>>); pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
@ -206,10 +287,7 @@ impl<V> AnyElement<V> {
view_state: &mut V, view_state: &mut V,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) -> Size<Pixels> { ) -> Size<Pixels> {
self.initialize(view_state, cx); self.0.measure(available_space, view_state, cx)
let layout_id = self.layout(view_state, cx);
cx.compute_layout(layout_id, available_space);
cx.layout_bounds(layout_id).size
} }
/// Initializes this element and performs layout in the available space, then paints it at the given origin. /// 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, view_state: &mut V,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) { ) {
self.initialize(view_state, cx); self.0.draw(origin, available_space, 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))
} }
} }

View file

@ -1,5 +1,6 @@
use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style}; 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 std::fmt::Debug;
use taffy::{ use taffy::{
geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
@ -12,6 +13,7 @@ pub struct TaffyLayoutEngine {
taffy: Taffy, taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>, children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>, absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
computed_layouts: HashSet<LayoutId>,
} }
static EXPECT_MESSAGE: &'static str = static EXPECT_MESSAGE: &'static str =
@ -23,9 +25,17 @@ impl TaffyLayoutEngine {
taffy: Taffy::new(), taffy: Taffy::new(),
children_to_parents: HashMap::default(), children_to_parents: HashMap::default(),
absolute_layout_bounds: 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( pub fn request_layout(
&mut self, &mut self,
style: &Style, style: &Style,
@ -115,6 +125,7 @@ impl TaffyLayoutEngine {
} }
pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) { 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!("Laying out {} children", self.count_all_children(id)?);
// println!("Max layout depth: {}", self.max_depth(0, 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!("N{} --> N{}", u64::from(a), u64::from(b));
// } // }
// println!(""); // 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(); // let started_at = std::time::Instant::now();
self.taffy self.taffy
@ -397,7 +424,7 @@ where
} }
} }
#[derive(Copy, Clone, Default, Debug)] #[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
pub enum AvailableSpace { pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels /// The amount of space available is the specified number of pixels
Definite(Pixels), Definite(Pixels),

View file

@ -1060,6 +1060,8 @@ impl<'a> WindowContext<'a> {
self.text_system().start_frame(); self.text_system().start_frame();
let window = &mut *self.window; let window = &mut *self.window;
window.layout_engine.clear();
mem::swap(&mut window.previous_frame, &mut window.current_frame); mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame; let frame = &mut window.current_frame;
frame.element_states.clear(); frame.element_states.clear();

View file

@ -98,6 +98,7 @@ impl<V: 'static> IconButton<V> {
if let Some(click_handler) = self.handlers.click.clone() { if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| { button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
cx.stop_propagation();
click_handler(state, cx); click_handler(state, cx);
}); });
} }