Re-applies what's been reverted in https://github.com/zed-industries/zed/pull/26832 with an action-related fix in64b5d37d32
Before, actions were resolved only if `data` is present and either of the possible fields is empty:e842b4eade/crates/project/src/lsp_store.rs (L1632-L1633)
But Zed resolves completions and inlays once, unconditionally, and the reverted PR applied the same strategy to actions. That did not work despite the spec not forbidding `data`-less actions to be resolved. Soon, it starts to work due to https://github.com/rust-lang/rust-analyzer/pull/19369 but it seems safer to restore the original filtering code. Code lens have no issues with `data`-less resolves:220d913cbc/crates/rust-analyzer/src/handlers/request.rs (L1618-L1620)
so the same approach as completions and inlays is kept: resolve once. Release Notes: - N/A
This commit is contained in:
parent
ef91e7afae
commit
8a31dcaeb0
13 changed files with 618 additions and 17 deletions
|
@ -3569,6 +3569,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
title: "Fix with Assistant".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
resolved: true,
|
||||
}]))
|
||||
} else {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
|
|
|
@ -1729,6 +1729,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
title: "Fix with Assistant".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
resolved: true,
|
||||
}]))
|
||||
} else {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
|
|
|
@ -307,6 +307,7 @@ impl Server {
|
|||
.add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::GetCodeLens>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitGetBranches>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenUnstagedDiff>)
|
||||
|
@ -347,6 +348,7 @@ impl Server {
|
|||
.add_message_handler(create_buffer_for_peer)
|
||||
.add_request_handler(update_buffer)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::RefreshInlayHints>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::RefreshCodeLens>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::UpdateBufferFile>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::BufferReloaded>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::BufferSaved>)
|
||||
|
|
|
@ -69,7 +69,7 @@ pub use element::{
|
|||
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
|
||||
};
|
||||
use futures::{
|
||||
future::{self, Shared},
|
||||
future::{self, join, Shared},
|
||||
FutureExt,
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
|
@ -82,10 +82,10 @@ use code_context_menus::{
|
|||
use git::blame::GitBlame;
|
||||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
|
||||
AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds,
|
||||
ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler,
|
||||
EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global,
|
||||
HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
|
||||
AnimationExt, AnyElement, App, AppContext, AsyncWindowContext, AvailableSpace, Background,
|
||||
Bounds, ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity,
|
||||
EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight,
|
||||
Global, HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
|
||||
ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription,
|
||||
Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
|
||||
WeakEntity, WeakFocusHandle, Window,
|
||||
|
@ -1233,11 +1233,15 @@ impl Editor {
|
|||
project_subscriptions.push(cx.subscribe_in(
|
||||
project,
|
||||
window,
|
||||
|editor, _, event, window, cx| {
|
||||
if let project::Event::RefreshInlayHints = event {
|
||||
|editor, _, event, window, cx| match event {
|
||||
project::Event::RefreshCodeLens => {
|
||||
// we always query lens with actions, without storing them, always refreshing them
|
||||
}
|
||||
project::Event::RefreshInlayHints => {
|
||||
editor
|
||||
.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
|
||||
} else if let project::Event::SnippetEdit(id, snippet_edits) = event {
|
||||
}
|
||||
project::Event::SnippetEdit(id, snippet_edits) => {
|
||||
if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
if focus_handle.is_focused(window) {
|
||||
|
@ -1257,6 +1261,7 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
));
|
||||
if let Some(task_inventory) = project
|
||||
|
@ -17027,7 +17032,16 @@ impl CodeActionProvider for Entity<Project> {
|
|||
cx: &mut App,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
self.update(cx, |project, cx| {
|
||||
project.code_actions(buffer, range, None, cx)
|
||||
let code_lens = project.code_lens(buffer, range.clone(), cx);
|
||||
let code_actions = project.code_actions(buffer, range, None, cx);
|
||||
cx.background_spawn(async move {
|
||||
let (code_lens, code_actions) = join(code_lens, code_actions).await;
|
||||
Ok(code_lens
|
||||
.context("code lens fetch")?
|
||||
.into_iter()
|
||||
.chain(code_actions.context("code action fetch")?)
|
||||
.collect())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -17233,6 +17233,187 @@ async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
|
|||
"});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"a.ts": "a",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
|
||||
)));
|
||||
let mut fake_language_servers = language_registry.register_fake_lsp(
|
||||
"TypeScript",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
code_lens_provider: Some(lsp::CodeLensOptions {
|
||||
resolve_provider: Some(true),
|
||||
}),
|
||||
execute_command_provider: Some(lsp::ExecuteCommandOptions {
|
||||
commands: vec!["_the/command".to_string()],
|
||||
..lsp::ExecuteCommandOptions::default()
|
||||
}),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
let (buffer, _handle) = project
|
||||
.update(cx, |p, cx| {
|
||||
p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let fake_server = fake_language_servers.next().await.unwrap();
|
||||
|
||||
let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
|
||||
drop(buffer_snapshot);
|
||||
let actions = cx
|
||||
.update_window(*workspace, |_, window, cx| {
|
||||
project.code_actions(&buffer, anchor..anchor, window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
fake_server
|
||||
.handle_request::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
|
||||
Ok(Some(vec![
|
||||
lsp::CodeLens {
|
||||
range: lsp::Range::default(),
|
||||
command: Some(lsp::Command {
|
||||
title: "Code lens command".to_owned(),
|
||||
command: "_the/command".to_owned(),
|
||||
arguments: None,
|
||||
}),
|
||||
data: None,
|
||||
},
|
||||
lsp::CodeLens {
|
||||
range: lsp::Range::default(),
|
||||
command: Some(lsp::Command {
|
||||
title: "Command not in capabilities".to_owned(),
|
||||
command: "not in capabilities".to_owned(),
|
||||
arguments: None,
|
||||
}),
|
||||
data: None,
|
||||
},
|
||||
lsp::CodeLens {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 1,
|
||||
character: 1,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 1,
|
||||
character: 1,
|
||||
},
|
||||
},
|
||||
command: Some(lsp::Command {
|
||||
title: "Command not in range".to_owned(),
|
||||
command: "_the/command".to_owned(),
|
||||
arguments: None,
|
||||
}),
|
||||
data: None,
|
||||
},
|
||||
]))
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
let actions = actions.await.unwrap();
|
||||
assert_eq!(
|
||||
actions.len(),
|
||||
1,
|
||||
"Should have only one valid action for the 0..0 range"
|
||||
);
|
||||
let action = actions[0].clone();
|
||||
let apply = project.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), action, true, cx)
|
||||
});
|
||||
|
||||
// Resolving the code action does not populate its edits. In absence of
|
||||
// edits, we must execute the given command.
|
||||
fake_server.handle_request::<lsp::request::CodeLensResolve, _, _>(|mut lens, _| async move {
|
||||
let lens_command = lens.command.as_mut().expect("should have a command");
|
||||
assert_eq!(lens_command.title, "Code lens command");
|
||||
lens_command.arguments = Some(vec![json!("the-argument")]);
|
||||
Ok(lens)
|
||||
});
|
||||
|
||||
// While executing the command, the language server sends the editor
|
||||
// a `workspaceEdit` request.
|
||||
fake_server
|
||||
.handle_request::<lsp::request::ExecuteCommand, _, _>({
|
||||
let fake = fake_server.clone();
|
||||
move |params, _| {
|
||||
assert_eq!(params.command, "_the/command");
|
||||
let fake = fake.clone();
|
||||
async move {
|
||||
fake.server
|
||||
.request::<lsp::request::ApplyWorkspaceEdit>(
|
||||
lsp::ApplyWorkspaceEditParams {
|
||||
label: None,
|
||||
edit: lsp::WorkspaceEdit {
|
||||
changes: Some(
|
||||
[(
|
||||
lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
|
||||
vec![lsp::TextEdit {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
lsp::Position::new(0, 0),
|
||||
),
|
||||
new_text: "X".into(),
|
||||
}],
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(Some(json!(null)))
|
||||
}
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
// Applying the code lens command returns a project transaction containing the edits
|
||||
// sent by the language server in its `workspaceEdit` request.
|
||||
let transaction = apply.await.unwrap();
|
||||
assert!(transaction.0.contains_key(&buffer));
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
assert_eq!(buffer.text(), "Xa");
|
||||
buffer.undo(cx);
|
||||
assert_eq!(buffer.text(), "a");
|
||||
});
|
||||
}
|
||||
|
||||
mod autoclose_tags {
|
||||
use super::*;
|
||||
use language::language_settings::JsxTagAutoCloseSettings;
|
||||
|
|
|
@ -7,7 +7,7 @@ use gpui::{App, AsyncApp, Task};
|
|||
use http_client::github::AssetKind;
|
||||
use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
pub use language::*;
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use lsp::LanguageServerBinary;
|
||||
use regex::Regex;
|
||||
use smol::fs::{self};
|
||||
use std::fmt::Display;
|
||||
|
|
|
@ -632,6 +632,9 @@ impl LanguageServer {
|
|||
diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
|
||||
refresh_support: None,
|
||||
}),
|
||||
code_lens: Some(CodeLensWorkspaceClientCapabilities {
|
||||
refresh_support: Some(true),
|
||||
}),
|
||||
workspace_edit: Some(WorkspaceEditClientCapabilities {
|
||||
resource_operations: Some(vec![
|
||||
ResourceOperationKind::Create,
|
||||
|
@ -763,6 +766,9 @@ impl LanguageServer {
|
|||
did_save: Some(true),
|
||||
..TextDocumentSyncClientCapabilities::default()
|
||||
}),
|
||||
code_lens: Some(CodeLensClientCapabilities {
|
||||
dynamic_registration: Some(false),
|
||||
}),
|
||||
..TextDocumentClientCapabilities::default()
|
||||
}),
|
||||
experimental: Some(json!({
|
||||
|
|
|
@ -234,6 +234,19 @@ pub(crate) struct InlayHints {
|
|||
pub range: Range<Anchor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct GetCodeLens;
|
||||
|
||||
impl GetCodeLens {
|
||||
pub(crate) fn can_resolve_lens(capabilities: &ServerCapabilities) -> bool {
|
||||
capabilities
|
||||
.code_lens_provider
|
||||
.as_ref()
|
||||
.and_then(|code_lens_options| code_lens_options.resolve_provider)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LinkedEditingRange {
|
||||
pub position: Anchor,
|
||||
|
@ -2229,18 +2242,18 @@ impl LspCommand for GetCodeActions {
|
|||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let lsp_action = match entry {
|
||||
let (lsp_action, resolved) = match entry {
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp_action) => {
|
||||
if let Some(command) = lsp_action.command.as_ref() {
|
||||
if !available_commands.contains(&command.command) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
LspAction::Action(Box::new(lsp_action))
|
||||
(LspAction::Action(Box::new(lsp_action)), false)
|
||||
}
|
||||
lsp::CodeActionOrCommand::Command(command) => {
|
||||
if available_commands.contains(&command.command) {
|
||||
LspAction::Command(command)
|
||||
(LspAction::Command(command), true)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
@ -2259,6 +2272,7 @@ impl LspCommand for GetCodeActions {
|
|||
server_id,
|
||||
range: self.range.clone(),
|
||||
lsp_action,
|
||||
resolved,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
|
@ -3037,6 +3051,152 @@ impl LspCommand for InlayHints {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetCodeLens {
|
||||
type Response = Vec<CodeAction>;
|
||||
type LspRequest = lsp::CodeLensRequest;
|
||||
type ProtoRequest = proto::GetCodeLens;
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
"Code Lens"
|
||||
}
|
||||
|
||||
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||
capabilities
|
||||
.server_capabilities
|
||||
.code_lens_provider
|
||||
.as_ref()
|
||||
.map_or(false, |code_lens_options| {
|
||||
code_lens_options.resolve_provider.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<lsp::CodeLensParams> {
|
||||
Ok(lsp::CodeLensParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: file_path_to_lsp_url(path)?,
|
||||
},
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams::default(),
|
||||
partial_result_params: lsp::PartialResultParams::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<Vec<lsp::CodeLens>>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
mut cx: AsyncApp,
|
||||
) -> anyhow::Result<Vec<CodeAction>> {
|
||||
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||
let language_server = cx.update(|cx| {
|
||||
lsp_store
|
||||
.read(cx)
|
||||
.language_server_for_id(server_id)
|
||||
.with_context(|| {
|
||||
format!("Missing the language server that just returned a response {server_id}")
|
||||
})
|
||||
})??;
|
||||
let server_capabilities = language_server.capabilities();
|
||||
let available_commands = server_capabilities
|
||||
.execute_command_provider
|
||||
.as_ref()
|
||||
.map(|options| options.commands.as_slice())
|
||||
.unwrap_or_default();
|
||||
Ok(message
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|code_lens| {
|
||||
code_lens
|
||||
.command
|
||||
.as_ref()
|
||||
.is_none_or(|command| available_commands.contains(&command.command))
|
||||
})
|
||||
.map(|code_lens| {
|
||||
let code_lens_range = range_from_lsp(code_lens.range);
|
||||
let start = snapshot.clip_point_utf16(code_lens_range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(code_lens_range.end, Bias::Right);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
CodeAction {
|
||||
server_id,
|
||||
range,
|
||||
lsp_action: LspAction::CodeLens(code_lens),
|
||||
resolved: false,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeLens {
|
||||
proto::GetCodeLens {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id().into(),
|
||||
version: serialize_version(&buffer.version()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: proto::GetCodeLens,
|
||||
_: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Vec<CodeAction>,
|
||||
_: &mut LspStore,
|
||||
_: PeerId,
|
||||
buffer_version: &clock::Global,
|
||||
_: &mut App,
|
||||
) -> proto::GetCodeLensResponse {
|
||||
proto::GetCodeLensResponse {
|
||||
lens_actions: response
|
||||
.iter()
|
||||
.map(LspStore::serialize_code_action)
|
||||
.collect(),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetCodeLensResponse,
|
||||
_: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> anyhow::Result<Vec<CodeAction>> {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
message
|
||||
.lens_actions
|
||||
.into_iter()
|
||||
.map(LspStore::deserialize_code_action)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.context("deserializing proto code lens response")
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetCodeLens) -> Result<BufferId> {
|
||||
BufferId::new(message.buffer_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for LinkedEditingRange {
|
||||
type Response = Vec<Range<Anchor>>;
|
||||
|
|
|
@ -807,6 +807,27 @@ impl LocalLspStore {
|
|||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::CodeLensRefresh, _, _>({
|
||||
let this = this.clone();
|
||||
move |(), mut cx| {
|
||||
let this = this.clone();
|
||||
async move {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.emit(LspStoreEvent::RefreshCodeLens);
|
||||
this.downstream_client.as_ref().map(|(client, project_id)| {
|
||||
client.send(proto::RefreshCodeLens {
|
||||
project_id: *project_id,
|
||||
})
|
||||
})
|
||||
})?
|
||||
.transpose()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||
let this = this.clone();
|
||||
|
@ -1628,7 +1649,8 @@ impl LocalLspStore {
|
|||
) -> anyhow::Result<()> {
|
||||
match &mut action.lsp_action {
|
||||
LspAction::Action(lsp_action) => {
|
||||
if GetCodeActions::can_resolve_actions(&lang_server.capabilities())
|
||||
if !action.resolved
|
||||
&& GetCodeActions::can_resolve_actions(&lang_server.capabilities())
|
||||
&& lsp_action.data.is_some()
|
||||
&& (lsp_action.command.is_none() || lsp_action.edit.is_none())
|
||||
{
|
||||
|
@ -1639,8 +1661,17 @@ impl LocalLspStore {
|
|||
);
|
||||
}
|
||||
}
|
||||
LspAction::CodeLens(lens) => {
|
||||
if !action.resolved && GetCodeLens::can_resolve_lens(&lang_server.capabilities()) {
|
||||
*lens = lang_server
|
||||
.request::<lsp::request::CodeLensResolve>(lens.clone())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
LspAction::Command(_) => {}
|
||||
}
|
||||
|
||||
action.resolved = true;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
|
@ -2887,6 +2918,7 @@ pub enum LspStoreEvent {
|
|||
},
|
||||
Notification(String),
|
||||
RefreshInlayHints,
|
||||
RefreshCodeLens,
|
||||
DiagnosticsUpdated {
|
||||
language_server_id: LanguageServerId,
|
||||
path: ProjectPath,
|
||||
|
@ -2942,6 +2974,7 @@ impl LspStore {
|
|||
client.add_entity_request_handler(Self::handle_resolve_inlay_hint);
|
||||
client.add_entity_request_handler(Self::handle_open_buffer_for_symbol);
|
||||
client.add_entity_request_handler(Self::handle_refresh_inlay_hints);
|
||||
client.add_entity_request_handler(Self::handle_refresh_code_lens);
|
||||
client.add_entity_request_handler(Self::handle_on_type_formatting);
|
||||
client.add_entity_request_handler(Self::handle_apply_additional_edits_for_completion);
|
||||
client.add_entity_request_handler(Self::handle_register_buffer_with_language_servers);
|
||||
|
@ -4316,6 +4349,7 @@ impl LspStore {
|
|||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn code_actions(
|
||||
&mut self,
|
||||
buffer_handle: &Entity<Buffer>,
|
||||
|
@ -4395,6 +4429,66 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn code_lens(
|
||||
&mut self,
|
||||
buffer_handle: &Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
if let Some((upstream_client, project_id)) = self.upstream_client() {
|
||||
let request_task = upstream_client.request(proto::MultiLspQuery {
|
||||
buffer_id: buffer_handle.read(cx).remote_id().into(),
|
||||
version: serialize_version(&buffer_handle.read(cx).version()),
|
||||
project_id,
|
||||
strategy: Some(proto::multi_lsp_query::Strategy::All(
|
||||
proto::AllLanguageServers {},
|
||||
)),
|
||||
request: Some(proto::multi_lsp_query::Request::GetCodeLens(
|
||||
GetCodeLens.to_proto(project_id, buffer_handle.read(cx)),
|
||||
)),
|
||||
});
|
||||
let buffer = buffer_handle.clone();
|
||||
cx.spawn(|weak_project, cx| async move {
|
||||
let Some(project) = weak_project.upgrade() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let responses = request_task.await?.responses;
|
||||
let code_lens = join_all(
|
||||
responses
|
||||
.into_iter()
|
||||
.filter_map(|lsp_response| match lsp_response.response? {
|
||||
proto::lsp_response::Response::GetCodeLensResponse(response) => {
|
||||
Some(response)
|
||||
}
|
||||
unexpected => {
|
||||
debug_panic!("Unexpected response: {unexpected:?}");
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|code_lens_response| {
|
||||
GetCodeLens.response_from_proto(
|
||||
code_lens_response,
|
||||
project.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(code_lens
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<Vec<_>>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect())
|
||||
})
|
||||
} else {
|
||||
let code_lens_task =
|
||||
self.request_multiple_lsp_locally(buffer_handle, None::<usize>, GetCodeLens, cx);
|
||||
cx.spawn(|_, _| async move { Ok(code_lens_task.await.into_iter().flatten().collect()) })
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn completions(
|
||||
&self,
|
||||
|
@ -6308,6 +6402,43 @@ impl LspStore {
|
|||
.collect(),
|
||||
})
|
||||
}
|
||||
Some(proto::multi_lsp_query::Request::GetCodeLens(get_code_lens)) => {
|
||||
let get_code_lens = GetCodeLens::from_proto(
|
||||
get_code_lens,
|
||||
this.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let code_lens_actions = this
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.request_multiple_lsp_locally(
|
||||
&buffer,
|
||||
None::<usize>,
|
||||
get_code_lens,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.into_iter();
|
||||
|
||||
this.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: code_lens_actions
|
||||
.map(|actions| proto::LspResponse {
|
||||
response: Some(proto::lsp_response::Response::GetCodeLensResponse(
|
||||
GetCodeLens::response_to_proto(
|
||||
actions,
|
||||
project,
|
||||
sender_id,
|
||||
&buffer_version,
|
||||
cx,
|
||||
),
|
||||
)),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
None => anyhow::bail!("empty multi lsp query request"),
|
||||
}
|
||||
}
|
||||
|
@ -7211,6 +7342,17 @@ impl LspStore {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_refresh_code_lens(
|
||||
this: Entity<Self>,
|
||||
_: TypedEnvelope<proto::RefreshCodeLens>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.emit(LspStoreEvent::RefreshCodeLens);
|
||||
})?;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_open_buffer_for_symbol(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::OpenBufferForSymbol>,
|
||||
|
@ -8434,6 +8576,10 @@ impl LspStore {
|
|||
proto::code_action::Kind::Command as i32,
|
||||
serde_json::to_vec(command).unwrap(),
|
||||
),
|
||||
LspAction::CodeLens(code_lens) => (
|
||||
proto::code_action::Kind::CodeLens as i32,
|
||||
serde_json::to_vec(code_lens).unwrap(),
|
||||
),
|
||||
};
|
||||
|
||||
proto::CodeAction {
|
||||
|
@ -8442,6 +8588,7 @@ impl LspStore {
|
|||
end: Some(serialize_anchor(&action.range.end)),
|
||||
lsp_action,
|
||||
kind,
|
||||
resolved: action.resolved,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8449,11 +8596,11 @@ impl LspStore {
|
|||
let start = action
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid start"))?;
|
||||
.context("invalid start")?;
|
||||
let end = action
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid end"))?;
|
||||
.context("invalid end")?;
|
||||
let lsp_action = match proto::code_action::Kind::from_i32(action.kind) {
|
||||
Some(proto::code_action::Kind::Action) => {
|
||||
LspAction::Action(serde_json::from_slice(&action.lsp_action)?)
|
||||
|
@ -8461,11 +8608,15 @@ impl LspStore {
|
|||
Some(proto::code_action::Kind::Command) => {
|
||||
LspAction::Command(serde_json::from_slice(&action.lsp_action)?)
|
||||
}
|
||||
Some(proto::code_action::Kind::CodeLens) => {
|
||||
LspAction::CodeLens(serde_json::from_slice(&action.lsp_action)?)
|
||||
}
|
||||
None => anyhow::bail!("Unknown action kind {}", action.kind),
|
||||
};
|
||||
Ok(CodeAction {
|
||||
server_id: LanguageServerId(action.server_id as usize),
|
||||
range: start..end,
|
||||
resolved: action.resolved,
|
||||
lsp_action,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,14 @@ use crate::{LanguageServerPromptRequest, LspStore, LspStoreEvent};
|
|||
|
||||
pub const RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||
|
||||
pub const EXTRA_SUPPORTED_COMMANDS: &[&str] = &[
|
||||
"rust-analyzer.runSingle",
|
||||
"rust-analyzer.showReferences",
|
||||
"rust-analyzer.gotoLocation",
|
||||
"rust-analyzer.triggerParameterHints",
|
||||
"rust-analyzer.rename",
|
||||
];
|
||||
|
||||
/// Experimental: Informs the end user about the state of the server
|
||||
///
|
||||
/// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status)
|
||||
|
|
|
@ -280,6 +280,7 @@ pub enum Event {
|
|||
Reshared,
|
||||
Rejoined,
|
||||
RefreshInlayHints,
|
||||
RefreshCodeLens,
|
||||
RevealInProjectPanel(ProjectEntryId),
|
||||
SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
|
||||
ExpandedAllForEntry(WorktreeId, ProjectEntryId),
|
||||
|
@ -509,6 +510,8 @@ pub struct CodeAction {
|
|||
/// The raw code action provided by the language server.
|
||||
/// Can be either an action or a command.
|
||||
pub lsp_action: LspAction,
|
||||
/// Whether the action needs to be resolved using the language server.
|
||||
pub resolved: bool,
|
||||
}
|
||||
|
||||
/// An action sent back by a language server.
|
||||
|
@ -519,6 +522,8 @@ pub enum LspAction {
|
|||
Action(Box<lsp::CodeAction>),
|
||||
/// A command data to run as an action.
|
||||
Command(lsp::Command),
|
||||
/// A code lens data to run as an action.
|
||||
CodeLens(lsp::CodeLens),
|
||||
}
|
||||
|
||||
impl LspAction {
|
||||
|
@ -526,6 +531,11 @@ impl LspAction {
|
|||
match self {
|
||||
Self::Action(action) => &action.title,
|
||||
Self::Command(command) => &command.title,
|
||||
Self::CodeLens(lens) => lens
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| command.title.as_str())
|
||||
.unwrap_or("Unknown command"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -533,6 +543,7 @@ impl LspAction {
|
|||
match self {
|
||||
Self::Action(action) => action.kind.clone(),
|
||||
Self::Command(_) => Some(lsp::CodeActionKind::new("command")),
|
||||
Self::CodeLens(_) => Some(lsp::CodeActionKind::new("code lens")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,6 +551,7 @@ impl LspAction {
|
|||
match self {
|
||||
Self::Action(action) => action.edit.as_ref(),
|
||||
Self::Command(_) => None,
|
||||
Self::CodeLens(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,6 +559,7 @@ impl LspAction {
|
|||
match self {
|
||||
Self::Action(action) => action.command.as_ref(),
|
||||
Self::Command(command) => Some(command),
|
||||
Self::CodeLens(lens) => lens.command.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2483,6 +2496,7 @@ impl Project {
|
|||
};
|
||||
}
|
||||
LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
|
||||
LspStoreEvent::RefreshCodeLens => cx.emit(Event::RefreshCodeLens),
|
||||
LspStoreEvent::LanguageServerPrompt(prompt) => {
|
||||
cx.emit(Event::LanguageServerPrompt(prompt.clone()))
|
||||
}
|
||||
|
@ -3163,6 +3177,34 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn code_lens<T: Clone + ToOffset>(
|
||||
&mut self,
|
||||
buffer_handle: &Entity<Buffer>,
|
||||
range: Range<T>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
let snapshot = buffer_handle.read(cx).snapshot();
|
||||
let range = snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end);
|
||||
let code_lens_actions = self
|
||||
.lsp_store
|
||||
.update(cx, |lsp_store, cx| lsp_store.code_lens(buffer_handle, cx));
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let mut code_lens_actions = code_lens_actions.await?;
|
||||
code_lens_actions.retain(|code_lens_action| {
|
||||
range
|
||||
.start
|
||||
.cmp(&code_lens_action.range.start, &snapshot)
|
||||
.is_ge()
|
||||
&& range
|
||||
.end
|
||||
.cmp(&code_lens_action.range.end, &snapshot)
|
||||
.is_le()
|
||||
});
|
||||
Ok(code_lens_actions)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_code_action(
|
||||
&self,
|
||||
buffer_handle: Entity<Buffer>,
|
||||
|
|
|
@ -346,7 +346,12 @@ message Envelope {
|
|||
GitDiff git_diff = 319;
|
||||
GitDiffResponse git_diff_response = 320;
|
||||
|
||||
GitInit git_init = 321; // current max
|
||||
GitInit git_init = 321;
|
||||
|
||||
CodeLens code_lens = 322;
|
||||
GetCodeLens get_code_lens = 323;
|
||||
GetCodeLensResponse get_code_lens_response = 324;
|
||||
RefreshCodeLens refresh_code_lens = 325; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
@ -1263,6 +1268,25 @@ message RefreshInlayHints {
|
|||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message CodeLens {
|
||||
bytes lsp_lens = 1;
|
||||
}
|
||||
|
||||
message GetCodeLens {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
repeated VectorClockEntry version = 3;
|
||||
}
|
||||
|
||||
message GetCodeLensResponse {
|
||||
repeated CodeAction lens_actions = 1;
|
||||
repeated VectorClockEntry version = 2;
|
||||
}
|
||||
|
||||
message RefreshCodeLens {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message MarkupContent {
|
||||
bool is_markdown = 1;
|
||||
string value = 2;
|
||||
|
@ -1298,9 +1322,11 @@ message CodeAction {
|
|||
Anchor end = 3;
|
||||
bytes lsp_action = 4;
|
||||
Kind kind = 5;
|
||||
bool resolved = 6;
|
||||
enum Kind {
|
||||
Action = 0;
|
||||
Command = 1;
|
||||
CodeLens = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2346,6 +2372,7 @@ message MultiLspQuery {
|
|||
GetHover get_hover = 5;
|
||||
GetCodeActions get_code_actions = 6;
|
||||
GetSignatureHelp get_signature_help = 7;
|
||||
GetCodeLens get_code_lens = 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2365,6 +2392,7 @@ message LspResponse {
|
|||
GetHoverResponse get_hover_response = 1;
|
||||
GetCodeActionsResponse get_code_actions_response = 2;
|
||||
GetSignatureHelpResponse get_signature_help_response = 3;
|
||||
GetCodeLensResponse get_code_lens_response = 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -340,6 +340,9 @@ messages!(
|
|||
(ResolveCompletionDocumentationResponse, Background),
|
||||
(ResolveInlayHint, Background),
|
||||
(ResolveInlayHintResponse, Background),
|
||||
(RefreshCodeLens, Background),
|
||||
(GetCodeLens, Background),
|
||||
(GetCodeLensResponse, Background),
|
||||
(RespondToChannelInvite, Foreground),
|
||||
(RespondToContactRequest, Foreground),
|
||||
(RoomUpdated, Foreground),
|
||||
|
@ -513,6 +516,7 @@ request_messages!(
|
|||
(GetUsers, UsersResponse),
|
||||
(IncomingCall, Ack),
|
||||
(InlayHints, InlayHintsResponse),
|
||||
(GetCodeLens, GetCodeLensResponse),
|
||||
(InviteChannelMember, Ack),
|
||||
(JoinChannel, JoinRoomResponse),
|
||||
(JoinChannelBuffer, JoinChannelBufferResponse),
|
||||
|
@ -534,6 +538,7 @@ request_messages!(
|
|||
(PrepareRename, PrepareRenameResponse),
|
||||
(CountLanguageModelTokens, CountLanguageModelTokensResponse),
|
||||
(RefreshInlayHints, Ack),
|
||||
(RefreshCodeLens, Ack),
|
||||
(RejoinChannelBuffers, RejoinChannelBuffersResponse),
|
||||
(RejoinRoom, RejoinRoomResponse),
|
||||
(ReloadBuffers, ReloadBuffersResponse),
|
||||
|
@ -632,6 +637,7 @@ entity_messages!(
|
|||
ApplyCodeActionKind,
|
||||
FormatBuffers,
|
||||
GetCodeActions,
|
||||
GetCodeLens,
|
||||
GetCompletions,
|
||||
GetDefinition,
|
||||
GetDeclaration,
|
||||
|
@ -659,6 +665,7 @@ entity_messages!(
|
|||
PerformRename,
|
||||
PrepareRename,
|
||||
RefreshInlayHints,
|
||||
RefreshCodeLens,
|
||||
ReloadBuffers,
|
||||
RemoveProjectCollaborator,
|
||||
RenameProjectEntry,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue