rust_analyzer_ext.rs

  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}