1use std::{fs, path::Path};
2
3use anyhow::Context as _;
4use gpui::{App, AppContext as _, Context, Entity, Window};
5use language::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.edit([(0..0, macro_expansion.expansion)], None, cx);
84 buffer.set_language(Some(rust_language), cx)
85 });
86 let multibuffer =
87 cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name));
88 workspace.add_item_to_active_pane(
89 Box::new(cx.new(|cx| {
90 Editor::for_multibuffer(multibuffer, Some(project), true, window, cx)
91 })),
92 None,
93 true,
94 window,
95 cx,
96 );
97 })
98 })
99 .detach_and_log_err(cx);
100}
101
102pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mut Context<Editor>) {
103 if editor.selections.count() == 0 {
104 return;
105 }
106 let Some(project) = &editor.project else {
107 return;
108 };
109 let Some(workspace) = editor.workspace() else {
110 return;
111 };
112
113 let Some((trigger_anchor, _rust_language, server_to_query, buffer)) =
114 find_specific_language_server_in_selection(
115 editor,
116 cx,
117 is_rust_language,
118 RUST_ANALYZER_NAME,
119 )
120 else {
121 return;
122 };
123
124 let project = project.clone();
125 let buffer_snapshot = buffer.read(cx).snapshot();
126 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
127 let open_docs_task = project.update(cx, |project, cx| {
128 project.request_lsp(
129 buffer,
130 project::LanguageServerToQuery::Other(server_to_query),
131 project::lsp_ext_command::OpenDocs { position },
132 cx,
133 )
134 });
135
136 cx.spawn_in(window, |_editor, mut cx| async move {
137 let docs_urls = open_docs_task.await.context("open docs")?;
138 if docs_urls.is_empty() {
139 log::debug!("Empty docs urls for position {position:?}");
140 return Ok(());
141 } else {
142 log::debug!("{:?}", docs_urls);
143 }
144
145 workspace.update(&mut cx, |_workspace, cx| {
146 // Check if the local document exists, otherwise fallback to the online document.
147 // Open with the default browser.
148 if let Some(local_url) = docs_urls.local {
149 if fs::metadata(Path::new(&local_url[8..])).is_ok() {
150 cx.open_url(&local_url);
151 return;
152 }
153 }
154
155 if let Some(web_url) = docs_urls.web {
156 cx.open_url(&web_url);
157 }
158 })
159 })
160 .detach_and_log_err(cx);
161}