Render code actions context menu

This commit is contained in:
Nathan Sobo 2022-02-05 11:04:05 -07:00
parent 93a3f4b615
commit 025e83c1ec

View file

@ -126,6 +126,7 @@ action!(Select, SelectPhase);
action!(ShowCompletions); action!(ShowCompletions);
action!(ShowCodeActions); action!(ShowCodeActions);
action!(ConfirmCompletion, Option<usize>); action!(ConfirmCompletion, Option<usize>);
action!(ConfirmCodeAction, Option<usize>);
pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpener>>) { pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpener>>) {
path_openers.push(Box::new(items::BufferOpener)); path_openers.push(Box::new(items::BufferOpener));
@ -463,36 +464,41 @@ struct SnippetState {
struct InvalidationStack<T>(Vec<T>); struct InvalidationStack<T>(Vec<T>);
enum ContextMenu { enum ContextMenu {
Completion(CompletionMenu), Completions(CompletionsMenu),
CodeActions(CodeActionsMenu),
} }
impl ContextMenu { impl ContextMenu {
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) { fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
match self { match self {
ContextMenu::Completion(menu) => menu.select_prev(cx), ContextMenu::Completions(menu) => menu.select_prev(cx),
ContextMenu::CodeActions(menu) => menu.select_prev(cx),
} }
} }
fn select_next(&mut self, cx: &mut ViewContext<Editor>) { fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
match self { match self {
ContextMenu::Completion(menu) => menu.select_next(cx), ContextMenu::Completions(menu) => menu.select_next(cx),
ContextMenu::CodeActions(menu) => menu.select_next(cx),
} }
} }
fn should_render(&self) -> bool { fn should_render(&self) -> bool {
match self { match self {
ContextMenu::Completion(menu) => menu.should_render(), ContextMenu::Completions(menu) => menu.should_render(),
ContextMenu::CodeActions(menu) => menu.should_render(),
} }
} }
fn render(&self, build_settings: BuildSettings, cx: &AppContext) -> ElementBox { fn render(&self, build_settings: BuildSettings, cx: &AppContext) -> ElementBox {
match self { match self {
ContextMenu::Completion(menu) => menu.render(build_settings, cx), ContextMenu::Completions(menu) => menu.render(build_settings, cx),
ContextMenu::CodeActions(menu) => menu.render(build_settings, cx),
} }
} }
} }
struct CompletionMenu { struct CompletionsMenu {
id: CompletionId, id: CompletionId,
initial_position: Anchor, initial_position: Anchor,
completions: Arc<[Completion<Anchor>]>, completions: Arc<[Completion<Anchor>]>,
@ -502,7 +508,7 @@ struct CompletionMenu {
list: UniformListState, list: UniformListState,
} }
impl CompletionMenu { impl CompletionsMenu {
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) { fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
if self.selected_item > 0 { if self.selected_item > 0 {
self.selected_item -= 1; self.selected_item -= 1;
@ -633,6 +639,79 @@ impl CompletionMenu {
} }
} }
struct CodeActionsMenu {
actions: Arc<[lsp::CodeAction]>,
selected_item: usize,
list: UniformListState,
}
impl CodeActionsMenu {
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
if self.selected_item > 0 {
self.selected_item -= 1;
cx.notify()
}
}
fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
if self.selected_item + 1 < self.actions.len() {
self.selected_item += 1;
cx.notify()
}
}
fn should_render(&self) -> bool {
!self.actions.is_empty()
}
fn render(&self, build_settings: BuildSettings, cx: &AppContext) -> 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::<ActionTag, _, _, _>(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.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.title.chars().count())
.map(|(ix, _)| ix),
)
.contained()
.with_style(settings.style.autocomplete.container)
.boxed()
}
}
#[derive(Debug)] #[derive(Debug)]
struct ActiveDiagnosticGroup { struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>, primary_range: Range<Anchor>,
@ -1794,7 +1873,7 @@ impl Editor {
async move { async move {
let completions = completions.await?; let completions = completions.await?;
let mut menu = CompletionMenu { let mut menu = CompletionsMenu {
id, id,
initial_position: position, initial_position: position,
match_candidates: completions match_candidates: completions
@ -1819,7 +1898,7 @@ impl Editor {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
match this.context_menu.as_ref() { match this.context_menu.as_ref() {
None => {} None => {}
Some(ContextMenu::Completion(prev_menu)) => { Some(ContextMenu::Completions(prev_menu)) => {
if prev_menu.id > menu.id { if prev_menu.id > menu.id {
return; return;
} }
@ -1831,7 +1910,7 @@ impl Editor {
if menu.matches.is_empty() { if menu.matches.is_empty() {
this.hide_completions(cx); this.hide_completions(cx);
} else if this.focused { } else if this.focused {
this.context_menu = Some(ContextMenu::Completion(menu)); this.context_menu = Some(ContextMenu::Completions(menu));
} }
cx.notify(); cx.notify();
@ -1854,17 +1933,29 @@ impl Editor {
let actions = self let actions = self
.buffer .buffer
.update(cx, |buffer, cx| buffer.code_actions(position.clone(), cx)); .update(cx, |buffer, cx| buffer.code_actions(position.clone(), cx));
cx.spawn(|this, cx| async move {
dbg!(actions.await.unwrap()); cx.spawn(|this, mut cx| async move {
let actions = actions.await?;
if !actions.is_empty() {
this.update(&mut cx, |this, cx| {
this.context_menu = Some(ContextMenu::CodeActions(CodeActionsMenu {
actions: actions.into(),
selected_item: 0,
list: UniformListState::default(),
}));
cx.notify();
});
}
Ok::<_, anyhow::Error>(())
}) })
.detach(); .detach_and_log_err(cx);
} }
fn hide_completions(&mut self, cx: &mut ViewContext<Self>) -> Option<CompletionMenu> { fn hide_completions(&mut self, cx: &mut ViewContext<Self>) -> Option<CompletionsMenu> {
cx.notify(); cx.notify();
self.completion_tasks.clear(); self.completion_tasks.clear();
self.context_menu.take().and_then(|menu| { self.context_menu.take().and_then(|menu| {
if let ContextMenu::Completion(menu) = menu { if let ContextMenu::Completions(menu) = menu {
Some(menu) Some(menu)
} else { } else {
None None
@ -4105,12 +4196,13 @@ impl Editor {
} }
} }
let completion_menu = let completion_menu = match self.context_menu.as_mut() {
if let Some(ContextMenu::Completion(menu)) = self.context_menu.as_mut() { Some(ContextMenu::Completions(menu)) => Some(menu),
Some(menu) _ => {
} else { self.context_menu.take();
None None
}; }
};
if let Some((completion_menu, cursor_position)) = completion_menu.zip(new_cursor_position) { if let Some((completion_menu, cursor_position)) = completion_menu.zip(new_cursor_position) {
let cursor_position = cursor_position.to_offset(&buffer); let cursor_position = cursor_position.to_offset(&buffer);