mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-14 14:11:34 +00:00
Render code actions context menu
This commit is contained in:
parent
93a3f4b615
commit
025e83c1ec
1 changed files with 112 additions and 20 deletions
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue