Support LSP command executions and showDocument requests (#63607)
Closes https://github.com/zed-industries/zed/issues/13756 Closes https://github.com/zed-industries/zed/issues/61572 Last window client capability we were missing! <img width="1557" height="159" alt="image" src="https://github.com/user-attachments/assets/2cca5d92-a28e-4262-8f55-89f582782bbc" /> Two features are connected to each other, as most of the commands result in opening a document: https://github.com/user-attachments/assets/661aae88-7af9-4173-9f6a-2fd11dbec675 Release Notes: - Added support for LSP command executions and showDocument requests
This commit is contained in:
1 parent
5697636cd5
commit
66a1d8d7cb
23 files changed
+2019
-29
No files matched your search
Generated
+24
@@ -10417,6 +10417,29 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lsp_command_selector"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"editor",
|
||||||
|
"futures 0.3.32",
|
||||||
|
"fuzzy",
|
||||||
|
"gpui",
|
||||||
|
"language",
|
||||||
|
"log",
|
||||||
|
"lsp",
|
||||||
|
"menu",
|
||||||
|
"picker",
|
||||||
|
"project",
|
||||||
|
"serde_json",
|
||||||
|
"settings",
|
||||||
|
"ui",
|
||||||
|
"util",
|
||||||
|
"workspace",
|
||||||
|
"zlog",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp_locations"
|
name = "lsp_locations"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -22806,6 +22829,7 @@ dependencies = [
|
|||||||
"languages",
|
"languages",
|
||||||
"line_ending_selector",
|
"line_ending_selector",
|
||||||
"log",
|
"log",
|
||||||
|
"lsp_command_selector",
|
||||||
"lsp_locations",
|
"lsp_locations",
|
||||||
"markdown",
|
"markdown",
|
||||||
"markdown_preview",
|
"markdown_preview",
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ members = [
|
|||||||
"crates/llama_cpp",
|
"crates/llama_cpp",
|
||||||
"crates/lmstudio",
|
"crates/lmstudio",
|
||||||
"crates/lsp",
|
"crates/lsp",
|
||||||
|
"crates/lsp_command_selector",
|
||||||
"crates/lsp_locations",
|
"crates/lsp_locations",
|
||||||
"crates/markdown",
|
"crates/markdown",
|
||||||
"crates/markdown_preview",
|
"crates/markdown_preview",
|
||||||
@@ -399,6 +400,7 @@ livekit_client = { path = "crates/livekit_client" }
|
|||||||
llama_cpp = { path = "crates/llama_cpp" }
|
llama_cpp = { path = "crates/llama_cpp" }
|
||||||
lmstudio = { path = "crates/lmstudio" }
|
lmstudio = { path = "crates/lmstudio" }
|
||||||
lsp = { path = "crates/lsp" }
|
lsp = { path = "crates/lsp" }
|
||||||
|
lsp_command_selector = { path = "crates/lsp_command_selector" }
|
||||||
lsp_locations = { path = "crates/lsp_locations" }
|
lsp_locations = { path = "crates/lsp_locations" }
|
||||||
markdown = { path = "crates/markdown" }
|
markdown = { path = "crates/markdown" }
|
||||||
markdown_preview = { path = "crates/markdown_preview" }
|
markdown_preview = { path = "crates/markdown_preview" }
|
||||||
|
|||||||
@@ -1222,6 +1222,12 @@
|
|||||||
// Editor bindings.
|
// Editor bindings.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "LspCommandSelector > Picker > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"tab": "lsp_command_selector::ToggleArgumentsFocus",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ChannelModal > Picker > Editor",
|
"context": "ChannelModal > Picker > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -1275,6 +1275,13 @@
|
|||||||
// Editor bindings.
|
// Editor bindings.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "LspCommandSelector > Picker > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"tab": "lsp_command_selector::ToggleArgumentsFocus",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ChannelModal > Picker > Editor",
|
"context": "ChannelModal > Picker > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|||||||
@@ -1230,6 +1230,13 @@
|
|||||||
// Editor bindings.
|
// Editor bindings.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "LspCommandSelector > Picker > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"tab": "lsp_command_selector::ToggleArgumentsFocus",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ChannelModal > Picker > Editor",
|
"context": "ChannelModal > Picker > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ impl Server {
|
|||||||
forward_mutating_project_request::<proto::ResolveCompletionDocumentation>,
|
forward_mutating_project_request::<proto::ResolveCompletionDocumentation>,
|
||||||
)
|
)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::ApplyCodeAction>)
|
.add_request_handler(forward_mutating_project_request::<proto::ApplyCodeAction>)
|
||||||
|
.add_request_handler(forward_mutating_project_request::<proto::ExecuteLspCommand>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::PrepareRename>)
|
.add_request_handler(forward_mutating_project_request::<proto::PrepareRename>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::PerformRename>)
|
.add_request_handler(forward_mutating_project_request::<proto::PerformRename>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>)
|
.add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>)
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ pub fn init(cx: &mut App) {
|
|||||||
workspace::register_serializable_item::<Editor>(cx);
|
workspace::register_serializable_item::<Editor>(cx);
|
||||||
|
|
||||||
cx.observe_new(
|
cx.observe_new(
|
||||||
|workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
|
|workspace: &mut Workspace, window: Option<&mut Window>, cx: &mut Context<Workspace>| {
|
||||||
workspace.register_action(Editor::new_file);
|
workspace.register_action(Editor::new_file);
|
||||||
workspace.register_action(Editor::new_file_split);
|
workspace.register_action(Editor::new_file_split);
|
||||||
workspace.register_action(Editor::new_file_vertical);
|
workspace.register_action(Editor::new_file_vertical);
|
||||||
@@ -373,6 +373,19 @@ pub fn init(cx: &mut App) {
|
|||||||
workspace.register_action(Editor::cancel_language_server_work);
|
workspace.register_action(Editor::cancel_language_server_work);
|
||||||
workspace.register_action(Editor::toggle_focus);
|
workspace.register_action(Editor::toggle_focus);
|
||||||
workspace.register_action(Editor::view_bookmarks);
|
workspace.register_action(Editor::view_bookmarks);
|
||||||
|
if let Some(window) = window {
|
||||||
|
cx.subscribe_in(
|
||||||
|
workspace.project(),
|
||||||
|
window,
|
||||||
|
|workspace, _, event, window, cx| {
|
||||||
|
if let project::Event::LanguageServerShowDocument(request) = event {
|
||||||
|
items::handle_lsp_show_document(workspace, request, window, cx)
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
@@ -46051,3 +46051,164 @@ async fn test_scroll_range_hold_freezes_before_first_settled_frame(cx: &mut Test
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_lsp_show_document(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||||
|
|
||||||
|
let target_path = EditorLspTestContext::root_path()
|
||||||
|
.join("dir")
|
||||||
|
.join("target.rs");
|
||||||
|
let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
|
||||||
|
fs.as_fake()
|
||||||
|
.insert_file(&target_path, b"fn target() {}".to_vec())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let response = cx
|
||||||
|
.lsp
|
||||||
|
.server
|
||||||
|
.request::<lsp::request::ShowDocument>(
|
||||||
|
lsp::ShowDocumentParams {
|
||||||
|
uri: lsp::Uri::from_file_path(&target_path).unwrap(),
|
||||||
|
external: None,
|
||||||
|
take_focus: Some(true),
|
||||||
|
selection: Some(lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 3),
|
||||||
|
lsp::Position::new(0, 9),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_response()
|
||||||
|
.expect("show document request should not error");
|
||||||
|
assert_eq!(response, lsp::ShowDocumentResult { success: true });
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
cx.update_workspace(|workspace, _, cx| {
|
||||||
|
let editor = workspace
|
||||||
|
.active_item_as::<Editor>(cx)
|
||||||
|
.expect("an editor should be opened for the shown document");
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let path = editor
|
||||||
|
.buffer()
|
||||||
|
.read(cx)
|
||||||
|
.as_singleton()
|
||||||
|
.expect("a singleton buffer should be opened")
|
||||||
|
.read(cx)
|
||||||
|
.file()
|
||||||
|
.expect("the opened buffer should have a file")
|
||||||
|
.path()
|
||||||
|
.clone();
|
||||||
|
assert_eq!(path.as_ref(), rel_path("dir/target.rs"));
|
||||||
|
assert_eq!(
|
||||||
|
editor
|
||||||
|
.selections
|
||||||
|
.ranges::<Point>(&editor.display_snapshot(cx)),
|
||||||
|
vec![Point::new(0, 3)..Point::new(0, 9)]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_lsp_show_document_external(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||||
|
|
||||||
|
let initial_item_id = cx
|
||||||
|
.update_workspace(|workspace, _, cx| workspace.active_item(cx).map(|item| item.item_id()));
|
||||||
|
let response = cx
|
||||||
|
.lsp
|
||||||
|
.server
|
||||||
|
.request::<lsp::request::ShowDocument>(
|
||||||
|
lsp::ShowDocumentParams {
|
||||||
|
uri: "https://zed.dev/docs".parse::<lsp::Uri>().unwrap(),
|
||||||
|
external: Some(true),
|
||||||
|
take_focus: None,
|
||||||
|
selection: None,
|
||||||
|
},
|
||||||
|
DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_response()
|
||||||
|
.expect("show document request should not error");
|
||||||
|
assert_eq!(response, lsp::ShowDocumentResult { success: true });
|
||||||
|
assert_eq!(cx.opened_url(), Some("https://zed.dev/docs".to_string()));
|
||||||
|
cx.run_until_parked();
|
||||||
|
cx.update_workspace(|workspace, _, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
workspace.active_item(cx).map(|item| item.item_id()),
|
||||||
|
initial_item_id,
|
||||||
|
"external documents should not open any workspace items"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_lsp_show_document_without_take_focus(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||||
|
|
||||||
|
let target_path = EditorLspTestContext::root_path()
|
||||||
|
.join("dir")
|
||||||
|
.join("target.rs");
|
||||||
|
let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
|
||||||
|
fs.as_fake()
|
||||||
|
.insert_file(&target_path, b"fn target() {}".to_vec())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let initial_editor = cx.editor.clone();
|
||||||
|
let response = cx
|
||||||
|
.lsp
|
||||||
|
.server
|
||||||
|
.request::<lsp::request::ShowDocument>(
|
||||||
|
lsp::ShowDocumentParams {
|
||||||
|
uri: lsp::Uri::from_file_path(&target_path).unwrap(),
|
||||||
|
external: None,
|
||||||
|
take_focus: None,
|
||||||
|
selection: None,
|
||||||
|
},
|
||||||
|
DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_response()
|
||||||
|
.expect("show document request should not error");
|
||||||
|
assert_eq!(response, lsp::ShowDocumentResult { success: true });
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
cx.update_workspace(|workspace, _, cx| {
|
||||||
|
let opened_editor = workspace
|
||||||
|
.active_item_as::<Editor>(cx)
|
||||||
|
.expect("an editor should be opened for the shown document");
|
||||||
|
assert_ne!(
|
||||||
|
opened_editor.entity_id(),
|
||||||
|
initial_editor.entity_id(),
|
||||||
|
"the shown document should become the active item"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_lsp_show_document_unsupported_uri(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
let cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||||
|
|
||||||
|
let response = cx
|
||||||
|
.lsp
|
||||||
|
.server
|
||||||
|
.request::<lsp::request::ShowDocument>(
|
||||||
|
lsp::ShowDocumentParams {
|
||||||
|
uri: "untitled:some-document".parse::<lsp::Uri>().unwrap(),
|
||||||
|
external: None,
|
||||||
|
take_focus: None,
|
||||||
|
selection: None,
|
||||||
|
},
|
||||||
|
DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_response()
|
||||||
|
.expect("show document request should not error");
|
||||||
|
assert_eq!(response, lsp::ShowDocumentResult { success: false });
|
||||||
|
}
|
||||||
@@ -26,8 +26,11 @@ use language::{
|
|||||||
use lsp::DiagnosticSeverity;
|
use lsp::DiagnosticSeverity;
|
||||||
use multi_buffer::{BufferOffset, MultiBufferOffset, MultiBufferRow, PathKey};
|
use multi_buffer::{BufferOffset, MultiBufferOffset, MultiBufferRow, PathKey};
|
||||||
use project::{
|
use project::{
|
||||||
File, Project, ProjectItem as _, ProjectPath, git_store::GitStore, lsp_store::FormatTrigger,
|
File, Project, ProjectItem as _, ProjectPath,
|
||||||
project_settings::ProjectSettings, search::SearchQuery,
|
git_store::GitStore,
|
||||||
|
lsp_store::{FormatTrigger, LanguageServerShowDocumentRequest},
|
||||||
|
project_settings::ProjectSettings,
|
||||||
|
search::SearchQuery,
|
||||||
};
|
};
|
||||||
use rope::TextSummary;
|
use rope::TextSummary;
|
||||||
use rpc::proto::{self, update_view};
|
use rpc::proto::{self, update_view};
|
||||||
@@ -43,10 +46,15 @@ use std::{
|
|||||||
};
|
};
|
||||||
use text::{BufferId, BufferSnapshot, OffsetRangeExt, Selection, ToPoint as _};
|
use text::{BufferId, BufferSnapshot, OffsetRangeExt, Selection, ToPoint as _};
|
||||||
use ui::{IconDecorationKind, prelude::*};
|
use ui::{IconDecorationKind, prelude::*};
|
||||||
use util::{ResultExt, TryFutureExt, debug_panic, paths::PathExt, rel_path::RelPath};
|
use util::{
|
||||||
|
ResultExt, TryFutureExt, debug_panic,
|
||||||
|
paths::{PathExt, UrlExt as _},
|
||||||
|
rel_path::RelPath,
|
||||||
|
};
|
||||||
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
CollaboratorId, ItemId, ItemNavHistory, OpenOptions, OpenVisible, ToolbarItemLocation, ViewId,
|
||||||
|
Workspace, WorkspaceId,
|
||||||
invalid_item_view::InvalidItemView,
|
invalid_item_view::InvalidItemView,
|
||||||
item::{FollowableItem, Item, ItemBufferKind, ItemEvent, ProjectItem, SaveOptions},
|
item::{FollowableItem, Item, ItemBufferKind, ItemEvent, ProjectItem, SaveOptions},
|
||||||
searchable::{
|
searchable::{
|
||||||
@@ -2504,6 +2512,68 @@ fn compute_modified_ranges(
|
|||||||
merged
|
merged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_lsp_show_document(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
request: &LanguageServerShowDocumentRequest,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Workspace>,
|
||||||
|
) -> Task<()> {
|
||||||
|
let request = request.clone();
|
||||||
|
if request.external {
|
||||||
|
cx.open_url(request.uri.as_str());
|
||||||
|
request.respond(true);
|
||||||
|
return Task::ready(());
|
||||||
|
}
|
||||||
|
let Ok(abs_path) = request.uri.to_file_path_ext(workspace.path_style(cx)) else {
|
||||||
|
log::error!(
|
||||||
|
"language server requested to show document with unsupported uri {}",
|
||||||
|
request.uri.as_str()
|
||||||
|
);
|
||||||
|
request.respond(false);
|
||||||
|
return Task::ready(());
|
||||||
|
};
|
||||||
|
let open_task = workspace.open_abs_path(
|
||||||
|
abs_path,
|
||||||
|
OpenOptions {
|
||||||
|
visible: Some(OpenVisible::None),
|
||||||
|
focus: Some(request.take_focus),
|
||||||
|
..OpenOptions::default()
|
||||||
|
},
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
|
let success = match open_task.await {
|
||||||
|
Ok(item) => match item.downcast::<Editor>().zip(request.selection) {
|
||||||
|
Some((editor, selection)) => editor
|
||||||
|
.update_in(cx, |editor, window, cx| {
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let range = language::range_from_lsp(selection);
|
||||||
|
let start = snapshot.point_utf16_to_offset(
|
||||||
|
snapshot.clip_point_utf16(range.start, Bias::Left),
|
||||||
|
);
|
||||||
|
let end = snapshot.point_utf16_to_offset(
|
||||||
|
snapshot.clip_point_utf16(range.end, Bias::Left),
|
||||||
|
);
|
||||||
|
editor.change_selections(
|
||||||
|
SelectionEffects::scroll(Autoscroll::center()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
|selections| selections.select_ranges([start..end]),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.is_ok(),
|
||||||
|
None => true,
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("failed to show document for a language server: {error:#}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.respond(success);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::editor_tests::init_test;
|
use crate::editor_tests::init_test;
|
||||||
|
|||||||
@@ -1141,7 +1141,7 @@ impl LanguageServer {
|
|||||||
additional_properties_support: Some(true),
|
additional_properties_support: Some(true),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
..WindowClientCapabilities::default()
|
show_document: Some(ShowDocumentClientCapabilities { support: true }),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
trace: None,
|
trace: None,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
[package]
|
||||||
|
name = "lsp_command_selector"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lsp_command_selector.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
editor.workspace = true
|
||||||
|
fuzzy.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
language.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
lsp.workspace = true
|
||||||
|
picker.workspace = true
|
||||||
|
project.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
ui.workspace = true
|
||||||
|
workspace.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
|
futures.workspace = true
|
||||||
|
menu.workspace = true
|
||||||
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
language = { workspace = true, features = ["test-support"] }
|
||||||
|
lsp = { workspace = true, features = ["test-support"] }
|
||||||
|
project = { workspace = true, features = ["test-support"] }
|
||||||
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
|
util.workspace = true
|
||||||
|
workspace = { workspace = true, features = ["test-support"] }
|
||||||
|
zlog.workspace = true
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-GPL
|
||||||
File diff suppressed because it is too large.
Load diff
+150
-20
@@ -1194,11 +1194,11 @@ impl LocalLspStore {
|
|||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||||
let this = lsp_store.clone();
|
let lsp_store = lsp_store.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
move |params, cx| {
|
move |params, cx| {
|
||||||
let this = this.clone();
|
let lsp_store = lsp_store.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
let mut cx = cx.clone();
|
let mut cx = cx.clone();
|
||||||
@@ -1219,29 +1219,49 @@ impl LocalLspStore {
|
|||||||
tx,
|
tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let did_update = this
|
lsp_store.update(&mut cx, |_, cx| {
|
||||||
.update(&mut cx, |_, cx| {
|
cx.emit(LspStoreEvent::LanguageServerPrompt(request));
|
||||||
cx.emit(LspStoreEvent::LanguageServerPrompt(request));
|
})?;
|
||||||
})
|
let response = rx.recv().await.ok();
|
||||||
.is_ok();
|
if let Some(ref selected_action) = response {
|
||||||
if did_update {
|
let context = language::PromptResponseContext {
|
||||||
let response = rx.recv().await.ok();
|
message,
|
||||||
if let Some(ref selected_action) = response {
|
selected_action: selected_action.clone(),
|
||||||
let context = language::PromptResponseContext {
|
};
|
||||||
message,
|
adapter.process_prompt_response(&context, &mut cx)
|
||||||
selected_action: selected_action.clone(),
|
|
||||||
};
|
|
||||||
adapter.process_prompt_response(&context, &mut cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
language_server
|
||||||
|
.on_request::<lsp::request::ShowDocument, _, _>({
|
||||||
|
let lsp_store = lsp_store.clone();
|
||||||
|
move |params, cx| {
|
||||||
|
let lsp_store = lsp_store.clone();
|
||||||
|
let mut cx = cx.clone();
|
||||||
|
async move {
|
||||||
|
let (tx, rx) = async_channel::bounded(1);
|
||||||
|
let request = LanguageServerShowDocumentRequest {
|
||||||
|
uri: params.uri,
|
||||||
|
external: params.external.unwrap_or(false),
|
||||||
|
take_focus: params.take_focus.unwrap_or(false),
|
||||||
|
selection: params.selection,
|
||||||
|
response_channel: tx,
|
||||||
|
};
|
||||||
|
lsp_store.update(&mut cx, |_, cx| {
|
||||||
|
cx.emit(LspStoreEvent::LanguageServerShowDocument(request));
|
||||||
|
})?;
|
||||||
|
let success = rx.recv().await.unwrap_or(false);
|
||||||
|
Ok(lsp::ShowDocumentResult { success })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::ShowMessage, _>({
|
.on_notification::<lsp::notification::ShowMessage, _>({
|
||||||
let this = lsp_store.clone();
|
let this = lsp_store.clone();
|
||||||
@@ -4606,6 +4626,7 @@ pub enum LspStoreEvent {
|
|||||||
new_language: Option<Arc<Language>>,
|
new_language: Option<Arc<Language>>,
|
||||||
},
|
},
|
||||||
Notification(String),
|
Notification(String),
|
||||||
|
LanguageServerShowDocument(LanguageServerShowDocumentRequest),
|
||||||
RefreshInlayHints {
|
RefreshInlayHints {
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
},
|
},
|
||||||
@@ -4757,6 +4778,7 @@ impl LspStore {
|
|||||||
});
|
});
|
||||||
client.add_entity_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
|
client.add_entity_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
|
||||||
|
|
||||||
|
client.add_entity_request_handler(Self::handle_execute_lsp_command);
|
||||||
client.add_entity_request_handler(Self::handle_lsp_ext_cancel_flycheck);
|
client.add_entity_request_handler(Self::handle_lsp_ext_cancel_flycheck);
|
||||||
client.add_entity_request_handler(Self::handle_lsp_ext_run_flycheck);
|
client.add_entity_request_handler(Self::handle_lsp_ext_run_flycheck);
|
||||||
client.add_entity_request_handler(Self::handle_lsp_ext_clear_flycheck);
|
client.add_entity_request_handler(Self::handle_lsp_ext_clear_flycheck);
|
||||||
@@ -6555,6 +6577,67 @@ impl LspStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn execute_lsp_command(
|
||||||
|
&self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
command: String,
|
||||||
|
arguments: Vec<serde_json::Value>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<Option<serde_json::Value>>> {
|
||||||
|
if let Some((upstream_client, project_id)) = self.upstream_client() {
|
||||||
|
let request = upstream_client.request(proto::ExecuteLspCommand {
|
||||||
|
project_id,
|
||||||
|
language_server_id: server_id.to_proto(),
|
||||||
|
command,
|
||||||
|
arguments: arguments
|
||||||
|
.iter()
|
||||||
|
.map(|argument| argument.to_string())
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let response = request.await?;
|
||||||
|
response
|
||||||
|
.result
|
||||||
|
.map(|result| serde_json::from_str(&result))
|
||||||
|
.transpose()
|
||||||
|
.context("deserializing executeCommand result")
|
||||||
|
})
|
||||||
|
} else if self.mode.is_local() {
|
||||||
|
let Some(server) = self.language_server_for_id(server_id) else {
|
||||||
|
return Task::ready(Err(anyhow!("language server {server_id} not found")));
|
||||||
|
};
|
||||||
|
let available_commands = server
|
||||||
|
.capabilities()
|
||||||
|
.execute_command_provider
|
||||||
|
.as_ref()
|
||||||
|
.map(|options| options.commands.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !available_commands.contains(&command) {
|
||||||
|
return Task::ready(Err(anyhow!(
|
||||||
|
"command {command} is not advertised by the language server"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let request_timeout = ProjectSettings::get_global(cx)
|
||||||
|
.global_lsp_settings
|
||||||
|
.get_request_timeout();
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
server
|
||||||
|
.request::<lsp::request::ExecuteCommand>(
|
||||||
|
lsp::ExecuteCommandParams {
|
||||||
|
command,
|
||||||
|
arguments,
|
||||||
|
..lsp::ExecuteCommandParams::default()
|
||||||
|
},
|
||||||
|
request_timeout,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_response()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Task::ready(Err(anyhow!("no upstream client and not local")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolve_code_action(
|
pub fn resolve_code_action(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Entity<Buffer>,
|
buffer: &Entity<Buffer>,
|
||||||
@@ -11403,6 +11486,29 @@ impl LspStore {
|
|||||||
Ok(proto::Ack {})
|
Ok(proto::Ack {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_execute_lsp_command(
|
||||||
|
lsp_store: Entity<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::ExecuteLspCommand>,
|
||||||
|
mut cx: AsyncApp,
|
||||||
|
) -> Result<proto::ExecuteLspCommandResponse> {
|
||||||
|
let server_id = LanguageServerId::from_proto(envelope.payload.language_server_id);
|
||||||
|
let arguments = envelope
|
||||||
|
.payload
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.map(|argument| serde_json::from_str(argument))
|
||||||
|
.collect::<Result<Vec<serde_json::Value>, _>>()
|
||||||
|
.context("deserializing executeCommand arguments")?;
|
||||||
|
let result = lsp_store
|
||||||
|
.update(&mut cx, |lsp_store, cx| {
|
||||||
|
lsp_store.execute_lsp_command(server_id, envelope.payload.command, arguments, cx)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(proto::ExecuteLspCommandResponse {
|
||||||
|
result: result.map(|result| result.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_lsp_ext_run_flycheck(
|
async fn handle_lsp_ext_run_flycheck(
|
||||||
lsp_store: Entity<Self>,
|
lsp_store: Entity<Self>,
|
||||||
envelope: TypedEnvelope<proto::LspExtRunFlycheck>,
|
envelope: TypedEnvelope<proto::LspExtRunFlycheck>,
|
||||||
@@ -15804,6 +15910,30 @@ impl PartialEq for LanguageServerPromptRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LanguageServerShowDocumentRequest {
|
||||||
|
pub uri: lsp::Uri,
|
||||||
|
pub external: bool,
|
||||||
|
pub take_focus: bool,
|
||||||
|
pub selection: Option<lsp::Range>,
|
||||||
|
pub(crate) response_channel: async_channel::Sender<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServerShowDocumentRequest {
|
||||||
|
pub fn respond(self, success: bool) {
|
||||||
|
self.response_channel.try_send(success).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for LanguageServerShowDocumentRequest {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.uri == other.uri
|
||||||
|
&& self.external == other.external
|
||||||
|
&& self.take_focus == other.take_focus
|
||||||
|
&& self.selection == other.selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum LanguageServerLogType {
|
pub enum LanguageServerLogType {
|
||||||
Log(MessageType),
|
Log(MessageType),
|
||||||
|
|||||||
@@ -167,8 +167,9 @@ pub use buffer_store::ProjectTransaction;
|
|||||||
pub use lsp_command::{CallHierarchyItem, IncomingCall, OutgoingCall};
|
pub use lsp_command::{CallHierarchyItem, IncomingCall, OutgoingCall};
|
||||||
pub use lsp_store::{
|
pub use lsp_store::{
|
||||||
DiagnosticSummary, InvalidationStrategy, LanguageServerLogType, LanguageServerProgress,
|
DiagnosticSummary, InvalidationStrategy, LanguageServerLogType, LanguageServerProgress,
|
||||||
LanguageServerPromptRequest, LanguageServerStatus, LanguageServerToQuery, LspStore,
|
LanguageServerPromptRequest, LanguageServerShowDocumentRequest, LanguageServerStatus,
|
||||||
LspStoreEvent, ProgressToken, SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
LanguageServerToQuery, LspStore, LspStoreEvent, ProgressToken,
|
||||||
|
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||||
};
|
};
|
||||||
pub use toolchain_store::{ToolchainStore, Toolchains};
|
pub use toolchain_store::{ToolchainStore, Toolchains};
|
||||||
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
|
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
|
||||||
@@ -363,6 +364,7 @@ pub enum Event {
|
|||||||
notification_id: SharedString,
|
notification_id: SharedString,
|
||||||
},
|
},
|
||||||
LanguageServerPrompt(LanguageServerPromptRequest),
|
LanguageServerPrompt(LanguageServerPromptRequest),
|
||||||
|
LanguageServerShowDocument(LanguageServerShowDocumentRequest),
|
||||||
LanguageNotFound(Entity<Buffer>),
|
LanguageNotFound(Entity<Buffer>),
|
||||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||||
ActivateProjectPanel,
|
ActivateProjectPanel,
|
||||||
@@ -1675,6 +1677,8 @@ impl Project {
|
|||||||
remote_proto.add_entity_message_handler(Self::handle_toast);
|
remote_proto.add_entity_message_handler(Self::handle_toast);
|
||||||
remote_proto.add_entity_message_handler(Self::handle_telemetry_event);
|
remote_proto.add_entity_message_handler(Self::handle_telemetry_event);
|
||||||
remote_proto.add_entity_request_handler(Self::handle_language_server_prompt_request);
|
remote_proto.add_entity_request_handler(Self::handle_language_server_prompt_request);
|
||||||
|
remote_proto
|
||||||
|
.add_entity_request_handler(Self::handle_language_server_show_document_request);
|
||||||
remote_proto.add_entity_message_handler(Self::handle_hide_toast);
|
remote_proto.add_entity_message_handler(Self::handle_hide_toast);
|
||||||
remote_proto.add_entity_request_handler(Self::handle_update_buffer_from_remote_server);
|
remote_proto.add_entity_request_handler(Self::handle_update_buffer_from_remote_server);
|
||||||
remote_proto.add_entity_request_handler(Self::handle_trust_worktrees);
|
remote_proto.add_entity_request_handler(Self::handle_trust_worktrees);
|
||||||
@@ -3756,6 +3760,9 @@ impl Project {
|
|||||||
LspStoreEvent::LanguageServerPrompt(prompt) => {
|
LspStoreEvent::LanguageServerPrompt(prompt) => {
|
||||||
cx.emit(Event::LanguageServerPrompt(prompt.clone()))
|
cx.emit(Event::LanguageServerPrompt(prompt.clone()))
|
||||||
}
|
}
|
||||||
|
LspStoreEvent::LanguageServerShowDocument(request) => {
|
||||||
|
cx.emit(Event::LanguageServerShowDocument(request.clone()))
|
||||||
|
}
|
||||||
LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
|
LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
|
||||||
cx.emit(Event::DiskBasedDiagnosticsStarted {
|
cx.emit(Event::DiskBasedDiagnosticsStarted {
|
||||||
language_server_id: *language_server_id,
|
language_server_id: *language_server_id,
|
||||||
@@ -5614,6 +5621,49 @@ impl Project {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_language_server_show_document_request(
|
||||||
|
project: Entity<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::LanguageServerShowDocumentRequest>,
|
||||||
|
mut cx: AsyncApp,
|
||||||
|
) -> Result<proto::Ack> {
|
||||||
|
let payload = envelope.payload;
|
||||||
|
let selection = payload.selection_start.zip(payload.selection_end).map(
|
||||||
|
|(selection_start, selection_end)| lsp::Range {
|
||||||
|
start: lsp::Position {
|
||||||
|
line: selection_start.row,
|
||||||
|
character: selection_start.column,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: selection_end.row,
|
||||||
|
character: selection_end.column,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let uri = lsp::Uri::from_str(&payload.uri)
|
||||||
|
.with_context(|| format!("parsing show document uri {}", payload.uri))?;
|
||||||
|
let (tx, rx) = async_channel::bounded(1);
|
||||||
|
project.update(&mut cx, |_, cx| {
|
||||||
|
cx.emit(Event::LanguageServerShowDocument(
|
||||||
|
LanguageServerShowDocumentRequest {
|
||||||
|
uri,
|
||||||
|
external: payload.external,
|
||||||
|
take_focus: payload.take_focus,
|
||||||
|
selection,
|
||||||
|
response_channel: tx,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
drop(project);
|
||||||
|
|
||||||
|
let success = rx.recv().await.unwrap_or(false);
|
||||||
|
anyhow::ensure!(
|
||||||
|
success,
|
||||||
|
"show document request for {} was not handled successfully",
|
||||||
|
payload.uri
|
||||||
|
);
|
||||||
|
Ok(proto::Ack {})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_hide_toast(
|
async fn handle_hide_toast(
|
||||||
this: Entity<Self>,
|
this: Entity<Self>,
|
||||||
envelope: TypedEnvelope<proto::HideToast>,
|
envelope: TypedEnvelope<proto::HideToast>,
|
||||||
|
|||||||
@@ -960,6 +960,26 @@ message LanguageServerPromptResponse {
|
|||||||
optional uint64 action_response = 1;
|
optional uint64 action_response = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LanguageServerShowDocumentRequest {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
string uri = 2;
|
||||||
|
bool external = 3;
|
||||||
|
bool take_focus = 4;
|
||||||
|
optional PointUtf16 selection_start = 5;
|
||||||
|
optional PointUtf16 selection_end = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExecuteLspCommand {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 language_server_id = 2;
|
||||||
|
string command = 3;
|
||||||
|
repeated string arguments = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExecuteLspCommandResponse {
|
||||||
|
optional string result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message GetDocumentColor {
|
message GetDocumentColor {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 buffer_id = 2;
|
uint64 buffer_id = 2;
|
||||||
|
|||||||
@@ -518,7 +518,10 @@ message Envelope {
|
|||||||
GetOutgoingCalls get_outgoing_calls = 486;
|
GetOutgoingCalls get_outgoing_calls = 486;
|
||||||
GetOutgoingCallsResponse get_outgoing_calls_response = 487;
|
GetOutgoingCallsResponse get_outgoing_calls_response = 487;
|
||||||
LspExtExpandAbbreviation lsp_ext_expand_abbreviation = 488;
|
LspExtExpandAbbreviation lsp_ext_expand_abbreviation = 488;
|
||||||
LspExtExpandAbbreviationResponse lsp_ext_expand_abbreviation_response = 489; // current max
|
LspExtExpandAbbreviationResponse lsp_ext_expand_abbreviation_response = 489;
|
||||||
|
LanguageServerShowDocumentRequest language_server_show_document_request = 490;
|
||||||
|
ExecuteLspCommand execute_lsp_command = 491;
|
||||||
|
ExecuteLspCommandResponse execute_lsp_command_response = 492; // current max
|
||||||
}
|
}
|
||||||
|
|
||||||
reserved 87 to 88;
|
reserved 87 to 88;
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ messages!(
|
|||||||
(LanguageServerLog, Foreground),
|
(LanguageServerLog, Foreground),
|
||||||
(LanguageServerPromptRequest, Foreground),
|
(LanguageServerPromptRequest, Foreground),
|
||||||
(LanguageServerPromptResponse, Foreground),
|
(LanguageServerPromptResponse, Foreground),
|
||||||
|
(LanguageServerShowDocumentRequest, Background),
|
||||||
(LeaveChannelBuffer, Background),
|
(LeaveChannelBuffer, Background),
|
||||||
(LeaveChannelChat, Foreground),
|
(LeaveChannelChat, Foreground),
|
||||||
(LeaveProject, Foreground),
|
(LeaveProject, Foreground),
|
||||||
@@ -203,6 +204,8 @@ messages!(
|
|||||||
(LspExtSwitchSourceHeaderResponse, Background),
|
(LspExtSwitchSourceHeaderResponse, Background),
|
||||||
(LspExtGoToParentModule, Background),
|
(LspExtGoToParentModule, Background),
|
||||||
(LspExtGoToParentModuleResponse, Background),
|
(LspExtGoToParentModuleResponse, Background),
|
||||||
|
(ExecuteLspCommand, Background),
|
||||||
|
(ExecuteLspCommandResponse, Background),
|
||||||
(LspExtCancelFlycheck, Background),
|
(LspExtCancelFlycheck, Background),
|
||||||
(LspExtRunFlycheck, Background),
|
(LspExtRunFlycheck, Background),
|
||||||
(LspExtClearFlycheck, Background),
|
(LspExtClearFlycheck, Background),
|
||||||
@@ -578,6 +581,7 @@ request_messages!(
|
|||||||
(StopLanguageServers, Ack),
|
(StopLanguageServers, Ack),
|
||||||
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
||||||
(LspExtGoToParentModule, LspExtGoToParentModuleResponse),
|
(LspExtGoToParentModule, LspExtGoToParentModuleResponse),
|
||||||
|
(ExecuteLspCommand, ExecuteLspCommandResponse),
|
||||||
(LspExtCancelFlycheck, Ack),
|
(LspExtCancelFlycheck, Ack),
|
||||||
(LspExtRunFlycheck, Ack),
|
(LspExtRunFlycheck, Ack),
|
||||||
(LspExtClearFlycheck, Ack),
|
(LspExtClearFlycheck, Ack),
|
||||||
@@ -589,6 +593,7 @@ request_messages!(
|
|||||||
(GetPermalinkToLine, GetPermalinkToLineResponse),
|
(GetPermalinkToLine, GetPermalinkToLineResponse),
|
||||||
(FlushBufferedMessages, Ack),
|
(FlushBufferedMessages, Ack),
|
||||||
(LanguageServerPromptRequest, LanguageServerPromptResponse),
|
(LanguageServerPromptRequest, LanguageServerPromptResponse),
|
||||||
|
(LanguageServerShowDocumentRequest, Ack),
|
||||||
(GitGetBranches, GitBranchesResponse),
|
(GitGetBranches, GitBranchesResponse),
|
||||||
(UpdateGitBranch, Ack),
|
(UpdateGitBranch, Ack),
|
||||||
(ListToolchains, ListToolchainsResponse),
|
(ListToolchains, ListToolchainsResponse),
|
||||||
@@ -809,6 +814,7 @@ entity_messages!(
|
|||||||
LspExtRunnables,
|
LspExtRunnables,
|
||||||
LspExtSwitchSourceHeader,
|
LspExtSwitchSourceHeader,
|
||||||
LspExtGoToParentModule,
|
LspExtGoToParentModule,
|
||||||
|
ExecuteLspCommand,
|
||||||
LspExtCancelFlycheck,
|
LspExtCancelFlycheck,
|
||||||
LspExtRunFlycheck,
|
LspExtRunFlycheck,
|
||||||
LspExtClearFlycheck,
|
LspExtClearFlycheck,
|
||||||
@@ -820,6 +826,7 @@ entity_messages!(
|
|||||||
GetFilePermalink,
|
GetFilePermalink,
|
||||||
GetPermalinkToLine,
|
GetPermalinkToLine,
|
||||||
LanguageServerPromptRequest,
|
LanguageServerPromptRequest,
|
||||||
|
LanguageServerShowDocumentRequest,
|
||||||
GitGetBranches,
|
GitGetBranches,
|
||||||
UpdateGitBranch,
|
UpdateGitBranch,
|
||||||
ListToolchains,
|
ListToolchains,
|
||||||
|
|||||||
@@ -479,6 +479,33 @@ impl HeadlessProject {
|
|||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
LspStoreEvent::LanguageServerShowDocument(show_document_request) => {
|
||||||
|
let request = self
|
||||||
|
.session
|
||||||
|
.request(proto::LanguageServerShowDocumentRequest {
|
||||||
|
project_id: REMOTE_SERVER_PROJECT_ID,
|
||||||
|
uri: show_document_request.uri.as_str().to_owned(),
|
||||||
|
external: show_document_request.external,
|
||||||
|
take_focus: show_document_request.take_focus,
|
||||||
|
selection_start: show_document_request.selection.map(|selection| {
|
||||||
|
proto::PointUtf16 {
|
||||||
|
row: selection.start.line,
|
||||||
|
column: selection.start.character,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
selection_end: show_document_request.selection.map(|selection| {
|
||||||
|
proto::PointUtf16 {
|
||||||
|
row: selection.end.line,
|
||||||
|
column: selection.end.character,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let show_document_request = show_document_request.clone();
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
show_document_request.respond(request.await.is_ok());
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
LspStoreEvent::LanguageServerPrompt(prompt) => {
|
LspStoreEvent::LanguageServerPrompt(prompt) => {
|
||||||
let request = self.session.request(proto::LanguageServerPromptRequest {
|
let request = self.session.request(proto::LanguageServerPromptRequest {
|
||||||
project_id: REMOTE_SERVER_PROJECT_ID,
|
project_id: REMOTE_SERVER_PROJECT_ID,
|
||||||
|
|||||||
@@ -4391,6 +4391,242 @@ async fn test_remote_apply_code_action_skips_unadvertised_command(
|
|||||||
assert_eq!(transaction.0.len(), 0);
|
assert_eq!(transaction.0.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_remote_lsp_show_document(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||||
|
let fs = FakeFs::new(server_cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/code"),
|
||||||
|
json!({
|
||||||
|
"project1": {
|
||||||
|
".git": {},
|
||||||
|
"src": {
|
||||||
|
"lib.rs": "fn one() -> usize { 1 }",
|
||||||
|
"other.rs": "fn two() -> usize { 2 }"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (project, headless) = init_test(&fs, cx, server_cx).await;
|
||||||
|
|
||||||
|
cx.update_entity(&project, |project, _| {
|
||||||
|
project.languages().register_test_language(LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
matcher: (LanguageMatcher {
|
||||||
|
path_suffixes: vec!["rs".into()],
|
||||||
|
..LanguageMatcher::default()
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
..LanguageConfig::default()
|
||||||
|
});
|
||||||
|
project.languages().register_fake_lsp_adapter(
|
||||||
|
"Rust",
|
||||||
|
FakeLspAdapter {
|
||||||
|
name: "rust-analyzer",
|
||||||
|
..FakeLspAdapter::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut fake_lsp = server_cx.update(|cx| {
|
||||||
|
headless.read(cx).languages.register_fake_lsp_server(
|
||||||
|
LanguageServerName("rust-analyzer".into()),
|
||||||
|
lsp::ServerCapabilities::default(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let worktree_id = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_worktree(path!("/code/project1"), true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.read_with(cx, |worktree, _| worktree.id());
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let (_buffer, _handle) = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_buffer_with_lsp((worktree_id, rel_path("src/lib.rs")), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let fake_lsp = fake_lsp.next().await.unwrap();
|
||||||
|
|
||||||
|
let shown_uri = lsp::Uri::from_file_path(path!("/code/project1/src/other.rs")).unwrap();
|
||||||
|
let shown_selection = lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6));
|
||||||
|
let handled_requests = Arc::new(AtomicUsize::new(0));
|
||||||
|
cx.update({
|
||||||
|
let project = project.clone();
|
||||||
|
let shown_uri = shown_uri.clone();
|
||||||
|
let handled_requests = handled_requests.clone();
|
||||||
|
move |cx| {
|
||||||
|
cx.subscribe(&project, move |_, event, _| {
|
||||||
|
if let project::Event::LanguageServerShowDocument(request) = event {
|
||||||
|
assert_eq!(request.uri, shown_uri);
|
||||||
|
assert_eq!(request.selection, Some(shown_selection));
|
||||||
|
assert_eq!((request.external, request.take_focus), (false, true));
|
||||||
|
handled_requests.fetch_add(1, Ordering::Release);
|
||||||
|
request.clone().respond(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = fake_lsp
|
||||||
|
.request::<lsp::request::ShowDocument>(
|
||||||
|
lsp::ShowDocumentParams {
|
||||||
|
uri: shown_uri,
|
||||||
|
external: None,
|
||||||
|
take_focus: Some(true),
|
||||||
|
selection: Some(shown_selection),
|
||||||
|
},
|
||||||
|
DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_response()
|
||||||
|
.expect("show document request should not error");
|
||||||
|
assert_eq!(response, lsp::ShowDocumentResult { success: true });
|
||||||
|
assert_eq!(handled_requests.load(Ordering::Acquire), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_remote_execute_lsp_command(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||||
|
let fs = FakeFs::new(server_cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/code"),
|
||||||
|
json!({
|
||||||
|
"project1": {
|
||||||
|
".git": {},
|
||||||
|
"src": {
|
||||||
|
"lib.rs": "fn one() -> usize { 1 }"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (project, headless) = init_test(&fs, cx, server_cx).await;
|
||||||
|
|
||||||
|
cx.update_entity(&project, |project, _| {
|
||||||
|
project.languages().register_test_language(LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
matcher: (LanguageMatcher {
|
||||||
|
path_suffixes: vec!["rs".into()],
|
||||||
|
..LanguageMatcher::default()
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
..LanguageConfig::default()
|
||||||
|
});
|
||||||
|
project.languages().register_fake_lsp_adapter(
|
||||||
|
"Rust",
|
||||||
|
FakeLspAdapter {
|
||||||
|
name: "rust-analyzer",
|
||||||
|
..FakeLspAdapter::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut fake_lsp = server_cx.update(|cx| {
|
||||||
|
headless.read(cx).languages.register_fake_lsp_server(
|
||||||
|
LanguageServerName("rust-analyzer".into()),
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
execute_command_provider: Some(lsp::ExecuteCommandOptions {
|
||||||
|
commands: vec!["the-command".to_string()],
|
||||||
|
..lsp::ExecuteCommandOptions::default()
|
||||||
|
}),
|
||||||
|
..lsp::ServerCapabilities::default()
|
||||||
|
},
|
||||||
|
Some(Box::new(|fake| {
|
||||||
|
fake.set_request_handler::<lsp::request::ExecuteCommand, _, _>(
|
||||||
|
|params, _| async move {
|
||||||
|
assert_eq!(params.command, "the-command");
|
||||||
|
assert_eq!(params.arguments, vec![json!("foo"), json!(42)]);
|
||||||
|
Ok(Some(json!({"answer": 3})))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let worktree_id = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_worktree(path!("/code/project1"), true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.read_with(cx, |worktree, _| worktree.id());
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let (_buffer, _handle) = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_buffer_with_lsp((worktree_id, rel_path("src/lib.rs")), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let _fake_lsp = fake_lsp.next().await.unwrap();
|
||||||
|
|
||||||
|
let server_id = cx.read(|cx| {
|
||||||
|
project
|
||||||
|
.read(cx)
|
||||||
|
.language_server_statuses(cx)
|
||||||
|
.next()
|
||||||
|
.expect("a language server should be running")
|
||||||
|
.0
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||||
|
lsp_store.execute_lsp_command(
|
||||||
|
server_id,
|
||||||
|
"the-command".to_string(),
|
||||||
|
vec![json!("foo"), json!(42)],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("executing an advertised command should succeed");
|
||||||
|
assert_eq!(result, Some(json!({"answer": 3})));
|
||||||
|
|
||||||
|
let unadvertised = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||||
|
lsp_store.execute_lsp_command(
|
||||||
|
server_id,
|
||||||
|
"unadvertised-command".to_string(),
|
||||||
|
Vec::new(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
unadvertised
|
||||||
|
.err()
|
||||||
|
.map(|error| format!("{error:#}").contains("not advertised")),
|
||||||
|
Some(true),
|
||||||
|
"unadvertised commands should be rejected"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_remote_restore_unstaged_hunk_clears_diff(
|
async fn test_remote_restore_unstaged_hunk_clears_diff(
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ language_selector.workspace = true
|
|||||||
language_tools.workspace = true
|
language_tools.workspace = true
|
||||||
languages = { workspace = true, features = ["load-grammars"] }
|
languages = { workspace = true, features = ["load-grammars"] }
|
||||||
line_ending_selector.workspace = true
|
line_ending_selector.workspace = true
|
||||||
|
lsp_command_selector.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp_locations.workspace = true
|
lsp_locations.workspace = true
|
||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
|
|||||||
@@ -762,6 +762,7 @@ fn main() {
|
|||||||
encoding_selector::init(cx);
|
encoding_selector::init(cx);
|
||||||
language_selector::init(cx);
|
language_selector::init(cx);
|
||||||
line_ending_selector::init(cx);
|
line_ending_selector::init(cx);
|
||||||
|
lsp_command_selector::init(cx);
|
||||||
toolchain_selector::init(cx);
|
toolchain_selector::init(cx);
|
||||||
theme_selector::init(cx);
|
theme_selector::init(cx);
|
||||||
settings_profile_selector::init(cx);
|
settings_profile_selector::init(cx);
|
||||||
|
|||||||
@@ -5935,6 +5935,7 @@ mod tests {
|
|||||||
"language_selector",
|
"language_selector",
|
||||||
"welcome",
|
"welcome",
|
||||||
"line_ending_selector",
|
"line_ending_selector",
|
||||||
|
"lsp_command_selector",
|
||||||
"lsp_tool",
|
"lsp_tool",
|
||||||
"markdown",
|
"markdown",
|
||||||
"menu",
|
"menu",
|
||||||
|
|||||||
Reference in new issue
Block a user