From 7b284189799585f01bff34e0446e52976910b5dd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Feb 2022 16:47:40 +0100 Subject: [PATCH] Allow deployment of code actions from indicator Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 160 ++++++++++++++++++++++------------- crates/editor/src/element.rs | 9 +- 2 files changed, 106 insertions(+), 63 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 03c40d29fc..442e16afa2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -125,7 +125,7 @@ action!(FoldSelectedRanges); action!(Scroll, Vector2F); action!(Select, SelectPhase); action!(ShowCompletions); -action!(ShowCodeActions); +action!(ShowCodeActions, bool); action!(ConfirmCompletion, Option); action!(ConfirmCodeAction, Option); @@ -251,7 +251,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, - available_code_actions: Option, + available_code_actions: Option<(ModelHandle, Arc<[CodeAction]>)>, code_actions_task: Option>, } @@ -508,10 +508,15 @@ impl ContextMenu { } } - fn render(&self, build_settings: BuildSettings, cx: &AppContext) -> ElementBox { + fn render( + &self, + cursor_position: DisplayPoint, + build_settings: BuildSettings, + cx: &AppContext, + ) -> (DisplayPoint, ElementBox) { match self { - ContextMenu::Completions(menu) => menu.render(build_settings, cx), - ContextMenu::CodeActions(menu) => menu.render(build_settings, cx), + ContextMenu::Completions(menu) => (cursor_position, menu.render(build_settings, cx)), + ContextMenu::CodeActions(menu) => menu.render(cursor_position, build_settings, cx), } } } @@ -664,6 +669,7 @@ struct CodeActionsMenu { buffer: ModelHandle, selected_item: usize, list: UniformListState, + deployed_from_indicator: bool, } impl CodeActionsMenu { @@ -685,51 +691,63 @@ impl CodeActionsMenu { !self.actions.is_empty() } - fn render(&self, build_settings: BuildSettings, cx: &AppContext) -> ElementBox { + fn render( + &self, + mut cursor_position: DisplayPoint, + build_settings: BuildSettings, + cx: &AppContext, + ) -> (DisplayPoint, ElementBox) { enum ActionTag {} let settings = build_settings(cx); let actions = self.actions.clone(); let selected_item = self.selected_item; - UniformList::new(self.list.clone(), actions.len(), move |range, items, cx| { - let settings = build_settings(cx); - let start_ix = range.start; - for (ix, action) in actions[range].iter().enumerate() { - let item_ix = start_ix + ix; - items.push( - MouseEventHandler::new::(item_ix, cx, |state, _| { - let item_style = if item_ix == selected_item { - settings.style.autocomplete.selected_item - } else if state.hovered { - settings.style.autocomplete.hovered_item - } else { - settings.style.autocomplete.item - }; + let element = + UniformList::new(self.list.clone(), actions.len(), move |range, items, cx| { + let settings = build_settings(cx); + let start_ix = range.start; + for (ix, action) in actions[range].iter().enumerate() { + let item_ix = start_ix + ix; + items.push( + MouseEventHandler::new::(item_ix, cx, |state, _| { + let item_style = if item_ix == selected_item { + settings.style.autocomplete.selected_item + } else if state.hovered { + settings.style.autocomplete.hovered_item + } else { + settings.style.autocomplete.item + }; - Text::new(action.lsp_action.title.clone(), settings.style.text.clone()) - .with_soft_wrap(false) - .contained() - .with_style(item_style) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(move |cx| { - cx.dispatch_action(ConfirmCodeAction(Some(item_ix))); - }) - .boxed(), - ); - } - }) - .with_width_from_item( - self.actions - .iter() - .enumerate() - .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) - .map(|(ix, _)| ix), - ) - .contained() - .with_style(settings.style.autocomplete.container) - .boxed() + Text::new(action.lsp_action.title.clone(), settings.style.text.clone()) + .with_soft_wrap(false) + .contained() + .with_style(item_style) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_mouse_down(move |cx| { + cx.dispatch_action(ConfirmCodeAction(Some(item_ix))); + }) + .boxed(), + ); + } + }) + .with_width_from_item( + self.actions + .iter() + .enumerate() + .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) + .map(|(ix, _)| ix), + ) + .contained() + .with_style(settings.style.autocomplete.container) + .boxed(); + + if self.deployed_from_indicator { + *cursor_position.column_mut() = 0; + } + + (cursor_position, element) } } @@ -2060,7 +2078,11 @@ impl Editor { })) } - fn show_code_actions(&mut self, _: &ShowCodeActions, cx: &mut ViewContext) { + fn show_code_actions( + &mut self, + &ShowCodeActions(deployed_from_indicator): &ShowCodeActions, + cx: &mut ViewContext, + ) { let mut task = self.code_actions_task.take(); cx.spawn_weak(|this, mut cx| async move { while let Some(prev_task) = task { @@ -2073,8 +2095,17 @@ impl Editor { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { if this.focused { - if let Some(menu) = this.available_code_actions.clone() { - this.show_context_menu(ContextMenu::CodeActions(menu), cx); + if let Some((buffer, actions)) = this.available_code_actions.clone() { + this.show_context_menu( + ContextMenu::CodeActions(CodeActionsMenu { + buffer, + actions, + selected_item: Default::default(), + list: Default::default(), + deployed_from_indicator, + }), + cx, + ); } } }) @@ -2176,14 +2207,22 @@ impl Editor { })) } - pub fn render_code_actions_indicator(&self, cx: &AppContext) -> Option { + pub fn render_code_actions_indicator(&self, cx: &mut ViewContext) -> Option { if self.available_code_actions.is_some() { + enum Tag {} let style = (self.build_settings)(cx).style; Some( - Svg::new("icons/zap.svg") - .with_color(style.code_actions_indicator) - .aligned() - .boxed(), + MouseEventHandler::new::(0, cx, |_, _| { + Svg::new("icons/zap.svg") + .with_color(style.code_actions_indicator) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .with_padding(Padding::uniform(3.)) + .on_mouse_down(|cx| { + cx.dispatch_action(ShowCodeActions(true)); + }) + .boxed(), ) } else { None @@ -2196,10 +2235,14 @@ impl Editor { .map_or(false, |menu| menu.visible()) } - pub fn render_context_menu(&self, cx: &AppContext) -> Option { + pub fn render_context_menu( + &self, + cursor_position: DisplayPoint, + cx: &AppContext, + ) -> Option<(DisplayPoint, ElementBox)> { self.context_menu .as_ref() - .map(|menu| menu.render(self.build_settings.clone(), cx)) + .map(|menu| menu.render(cursor_position, self.build_settings.clone(), cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { @@ -4418,12 +4461,7 @@ impl Editor { if actions.is_empty() { None } else { - Some(CodeActionsMenu { - actions: actions.into(), - buffer, - selected_item: 0, - list: Default::default(), - }) + Some((buffer, actions.into())) } }); cx.notify(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1965f76c88..f72320be42 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -955,8 +955,7 @@ impl Element for EditorElement { if (start_row..end_row).contains(&newest_selection_head.row()) { if view.context_menu_visible() { - let list = view.render_context_menu(cx).unwrap(); - context_menu = Some((newest_selection_head, list)); + context_menu = view.render_context_menu(newest_selection_head, cx); } code_actions_indicator = view @@ -1075,6 +1074,12 @@ impl Element for EditorElement { } } + if let Some((_, indicator)) = &mut layout.code_actions_indicator { + if indicator.dispatch_event(event, cx) { + return true; + } + } + match event { Event::LeftMouseDown { position,