rust_analyzer_ext.rs

  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}