Use textDocument/codeLens data in the actions menu when applicable #2 (#26848)

Re-applies what's been reverted in
https://github.com/zed-industries/zed/pull/26832 with an action-related
fix in
64b5d37d32

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:
Kirill Bulatov 2025-03-15 22:09:32 +02:00 committed by GitHub
parent ef91e7afae
commit 8a31dcaeb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 618 additions and 17 deletions

View file

@ -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>,