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}