1use std::sync::Arc;
2
3use anyhow::Context as _;
4use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
5use language::Language;
6use multi_buffer::MultiBuffer;
7use project::lsp_ext_command::ExpandMacro;
8use text::ToPointUtf16;
9
10use crate::{element::register_action, Editor, ExpandMacroRecursively};
11
12pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
13 let is_rust_related = editor.update(cx, |editor, cx| {
14 editor
15 .buffer()
16 .read(cx)
17 .all_buffers()
18 .iter()
19 .any(|b| match b.read(cx).language() {
20 Some(l) => is_rust_language(l),
21 None => false,
22 })
23 });
24
25 if is_rust_related {
26 register_action(editor, cx, expand_macro_recursively);
27 }
28}
29
30pub fn expand_macro_recursively(
31 editor: &mut Editor,
32 _: &ExpandMacroRecursively,
33 cx: &mut ViewContext<'_, Editor>,
34) {
35 if editor.selections.count() == 0 {
36 return;
37 }
38 let Some(project) = &editor.project else {
39 return;
40 };
41 let Some(workspace) = editor.workspace() else {
42 return;
43 };
44
45 let multibuffer = editor.buffer().read(cx);
46
47 let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
48 .selections
49 .disjoint_anchors()
50 .into_iter()
51 .filter(|selection| selection.start == selection.end)
52 .filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
53 .filter_map(|(buffer_id, trigger_anchor)| {
54 let buffer = multibuffer.buffer(buffer_id)?;
55 let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
56 if !is_rust_language(&rust_language) {
57 return None;
58 }
59 Some((trigger_anchor, rust_language, buffer))
60 })
61 .find_map(|(trigger_anchor, rust_language, buffer)| {
62 project
63 .read(cx)
64 .language_servers_for_buffer(buffer.read(cx), cx)
65 .find_map(|(adapter, server)| {
66 if adapter.name.0.as_ref() == "rust-analyzer" {
67 Some((
68 trigger_anchor,
69 Arc::clone(&rust_language),
70 server.server_id(),
71 buffer.clone(),
72 ))
73 } else {
74 None
75 }
76 })
77 })
78 else {
79 return;
80 };
81
82 let project = project.clone();
83 let buffer_snapshot = buffer.read(cx).snapshot();
84 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
85 let expand_macro_task = project.update(cx, |project, cx| {
86 project.request_lsp(
87 buffer,
88 project::LanguageServerToQuery::Other(server_to_query),
89 ExpandMacro { position },
90 cx,
91 )
92 });
93 cx.spawn(|_editor, mut cx| async move {
94 let macro_expansion = expand_macro_task.await.context("expand macro")?;
95 if macro_expansion.is_empty() {
96 log::info!("Empty macro expansion for position {position:?}");
97 return Ok(());
98 }
99
100 let buffer = project
101 .update(&mut cx, |project, cx| project.create_buffer(cx))?
102 .await?;
103 workspace.update(&mut cx, |workspace, cx| {
104 buffer.update(cx, |buffer, cx| {
105 buffer.edit([(0..0, macro_expansion.expansion)], None, cx);
106 buffer.set_language(Some(rust_language), cx)
107 });
108 let multibuffer = cx.new_model(|cx| {
109 MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
110 });
111 workspace.add_item_to_active_pane(
112 Box::new(
113 cx.new_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), true, cx)),
114 ),
115 None,
116 cx,
117 );
118 })
119 })
120 .detach_and_log_err(cx);
121}
122
123fn is_rust_language(language: &Language) -> bool {
124 language.name().as_ref() == "Rust"
125}