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_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}