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