clangd: Implement switch source/header extension (#14646)
Release Notes: - Added switch source/header action for clangd language server (fixes [#12801](https://github.com/zed-industries/zed/issues/12801)). Note: I'm new to both rust and this codebase. I started my implementation by copying how rust analyzer's "expand macro" LSP extension is implemented. I don't yet understand some of the code I copied (mostly the way to get the `server_to_query` in `clangd_ext.rs` and the whole proto implementation). --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
parent
96bcceed40
commit
f85ca387a7
9 changed files with 286 additions and 55 deletions
|
@ -306,6 +306,7 @@ gpui::actions!(
|
||||||
SortLinesCaseInsensitive,
|
SortLinesCaseInsensitive,
|
||||||
SortLinesCaseSensitive,
|
SortLinesCaseSensitive,
|
||||||
SplitSelectionIntoLines,
|
SplitSelectionIntoLines,
|
||||||
|
SwitchSourceHeader,
|
||||||
Tab,
|
Tab,
|
||||||
TabPrev,
|
TabPrev,
|
||||||
ToggleAutoSignatureHelp,
|
ToggleAutoSignatureHelp,
|
||||||
|
|
93
crates/editor/src/clangd_ext.rs
Normal file
93
crates/editor/src/clangd_ext.rs
Normal file
|
@ -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;
|
pub mod actions;
|
||||||
mod blame_entry_tooltip;
|
mod blame_entry_tooltip;
|
||||||
mod blink_manager;
|
mod blink_manager;
|
||||||
|
mod clangd_ext;
|
||||||
mod debounced_delay;
|
mod debounced_delay;
|
||||||
pub mod display_map;
|
pub mod display_map;
|
||||||
mod editor_settings;
|
mod editor_settings;
|
||||||
|
@ -30,6 +31,7 @@ mod inlay_hint_cache;
|
||||||
mod inline_completion_provider;
|
mod inline_completion_provider;
|
||||||
pub mod items;
|
pub mod items;
|
||||||
mod linked_editing_ranges;
|
mod linked_editing_ranges;
|
||||||
|
mod lsp_ext;
|
||||||
mod mouse_context_menu;
|
mod mouse_context_menu;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
|
|
@ -165,6 +165,7 @@ impl EditorElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
crate::rust_analyzer_ext::apply_related_actions(view, cx);
|
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_left);
|
||||||
register_action(view, cx, Editor::move_right);
|
register_action(view, cx, Editor::move_right);
|
||||||
register_action(view, cx, Editor::move_down);
|
register_action(view, cx, Editor::move_down);
|
||||||
|
|
54
crates/editor/src/lsp_ext.rs
Normal file
54
crates/editor/src/lsp_ext.rs
Normal file
|
@ -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 anyhow::Context as _;
|
||||||
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
|
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
|
||||||
use language::Language;
|
use language::Language;
|
||||||
|
@ -7,22 +5,24 @@ use multi_buffer::MultiBuffer;
|
||||||
use project::lsp_ext_command::ExpandMacro;
|
use project::lsp_ext_command::ExpandMacro;
|
||||||
use text::ToPointUtf16;
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||||
|
|
||||||
|
fn is_rust_language(language: &Language) -> bool {
|
||||||
|
language.name().as_ref() == "Rust"
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||||
let is_rust_related = editor.update(cx, |editor, cx| {
|
if editor
|
||||||
editor
|
.update(cx, |e, cx| {
|
||||||
.buffer()
|
find_specific_language_server_in_selection(e, cx, &is_rust_language, RUST_ANALYZER_NAME)
|
||||||
.read(cx)
|
|
||||||
.all_buffers()
|
|
||||||
.iter()
|
|
||||||
.any(|b| match b.read(cx).language() {
|
|
||||||
Some(l) => is_rust_language(l),
|
|
||||||
None => false,
|
|
||||||
})
|
})
|
||||||
});
|
.is_some()
|
||||||
|
{
|
||||||
if is_rust_related {
|
|
||||||
register_action(editor, cx, expand_macro_recursively);
|
register_action(editor, cx, expand_macro_recursively);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,39 +42,13 @@ pub fn expand_macro_recursively(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let multibuffer = editor.buffer().read(cx);
|
let Some((trigger_anchor, rust_language, server_to_query, buffer)) =
|
||||||
|
find_specific_language_server_in_selection(
|
||||||
let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
|
&editor,
|
||||||
.selections
|
cx,
|
||||||
.disjoint_anchors()
|
&is_rust_language,
|
||||||
.into_iter()
|
RUST_ANALYZER_NAME,
|
||||||
.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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -120,7 +94,3 @@ pub fn expand_macro_recursively(
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.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)
|
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;
|
UpdateUserPlan update_user_plan = 234;
|
||||||
UpdateDiffBase update_diff_base = 104;
|
UpdateDiffBase update_diff_base = 104;
|
||||||
AcceptTermsOfService accept_terms_of_service = 239;
|
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;
|
OnTypeFormatting on_type_formatting = 105;
|
||||||
OnTypeFormattingResponse on_type_formatting_response = 106;
|
OnTypeFormattingResponse on_type_formatting_response = 106;
|
||||||
|
@ -264,15 +264,18 @@ message Envelope {
|
||||||
|
|
||||||
GetSignatureHelp get_signature_help = 217;
|
GetSignatureHelp get_signature_help = 217;
|
||||||
GetSignatureHelpResponse get_signature_help_response = 218;
|
GetSignatureHelpResponse get_signature_help_response = 218;
|
||||||
|
|
||||||
ListRemoteDirectory list_remote_directory = 219;
|
ListRemoteDirectory list_remote_directory = 219;
|
||||||
ListRemoteDirectoryResponse list_remote_directory_response = 220;
|
ListRemoteDirectoryResponse list_remote_directory_response = 220;
|
||||||
UpdateDevServerProject update_dev_server_project = 221;
|
UpdateDevServerProject update_dev_server_project = 221;
|
||||||
|
|
||||||
AddWorktree add_worktree = 222;
|
AddWorktree add_worktree = 222;
|
||||||
AddWorktreeResponse add_worktree_response = 223;
|
AddWorktreeResponse add_worktree_response = 223;
|
||||||
|
|
||||||
GetLlmToken get_llm_token = 235;
|
GetLlmToken get_llm_token = 235;
|
||||||
GetLlmTokenResponse get_llm_token_response = 236;
|
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;
|
reserved 158 to 161;
|
||||||
|
@ -2076,6 +2079,15 @@ message LspExtExpandMacroResponse {
|
||||||
string expansion = 2;
|
string expansion = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LspExtSwitchSourceHeader {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 buffer_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LspExtSwitchSourceHeaderResponse {
|
||||||
|
string target_file = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message SetRoomParticipantRole {
|
message SetRoomParticipantRole {
|
||||||
uint64 room_id = 1;
|
uint64 room_id = 1;
|
||||||
uint64 user_id = 2;
|
uint64 user_id = 2;
|
||||||
|
|
|
@ -406,6 +406,8 @@ messages!(
|
||||||
(UpdateContext, Foreground),
|
(UpdateContext, Foreground),
|
||||||
(SynchronizeContexts, Foreground),
|
(SynchronizeContexts, Foreground),
|
||||||
(SynchronizeContextsResponse, Foreground),
|
(SynchronizeContextsResponse, Foreground),
|
||||||
|
(LspExtSwitchSourceHeader, Background),
|
||||||
|
(LspExtSwitchSourceHeaderResponse, Background),
|
||||||
(AddWorktree, Foreground),
|
(AddWorktree, Foreground),
|
||||||
(AddWorktreeResponse, Foreground),
|
(AddWorktreeResponse, Foreground),
|
||||||
);
|
);
|
||||||
|
@ -528,6 +530,7 @@ request_messages!(
|
||||||
(OpenContext, OpenContextResponse),
|
(OpenContext, OpenContextResponse),
|
||||||
(CreateContext, CreateContextResponse),
|
(CreateContext, CreateContextResponse),
|
||||||
(SynchronizeContexts, SynchronizeContextsResponse),
|
(SynchronizeContexts, SynchronizeContextsResponse),
|
||||||
|
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
||||||
(AddWorktree, AddWorktreeResponse),
|
(AddWorktree, AddWorktreeResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -597,6 +600,7 @@ entity_messages!(
|
||||||
CreateContext,
|
CreateContext,
|
||||||
UpdateContext,
|
UpdateContext,
|
||||||
SynchronizeContexts,
|
SynchronizeContexts,
|
||||||
|
LspExtSwitchSourceHeader
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue