1use std::{fs, path::Path};
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::{
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: &View<Editor>, cx: &mut WindowContext) {
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, cx, expand_macro_recursively);
29 register_action(editor, cx, open_docs);
30 }
31}
32
33pub fn expand_macro_recursively(
34 editor: &mut Editor,
35 _: &ExpandMacroRecursively,
36 cx: &mut ViewContext<Editor>,
37) {
38 if editor.selections.count() == 0 {
39 return;
40 }
41 let Some(project) = &editor.project else {
42 return;
43 };
44 let Some(workspace) = editor.workspace() else {
45 return;
46 };
47
48 let Some((trigger_anchor, rust_language, server_to_query, buffer)) =
49 find_specific_language_server_in_selection(
50 editor,
51 cx,
52 is_rust_language,
53 RUST_ANALYZER_NAME,
54 )
55 else {
56 return;
57 };
58
59 let project = project.clone();
60 let buffer_snapshot = buffer.read(cx).snapshot();
61 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
62 let expand_macro_task = project.update(cx, |project, cx| {
63 project.request_lsp(
64 buffer,
65 project::LanguageServerToQuery::Other(server_to_query),
66 ExpandMacro { position },
67 cx,
68 )
69 });
70 cx.spawn(|_editor, mut cx| async move {
71 let macro_expansion = expand_macro_task.await.context("expand macro")?;
72 if macro_expansion.is_empty() {
73 log::info!("Empty macro expansion for position {position:?}");
74 return Ok(());
75 }
76
77 let buffer = project
78 .update(&mut cx, |project, cx| project.create_buffer(cx))?
79 .await?;
80 workspace.update(&mut cx, |workspace, cx| {
81 buffer.update(cx, |buffer, cx| {
82 buffer.edit([(0..0, macro_expansion.expansion)], None, cx);
83 buffer.set_language(Some(rust_language), cx)
84 });
85 let multibuffer = cx.new_model(|cx| {
86 MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
87 });
88 workspace.add_item_to_active_pane(
89 Box::new(
90 cx.new_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), true, cx)),
91 ),
92 None,
93 true,
94 cx,
95 );
96 })
97 })
98 .detach_and_log_err(cx);
99}
100
101pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<Editor>) {
102 if editor.selections.count() == 0 {
103 return;
104 }
105 let Some(project) = &editor.project else {
106 return;
107 };
108 let Some(workspace) = editor.workspace() else {
109 return;
110 };
111
112 let Some((trigger_anchor, _rust_language, server_to_query, buffer)) =
113 find_specific_language_server_in_selection(
114 editor,
115 cx,
116 is_rust_language,
117 RUST_ANALYZER_NAME,
118 )
119 else {
120 return;
121 };
122
123 let project = project.clone();
124 let buffer_snapshot = buffer.read(cx).snapshot();
125 let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
126 let open_docs_task = project.update(cx, |project, cx| {
127 project.request_lsp(
128 buffer,
129 project::LanguageServerToQuery::Other(server_to_query),
130 project::lsp_ext_command::OpenDocs { position },
131 cx,
132 )
133 });
134
135 cx.spawn(|_editor, mut cx| async move {
136 let docs_urls = open_docs_task.await.context("open docs")?;
137 if docs_urls.is_empty() {
138 log::debug!("Empty docs urls for position {position:?}");
139 return Ok(());
140 } else {
141 log::debug!("{:?}", docs_urls);
142 }
143
144 workspace.update(&mut cx, |_workspace, cx| {
145 // Check if the local document exists, otherwise fallback to the online document.
146 // Open with the default browser.
147 if let Some(local_url) = docs_urls.local {
148 if fs::metadata(Path::new(&local_url[8..])).is_ok() {
149 cx.open_url(&local_url);
150 return;
151 }
152 }
153
154 if let Some(web_url) = docs_urls.web {
155 cx.open_url(&web_url);
156 }
157 })
158 })
159 .detach_and_log_err(cx);
160}