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 .into_iter()
66 .find_map(|(adapter, server)| {
67 if adapter.name.0.as_ref() == "rust-analyzer" {
68 Some((
69 trigger_anchor,
70 Arc::clone(&rust_language),
71 server.server_id(),
72 buffer.clone(),
73 ))
74 } else {
75 None
76 }
77 })
78 })
79 else {
80 return;
81 };
82
83 let project = project.clone();
84 let buffer_snapshot = buffer.read(cx).snapshot();
85 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
86 let expand_macro_task = project.update(cx, |project, cx| {
87 project.request_lsp(
88 buffer,
89 project::LanguageServerToQuery::Other(server_to_query),
90 ExpandMacro { position },
91 cx,
92 )
93 });
94 cx.spawn(|_editor, mut cx| async move {
95 let macro_expansion = expand_macro_task.await.context("expand macro")?;
96 if macro_expansion.is_empty() {
97 log::info!("Empty macro expansion for position {position:?}");
98 return Ok(());
99 }
100
101 let buffer = project.update(&mut cx, |project, cx| {
102 project.create_buffer(¯o_expansion.expansion, Some(rust_language), cx)
103 })??;
104 workspace.update(&mut cx, |workspace, cx| {
105 let buffer = cx.new_model(|cx| {
106 MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
107 });
108 workspace.add_item(
109 Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
110 cx,
111 );
112 })
113 })
114 .detach_and_log_err(cx);
115}
116
117fn is_rust_language(language: &Language) -> bool {
118 language.name().as_ref() == "Rust"
119}