clangd_ext.rs

  1use anyhow::Context as _;
  2use gpui::{App, Context, Entity, Window};
  3use language::Language;
  4use project::lsp_store::lsp_ext_command::SwitchSourceHeaderResult;
  5use rpc::proto;
  6use url::Url;
  7use workspace::{OpenOptions, OpenVisible};
  8
  9use crate::lsp_ext::find_specific_language_server_in_selection;
 10
 11use crate::{Editor, SwitchSourceHeader, element::register_action};
 12
 13use project::lsp_store::clangd_ext::CLANGD_SERVER_NAME;
 14
 15fn is_c_language(language: &Language) -> bool {
 16    return language.name() == "C++".into() || language.name() == "C".into();
 17}
 18
 19pub fn switch_source_header(
 20    editor: &mut Editor,
 21    _: &SwitchSourceHeader,
 22    window: &mut Window,
 23    cx: &mut Context<Editor>,
 24) {
 25    let Some(project) = &editor.project else {
 26        return;
 27    };
 28    let Some(workspace) = editor.workspace() else {
 29        return;
 30    };
 31
 32    let server_lookup =
 33        find_specific_language_server_in_selection(editor, cx, is_c_language, CLANGD_SERVER_NAME);
 34    let project = project.clone();
 35    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
 36    cx.spawn_in(window, async move |_editor, cx| {
 37        let Some((_, _, server_to_query, buffer)) =
 38            server_lookup.await
 39        else {
 40            return Ok(());
 41        };
 42        let source_file = buffer.read_with(cx, |buffer, _| {
 43            buffer.file().map(|file| file.path()).map(|path| path.to_string_lossy().to_string()).unwrap_or_else(|| "Unknown".to_string())
 44        })?;
 45
 46        let switch_source_header = if let Some((client, project_id)) = upstream_client {
 47            let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id())?;
 48            let request = proto::LspExtSwitchSourceHeader {
 49                project_id,
 50                buffer_id: buffer_id.to_proto(),
 51            };
 52            let response = client
 53                .request(request)
 54                .await
 55                .context("lsp ext switch source header proto request")?;
 56            SwitchSourceHeaderResult(response.target_file)
 57        } else {
 58            project.update(cx, |project, cx| {
 59                project.request_lsp(
 60                    buffer,
 61                    project::LanguageServerToQuery::Other(server_to_query),
 62                    project::lsp_store::lsp_ext_command::SwitchSourceHeader,
 63                    cx,
 64                )
 65            })?.await.with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))?
 66        };
 67
 68        if switch_source_header.0.is_empty() {
 69            log::info!("Clangd returned an empty string when requesting to switch source/header from \"{source_file}\"" );
 70            return Ok(());
 71        }
 72
 73        let goto = Url::parse(&switch_source_header.0).with_context(|| {
 74            format!(
 75                "Parsing URL \"{}\" returned from switch source/header failed",
 76                switch_source_header.0
 77            )
 78        })?;
 79
 80        let path = goto.to_file_path().map_err(|()| {
 81            anyhow::anyhow!("URL conversion to file path failed for \"{goto}\"")
 82        })?;
 83
 84        workspace
 85            .update_in(cx, |workspace, window, cx| {
 86                workspace.open_abs_path(path, OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx)
 87            })
 88            .with_context(|| {
 89                format!(
 90                    "Switch source/header could not open \"{goto}\" in workspace"
 91                )
 92            })?
 93            .await
 94            .map(|_| ())
 95    })
 96    .detach_and_log_err(cx);
 97}
 98
 99pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) {
100    if editor
101        .read(cx)
102        .buffer()
103        .read(cx)
104        .all_buffers()
105        .into_iter()
106        .filter_map(|buffer| buffer.read(cx).language())
107        .any(|language| is_c_language(language))
108    {
109        register_action(&editor, window, switch_source_header);
110    }
111}