rust_analyzer_ext.rs

  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}