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