Detailed changes
@@ -306,6 +306,7 @@ gpui::actions!(
SortLinesCaseInsensitive,
SortLinesCaseSensitive,
SplitSelectionIntoLines,
+ SwitchSourceHeader,
Tab,
TabPrev,
ToggleAutoSignatureHelp,
@@ -0,0 +1,93 @@
+use std::path::PathBuf;
+
+use anyhow::Context as _;
+use gpui::{View, ViewContext, WindowContext};
+use language::Language;
+use url::Url;
+
+use crate::lsp_ext::find_specific_language_server_in_selection;
+
+use crate::{element::register_action, Editor, SwitchSourceHeader};
+
+static CLANGD_SERVER_NAME: &str = "clangd";
+
+fn is_c_language(language: &Language) -> bool {
+ return language.name().as_ref() == "C++" || language.name().as_ref() == "C";
+}
+
+pub fn switch_source_header(
+ editor: &mut Editor,
+ _: &SwitchSourceHeader,
+ cx: &mut ViewContext<'_, Editor>,
+) {
+ let Some(project) = &editor.project else {
+ return;
+ };
+ let Some(workspace) = editor.workspace() else {
+ return;
+ };
+
+ let Some((_, _, server_to_query, buffer)) =
+ find_specific_language_server_in_selection(&editor, cx, &is_c_language, CLANGD_SERVER_NAME)
+ else {
+ return;
+ };
+
+ let project = project.clone();
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ let source_file = buffer_snapshot
+ .file()
+ .unwrap()
+ .file_name(cx)
+ .to_str()
+ .unwrap()
+ .to_owned();
+
+ let switch_source_header_task = project.update(cx, |project, cx| {
+ project.request_lsp(
+ buffer,
+ project::LanguageServerToQuery::Other(server_to_query),
+ project::lsp_ext_command::SwitchSourceHeader,
+ cx,
+ )
+ });
+ cx.spawn(|_editor, mut cx| async move {
+ let switch_source_header = switch_source_header_task
+ .await
+ .with_context(|| format!("Switch source/header LSP request for path \"{}\" failed", source_file))?;
+ if switch_source_header.0.is_empty() {
+ log::info!("Clangd returned an empty string when requesting to switch source/header from \"{}\"", source_file);
+ return Ok(());
+ }
+
+ let goto = Url::parse(&switch_source_header.0).with_context(|| {
+ format!(
+ "Parsing URL \"{}\" returned from switch source/header failed",
+ switch_source_header.0
+ )
+ })?;
+
+ workspace
+ .update(&mut cx, |workspace, view_cx| {
+ workspace.open_abs_path(PathBuf::from(goto.path()), false, view_cx)
+ })
+ .with_context(|| {
+ format!(
+ "Switch source/header could not open \"{}\" in workspace",
+ goto.path()
+ )
+ })?
+ .await
+ .map(|_| ())
+ })
+ .detach_and_log_err(cx);
+}
+
+pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
+ if editor.update(cx, |e, cx| {
+ find_specific_language_server_in_selection(e, cx, &is_c_language, CLANGD_SERVER_NAME)
+ .is_some()
+ }) {
+ register_action(editor, cx, switch_source_header);
+ }
+}
@@ -15,6 +15,7 @@
pub mod actions;
mod blame_entry_tooltip;
mod blink_manager;
+mod clangd_ext;
mod debounced_delay;
pub mod display_map;
mod editor_settings;
@@ -30,6 +31,7 @@ mod inlay_hint_cache;
mod inline_completion_provider;
pub mod items;
mod linked_editing_ranges;
+mod lsp_ext;
mod mouse_context_menu;
pub mod movement;
mod persistence;
@@ -165,6 +165,7 @@ impl EditorElement {
});
crate::rust_analyzer_ext::apply_related_actions(view, cx);
+ crate::clangd_ext::apply_related_actions(view, cx);
register_action(view, cx, Editor::move_left);
register_action(view, cx, Editor::move_right);
register_action(view, cx, Editor::move_down);
@@ -0,0 +1,54 @@
+use std::sync::Arc;
+
+use crate::Editor;
+use gpui::{Model, WindowContext};
+use language::Buffer;
+use language::Language;
+use lsp::LanguageServerId;
+use multi_buffer::Anchor;
+
+pub(crate) fn find_specific_language_server_in_selection<F>(
+ editor: &Editor,
+ cx: &WindowContext,
+ filter_language: F,
+ language_server_name: &str,
+) -> Option<(Anchor, Arc<Language>, LanguageServerId, Model<Buffer>)>
+where
+ F: Fn(&Language) -> bool,
+{
+ let Some(project) = &editor.project else {
+ return None;
+ };
+ let multibuffer = editor.buffer().read(cx);
+ editor
+ .selections
+ .disjoint_anchors()
+ .into_iter()
+ .filter(|selection| selection.start == selection.end)
+ .filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
+ .filter_map(|(buffer_id, trigger_anchor)| {
+ let buffer = multibuffer.buffer(buffer_id)?;
+ let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
+ if !filter_language(&language) {
+ return None;
+ }
+ Some((trigger_anchor, language, buffer))
+ })
+ .find_map(|(trigger_anchor, language, buffer)| {
+ project
+ .read(cx)
+ .language_servers_for_buffer(buffer.read(cx), cx)
+ .find_map(|(adapter, server)| {
+ if adapter.name.0.as_ref() == language_server_name {
+ Some((
+ trigger_anchor,
+ Arc::clone(&language),
+ server.server_id(),
+ buffer.clone(),
+ ))
+ } else {
+ None
+ }
+ })
+ })
+}
@@ -1,5 +1,3 @@
-use std::sync::Arc;
-
use anyhow::Context as _;
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
use language::Language;
@@ -7,22 +5,24 @@ use multi_buffer::MultiBuffer;
use project::lsp_ext_command::ExpandMacro;
use text::ToPointUtf16;
-use crate::{element::register_action, Editor, ExpandMacroRecursively};
+use crate::{
+ element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
+ ExpandMacroRecursively,
+};
-pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
- let is_rust_related = editor.update(cx, |editor, cx| {
- editor
- .buffer()
- .read(cx)
- .all_buffers()
- .iter()
- .any(|b| match b.read(cx).language() {
- Some(l) => is_rust_language(l),
- None => false,
- })
- });
+static RUST_ANALYZER_NAME: &str = "rust-analyzer";
- if is_rust_related {
+fn is_rust_language(language: &Language) -> bool {
+ language.name().as_ref() == "Rust"
+}
+
+pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
+ if editor
+ .update(cx, |e, cx| {
+ find_specific_language_server_in_selection(e, cx, &is_rust_language, RUST_ANALYZER_NAME)
+ })
+ .is_some()
+ {
register_action(editor, cx, expand_macro_recursively);
}
}
@@ -42,39 +42,13 @@ pub fn expand_macro_recursively(
return;
};
- let multibuffer = editor.buffer().read(cx);
-
- let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
- .selections
- .disjoint_anchors()
- .into_iter()
- .filter(|selection| selection.start == selection.end)
- .filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
- .filter_map(|(buffer_id, trigger_anchor)| {
- let buffer = multibuffer.buffer(buffer_id)?;
- let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
- if !is_rust_language(&rust_language) {
- return None;
- }
- Some((trigger_anchor, rust_language, buffer))
- })
- .find_map(|(trigger_anchor, rust_language, buffer)| {
- project
- .read(cx)
- .language_servers_for_buffer(buffer.read(cx), cx)
- .find_map(|(adapter, server)| {
- if adapter.name.0.as_ref() == "rust-analyzer" {
- Some((
- trigger_anchor,
- Arc::clone(&rust_language),
- server.server_id(),
- buffer.clone(),
- ))
- } else {
- None
- }
- })
- })
+ let Some((trigger_anchor, rust_language, server_to_query, buffer)) =
+ find_specific_language_server_in_selection(
+ &editor,
+ cx,
+ &is_rust_language,
+ RUST_ANALYZER_NAME,
+ )
else {
return;
};
@@ -120,7 +94,3 @@ pub fn expand_macro_recursively(
})
.detach_and_log_err(cx);
}
-
-fn is_rust_language(language: &Language) -> bool {
- language.name().as_ref() == "Rust"
-}
@@ -135,3 +135,97 @@ impl LspCommand for ExpandMacro {
BufferId::new(message.buffer_id)
}
}
+
+pub enum LspSwitchSourceHeader {}
+
+impl lsp::request::Request for LspSwitchSourceHeader {
+ type Params = SwitchSourceHeaderParams;
+ type Result = Option<SwitchSourceHeaderResult>;
+ const METHOD: &'static str = "textDocument/switchSourceHeader";
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct SwitchSourceHeaderParams(lsp::TextDocumentIdentifier);
+
+#[derive(Serialize, Deserialize, Debug, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct SwitchSourceHeaderResult(pub String);
+
+#[derive(Default, Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct SwitchSourceHeader;
+
+#[async_trait(?Send)]
+impl LspCommand for SwitchSourceHeader {
+ type Response = SwitchSourceHeaderResult;
+ type LspRequest = LspSwitchSourceHeader;
+ type ProtoRequest = proto::LspExtSwitchSourceHeader;
+
+ fn to_lsp(
+ &self,
+ path: &Path,
+ _: &Buffer,
+ _: &Arc<LanguageServer>,
+ _: &AppContext,
+ ) -> SwitchSourceHeaderParams {
+ SwitchSourceHeaderParams(lsp::TextDocumentIdentifier {
+ uri: lsp::Url::from_file_path(path).unwrap(),
+ })
+ }
+
+ async fn response_from_lsp(
+ self,
+ message: Option<SwitchSourceHeaderResult>,
+ _: Model<Project>,
+ _: Model<Buffer>,
+ _: LanguageServerId,
+ _: AsyncAppContext,
+ ) -> anyhow::Result<SwitchSourceHeaderResult> {
+ Ok(message
+ .map(|message| SwitchSourceHeaderResult(message.0))
+ .unwrap_or_default())
+ }
+
+ fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtSwitchSourceHeader {
+ proto::LspExtSwitchSourceHeader {
+ project_id,
+ buffer_id: buffer.remote_id().into(),
+ }
+ }
+
+ async fn from_proto(
+ _: Self::ProtoRequest,
+ _: Model<Project>,
+ _: Model<Buffer>,
+ _: AsyncAppContext,
+ ) -> anyhow::Result<Self> {
+ Ok(Self {})
+ }
+
+ fn response_to_proto(
+ response: SwitchSourceHeaderResult,
+ _: &mut Project,
+ _: PeerId,
+ _: &clock::Global,
+ _: &mut AppContext,
+ ) -> proto::LspExtSwitchSourceHeaderResponse {
+ proto::LspExtSwitchSourceHeaderResponse {
+ target_file: response.0,
+ }
+ }
+
+ async fn response_from_proto(
+ self,
+ message: proto::LspExtSwitchSourceHeaderResponse,
+ _: Model<Project>,
+ _: Model<Buffer>,
+ _: AsyncAppContext,
+ ) -> anyhow::Result<SwitchSourceHeaderResult> {
+ Ok(SwitchSourceHeaderResult(message.target_file))
+ }
+
+ fn buffer_id_from_proto(message: &proto::LspExtSwitchSourceHeader) -> Result<BufferId> {
+ BufferId::new(message.buffer_id)
+ }
+}
@@ -131,7 +131,7 @@ message Envelope {
UpdateUserPlan update_user_plan = 234;
UpdateDiffBase update_diff_base = 104;
AcceptTermsOfService accept_terms_of_service = 239;
- AcceptTermsOfServiceResponse accept_terms_of_service_response = 240; // current max
+ AcceptTermsOfServiceResponse accept_terms_of_service_response = 240;
OnTypeFormatting on_type_formatting = 105;
OnTypeFormattingResponse on_type_formatting_response = 106;
@@ -264,15 +264,18 @@ message Envelope {
GetSignatureHelp get_signature_help = 217;
GetSignatureHelpResponse get_signature_help_response = 218;
+
ListRemoteDirectory list_remote_directory = 219;
ListRemoteDirectoryResponse list_remote_directory_response = 220;
UpdateDevServerProject update_dev_server_project = 221;
-
AddWorktree add_worktree = 222;
AddWorktreeResponse add_worktree_response = 223;
GetLlmToken get_llm_token = 235;
GetLlmTokenResponse get_llm_token_response = 236;
+
+ LspExtSwitchSourceHeader lsp_ext_switch_source_header = 241;
+ LspExtSwitchSourceHeaderResponse lsp_ext_switch_source_header_response = 242; // current max
}
reserved 158 to 161;
@@ -2076,6 +2079,15 @@ message LspExtExpandMacroResponse {
string expansion = 2;
}
+message LspExtSwitchSourceHeader {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+}
+
+message LspExtSwitchSourceHeaderResponse {
+ string target_file = 1;
+}
+
message SetRoomParticipantRole {
uint64 room_id = 1;
uint64 user_id = 2;
@@ -406,6 +406,8 @@ messages!(
(UpdateContext, Foreground),
(SynchronizeContexts, Foreground),
(SynchronizeContextsResponse, Foreground),
+ (LspExtSwitchSourceHeader, Background),
+ (LspExtSwitchSourceHeaderResponse, Background),
(AddWorktree, Foreground),
(AddWorktreeResponse, Foreground),
);
@@ -528,6 +530,7 @@ request_messages!(
(OpenContext, OpenContextResponse),
(CreateContext, CreateContextResponse),
(SynchronizeContexts, SynchronizeContextsResponse),
+ (LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse),
);
@@ -597,6 +600,7 @@ entity_messages!(
CreateContext,
UpdateContext,
SynchronizeContexts,
+ LspExtSwitchSourceHeader
);
entity_messages!(