Skip to content

Commit 57cd936

Browse files
committed
Preliminary implementation of lazy CodeAssits
1 parent 61e8f39 commit 57cd936

13 files changed

+215
-85
lines changed

crates/ra_ide/src/lib.rs

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub use crate::{
7777
};
7878

7979
pub use hir::Documentation;
80-
pub use ra_assists::{AssistConfig, AssistId};
80+
pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
8181
pub use ra_db::{
8282
Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
8383
};
@@ -142,14 +142,6 @@ pub struct AnalysisHost {
142142
db: RootDatabase,
143143
}
144144

145-
#[derive(Debug)]
146-
pub struct Assist {
147-
pub id: AssistId,
148-
pub label: String,
149-
pub group_label: Option<String>,
150-
pub source_change: SourceChange,
151-
}
152-
153145
impl AnalysisHost {
154146
pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
155147
AnalysisHost { db: RootDatabase::new(lru_capacity) }
@@ -470,20 +462,23 @@ impl Analysis {
470462
self.with_db(|db| completion::completions(db, config, position).map(Into::into))
471463
}
472464

473-
/// Computes assists (aka code actions aka intentions) for the given
465+
/// Computes resolved assists with source changes for the given position.
466+
pub fn resolved_assists(
467+
&self,
468+
config: &AssistConfig,
469+
frange: FileRange,
470+
) -> Cancelable<Vec<ResolvedAssist>> {
471+
self.with_db(|db| ra_assists::Assist::resolved(db, config, frange))
472+
}
473+
474+
/// Computes unresolved assists (aka code actions aka intentions) for the given
474475
/// position.
475-
pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> {
476-
self.with_db(|db| {
477-
ra_assists::Assist::resolved(db, config, frange)
478-
.into_iter()
479-
.map(|assist| Assist {
480-
id: assist.assist.id,
481-
label: assist.assist.label,
482-
group_label: assist.assist.group.map(|it| it.0),
483-
source_change: assist.source_change,
484-
})
485-
.collect()
486-
})
476+
pub fn unresolved_assists(
477+
&self,
478+
config: &AssistConfig,
479+
frange: FileRange,
480+
) -> Cancelable<Vec<Assist>> {
481+
self.with_db(|db| Assist::unresolved(db, config, frange))
487482
}
488483

489484
/// Computes the set of diagnostics for the given file.

crates/rust-analyzer/src/config.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub struct ClientCapsConfig {
103103
pub code_action_literals: bool,
104104
pub work_done_progress: bool,
105105
pub code_action_group: bool,
106+
pub resolve_code_action: bool,
106107
}
107108

108109
impl Default for Config {
@@ -299,7 +300,11 @@ impl Config {
299300

300301
let code_action_group =
301302
experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true);
302-
self.client_caps.code_action_group = code_action_group
303+
self.client_caps.code_action_group = code_action_group;
304+
305+
let resolve_code_action =
306+
experimental.get("resolveCodeAction").and_then(|it| it.as_bool()) == Some(true);
307+
self.client_caps.resolve_code_action = resolve_code_action;
303308
}
304309
}
305310
}

crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ expression: diag
6565
fixes: [
6666
CodeAction {
6767
title: "return the expression directly",
68+
id: None,
6869
group: None,
6970
kind: Some(
7071
"quickfix",

crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ expression: diag
5050
fixes: [
5151
CodeAction {
5252
title: "consider prefixing with an underscore",
53+
id: None,
5354
group: None,
5455
kind: Some(
5556
"quickfix",

crates/rust-analyzer/src/diagnostics/to_proto.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ fn map_rust_child_diagnostic(
145145
} else {
146146
MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction {
147147
title: rd.message.clone(),
148+
id: None,
148149
group: None,
149150
kind: Some("quickfix".to_string()),
150151
edit: Some(lsp_ext::SnippetWorkspaceEdit {

crates/rust-analyzer/src/lsp_ext.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ pub struct JoinLinesParams {
9898
pub ranges: Vec<Range>,
9999
}
100100

101+
pub enum ResolveCodeActionRequest {}
102+
103+
impl Request for ResolveCodeActionRequest {
104+
type Params = ResolveCodeActionParams;
105+
type Result = Option<SnippetWorkspaceEdit>;
106+
const METHOD: &'static str = "experimental/resolveCodeAction";
107+
}
108+
109+
/// Params for the ResolveCodeActionRequest
110+
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
111+
#[serde(rename_all = "camelCase")]
112+
pub struct ResolveCodeActionParams {
113+
pub code_action_params: lsp_types::CodeActionParams,
114+
pub id: String,
115+
pub label: String,
116+
}
117+
101118
pub enum OnEnter {}
102119

103120
impl Request for OnEnter {
@@ -197,6 +214,8 @@ impl Request for CodeActionRequest {
197214
pub struct CodeAction {
198215
pub title: String,
199216
#[serde(skip_serializing_if = "Option::is_none")]
217+
pub id: Option<String>,
218+
#[serde(skip_serializing_if = "Option::is_none")]
200219
pub group: Option<String>,
201220
#[serde(skip_serializing_if = "Option::is_none")]
202221
pub kind: Option<String>,

crates/rust-analyzer/src/main_loop.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ fn on_request(
517517
.on::<lsp_ext::Runnables>(handlers::handle_runnables)?
518518
.on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
519519
.on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
520+
.on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
520521
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
521522
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
522523
.on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?

crates/rust-analyzer/src/main_loop/handlers.rs

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -693,54 +693,103 @@ pub fn handle_formatting(
693693
}]))
694694
}
695695

696-
pub fn handle_code_action(
697-
world: WorldSnapshot,
698-
params: lsp_types::CodeActionParams,
699-
) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
700-
let _p = profile("handle_code_action");
701-
// We intentionally don't support command-based actions, as those either
702-
// requires custom client-code anyway, or requires server-initiated edits.
703-
// Server initiated edits break causality, so we avoid those as well.
704-
if !world.config.client_caps.code_action_literals {
705-
return Ok(None);
706-
}
707-
696+
fn handle_fixes(
697+
world: &WorldSnapshot,
698+
params: &lsp_types::CodeActionParams,
699+
res: &mut Vec<lsp_ext::CodeAction>,
700+
) -> Result<()> {
708701
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
709702
let line_index = world.analysis().file_line_index(file_id)?;
710703
let range = from_proto::text_range(&line_index, params.range);
711-
let frange = FileRange { file_id, range };
712704

713705
let diagnostics = world.analysis().diagnostics(file_id)?;
714-
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
715706

716707
let fixes_from_diagnostics = diagnostics
717708
.into_iter()
718709
.filter_map(|d| Some((d.range, d.fix?)))
719710
.filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
720711
.map(|(_range, fix)| fix);
721-
722712
for fix in fixes_from_diagnostics {
723713
let title = fix.label;
724714
let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?;
725-
let action =
726-
lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None };
715+
let action = lsp_ext::CodeAction {
716+
title,
717+
id: None,
718+
group: None,
719+
kind: None,
720+
edit: Some(edit),
721+
command: None,
722+
};
727723
res.push(action);
728724
}
729-
730725
for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
731726
let fix_range = from_proto::text_range(&line_index, fix.range);
732727
if fix_range.intersect(range).is_none() {
733728
continue;
734729
}
735730
res.push(fix.action.clone());
736731
}
732+
Ok(())
733+
}
734+
735+
pub fn handle_code_action(
736+
world: WorldSnapshot,
737+
params: lsp_types::CodeActionParams,
738+
) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
739+
let _p = profile("handle_code_action");
740+
// We intentionally don't support command-based actions, as those either
741+
// requires custom client-code anyway, or requires server-initiated edits.
742+
// Server initiated edits break causality, so we avoid those as well.
743+
if !world.config.client_caps.code_action_literals {
744+
return Ok(None);
745+
}
746+
747+
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
748+
let line_index = world.analysis().file_line_index(file_id)?;
749+
let range = from_proto::text_range(&line_index, params.range);
750+
let frange = FileRange { file_id, range };
751+
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
752+
753+
handle_fixes(&world, &params, &mut res)?;
737754

738-
for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() {
739-
res.push(to_proto::code_action(&world, assist)?.into());
755+
if world.config.client_caps.resolve_code_action {
756+
for assist in world.analysis().unresolved_assists(&world.config.assist, frange)?.into_iter()
757+
{
758+
res.push(to_proto::unresolved_code_action(&world, assist)?.into());
759+
}
760+
} else {
761+
for assist in world.analysis().resolved_assists(&world.config.assist, frange)?.into_iter() {
762+
res.push(to_proto::resolved_code_action(&world, assist)?.into());
763+
}
740764
}
765+
741766
Ok(Some(res))
742767
}
743768

769+
pub fn handle_resolve_code_action(
770+
world: WorldSnapshot,
771+
params: lsp_ext::ResolveCodeActionParams,
772+
) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
773+
if !world.config.client_caps.resolve_code_action {
774+
return Ok(None);
775+
}
776+
777+
let _p = profile("handle_resolve_code_action");
778+
let file_id = from_proto::file_id(&world, &params.code_action_params.text_document.uri)?;
779+
let line_index = world.analysis().file_line_index(file_id)?;
780+
let range = from_proto::text_range(&line_index, params.code_action_params.range);
781+
let frange = FileRange { file_id, range };
782+
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
783+
784+
for assist in world.analysis().resolved_assists(&world.config.assist, frange)?.into_iter() {
785+
res.push(to_proto::resolved_code_action(&world, assist)?.into());
786+
}
787+
Ok(res
788+
.into_iter()
789+
.find(|action| action.id.clone().unwrap() == params.id && action.title == params.label)
790+
.and_then(|action| action.edit))
791+
}
792+
744793
pub fn handle_code_lens(
745794
world: WorldSnapshot,
746795
params: lsp_types::CodeLensParams,

crates/rust-analyzer/src/to_proto.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use ra_db::{FileId, FileRange};
33
use ra_ide::{
44
Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
55
FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel,
6-
InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Severity,
7-
SourceChange, SourceFileEdit, TextEdit,
6+
InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess,
7+
ResolvedAssist, Severity, SourceChange, SourceFileEdit, TextEdit,
88
};
99
use ra_syntax::{SyntaxKind, TextRange, TextSize};
1010
use ra_vfs::LineEndings;
@@ -617,10 +617,41 @@ fn main() <fold>{
617617
}
618618
}
619619

620-
pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> {
620+
pub(crate) fn unresolved_code_action(
621+
world: &WorldSnapshot,
622+
assist: Assist,
623+
) -> Result<lsp_ext::CodeAction> {
621624
let res = lsp_ext::CodeAction {
622625
title: assist.label,
623-
group: if world.config.client_caps.code_action_group { assist.group_label } else { None },
626+
id: Some(assist.id.0.to_owned()),
627+
group: assist.group.and_then(|it| {
628+
if world.config.client_caps.code_action_group {
629+
None
630+
} else {
631+
Some(it.0)
632+
}
633+
}),
634+
kind: Some(String::new()),
635+
edit: None,
636+
command: None,
637+
};
638+
Ok(res)
639+
}
640+
641+
pub(crate) fn resolved_code_action(
642+
world: &WorldSnapshot,
643+
assist: ResolvedAssist,
644+
) -> Result<lsp_ext::CodeAction> {
645+
let res = lsp_ext::CodeAction {
646+
title: assist.assist.label,
647+
id: Some(assist.assist.id.0.to_owned()),
648+
group: assist.assist.group.and_then(|it| {
649+
if world.config.client_caps.code_action_group {
650+
None
651+
} else {
652+
Some(it.0)
653+
}
654+
}),
624655
kind: Some(String::new()),
625656
edit: Some(snippet_workspace_edit(world, assist.source_change)?),
626657
command: None,

0 commit comments

Comments
 (0)