1use std::{fs, path::Path};
2
3use anyhow::Context as _;
4use gpui::{App, AppContext as _, Context, Entity, Window};
5use language::{Capability, Language};
6use multi_buffer::MultiBuffer;
7use project::lsp_store::{lsp_ext_command::ExpandMacro, rust_analyzer_ext::RUST_ANALYZER_NAME};
8use text::ToPointUtf16;
9
10use crate::{
11 element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
12 ExpandMacroRecursively, OpenDocs,
13};
14
15fn is_rust_language(language: &Language) -> bool {
16 language.name() == "Rust".into()
17}
18
19pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) {
20 if editor
21 .update(cx, |e, cx| {
22 find_specific_language_server_in_selection(e, cx, is_rust_language, RUST_ANALYZER_NAME)
23 })
24 .is_some()
25 {
26 register_action(editor, window, expand_macro_recursively);
27 register_action(editor, window, open_docs);
28 }
29}
30
31pub fn expand_macro_recursively(
32 editor: &mut Editor,
33 _: &ExpandMacroRecursively,
34 window: &mut Window,
35 cx: &mut Context<Editor>,
36) {
37 if editor.selections.count() == 0 {
38 return;
39 }
40 let Some(project) = &editor.project else {
41 return;
42 };
43 let Some(workspace) = editor.workspace() else {
44 return;
45 };
46
47 let Some((trigger_anchor, rust_language, server_to_query, buffer)) =
48 find_specific_language_server_in_selection(
49 editor,
50 cx,
51 is_rust_language,
52 RUST_ANALYZER_NAME,
53 )
54 else {
55 return;
56 };
57
58 let project = project.clone();
59 let buffer_snapshot = buffer.read(cx).snapshot();
60 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
61 let expand_macro_task = project.update(cx, |project, cx| {
62 project.request_lsp(
63 buffer,
64 project::LanguageServerToQuery::Other(server_to_query),
65 ExpandMacro { position },
66 cx,
67 )
68 });
69 cx.spawn_in(window, async move |_editor, cx| {
70 let macro_expansion = expand_macro_task.await.context("expand macro")?;
71 if macro_expansion.is_empty() {
72 log::info!("Empty macro expansion for position {position:?}");
73 return Ok(());
74 }
75
76 let buffer = project
77 .update(cx, |project, cx| project.create_buffer(cx))?
78 .await?;
79 workspace.update_in(cx, |workspace, window, cx| {
80 buffer.update(cx, |buffer, cx| {
81 buffer.set_text(macro_expansion.expansion, cx);
82 buffer.set_language(Some(rust_language), cx);
83 buffer.set_capability(Capability::ReadOnly, cx);
84 });
85 let multibuffer =
86 cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name));
87 workspace.add_item_to_active_pane(
88 Box::new(cx.new(|cx| {
89 let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
90 editor.set_read_only(true);
91 editor
92 })),
93 None,
94 true,
95 window,
96 cx,
97 );
98 })
99 })
100 .detach_and_log_err(cx);
101}
102
103pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mut Context<Editor>) {
104 if editor.selections.count() == 0 {
105 return;
106 }
107 let Some(project) = &editor.project else {
108 return;
109 };
110 let Some(workspace) = editor.workspace() else {
111 return;
112 };
113
114 let Some((trigger_anchor, _rust_language, server_to_query, buffer)) =
115 find_specific_language_server_in_selection(
116 editor,
117 cx,
118 is_rust_language,
119 RUST_ANALYZER_NAME,
120 )
121 else {
122 return;
123 };
124
125 let project = project.clone();
126 let buffer_snapshot = buffer.read(cx).snapshot();
127 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
128 let open_docs_task = project.update(cx, |project, cx| {
129 project.request_lsp(
130 buffer,
131 project::LanguageServerToQuery::Other(server_to_query),
132 project::lsp_store::lsp_ext_command::OpenDocs { position },
133 cx,
134 )
135 });
136
137 cx.spawn_in(window, async move |_editor, cx| {
138 let docs_urls = open_docs_task.await.context("open docs")?;
139 if docs_urls.is_empty() {
140 log::debug!("Empty docs urls for position {position:?}");
141 return Ok(());
142 } else {
143 log::debug!("{:?}", docs_urls);
144 }
145
146 workspace.update(cx, |_workspace, cx| {
147 // Check if the local document exists, otherwise fallback to the online document.
148 // Open with the default browser.
149 if let Some(local_url) = docs_urls.local {
150 if fs::metadata(Path::new(&local_url[8..])).is_ok() {
151 cx.open_url(&local_url);
152 return;
153 }
154 }
155
156 if let Some(web_url) = docs_urls.web {
157 cx.open_url(&web_url);
158 }
159 })
160 })
161 .detach_and_log_err(cx);
162}