mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
ra_assists: assist "providers" can produce multiple assists
This commit is contained in:
parent
af62fde57f
commit
5c9c0d3ae2
14 changed files with 146 additions and 64 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -921,6 +921,7 @@ version = "0.1.0"
|
||||||
name = "ra_assists"
|
name = "ra_assists"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ra_db 0.1.0",
|
"ra_db 0.1.0",
|
||||||
"ra_fmt 0.1.0",
|
"ra_fmt 0.1.0",
|
||||||
|
|
|
@ -6,6 +6,7 @@ authors = ["rust-analyzer developers"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
join_to_string = "0.1.3"
|
join_to_string = "0.1.3"
|
||||||
|
itertools = "0.8.0"
|
||||||
|
|
||||||
ra_syntax = { path = "../ra_syntax" }
|
ra_syntax = { path = "../ra_syntax" }
|
||||||
ra_text_edit = { path = "../ra_text_edit" }
|
ra_text_edit = { path = "../ra_text_edit" }
|
||||||
|
|
|
@ -7,10 +7,10 @@ use ra_syntax::{
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
|
let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
|
||||||
let node_start = derive_insertion_offset(nominal)?;
|
let node_start = derive_insertion_offset(nominal)?;
|
||||||
ctx.build("add `#[derive]`", |edit| {
|
ctx.add_action("add `#[derive]`", |edit| {
|
||||||
let derive_attr = nominal
|
let derive_attr = nominal
|
||||||
.attrs()
|
.attrs()
|
||||||
.filter_map(|x| x.as_call())
|
.filter_map(|x| x.as_call())
|
||||||
|
@ -26,7 +26,9 @@ pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
};
|
};
|
||||||
edit.target(nominal.syntax().range());
|
edit.target(nominal.syntax().range());
|
||||||
edit.set_cursor(offset)
|
edit.set_cursor(offset)
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert `derive` after doc comments.
|
// Insert `derive` after doc comments.
|
||||||
|
|
|
@ -7,10 +7,10 @@ use ra_syntax::{
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
|
let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
|
||||||
let name = nominal.name()?;
|
let name = nominal.name()?;
|
||||||
ctx.build("add impl", |edit| {
|
ctx.add_action("add impl", |edit| {
|
||||||
edit.target(nominal.syntax().range());
|
edit.target(nominal.syntax().range());
|
||||||
let type_params = nominal.type_param_list();
|
let type_params = nominal.type_param_list();
|
||||||
let start_offset = nominal.syntax().range().end();
|
let start_offset = nominal.syntax().range().end();
|
||||||
|
@ -32,7 +32,9 @@ pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
edit.set_cursor(start_offset + TextUnit::of_str(&buf));
|
edit.set_cursor(start_offset + TextUnit::of_str(&buf));
|
||||||
buf.push_str("\n}");
|
buf.push_str("\n}");
|
||||||
edit.insert(start_offset, buf);
|
edit.insert(start_offset, buf);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -9,9 +9,10 @@ use ra_fmt::{leading_indent, reindent};
|
||||||
|
|
||||||
use crate::{AssistLabel, AssistAction};
|
use crate::{AssistLabel, AssistAction};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum Assist {
|
pub(crate) enum Assist {
|
||||||
Unresolved(AssistLabel),
|
Unresolved(Vec<AssistLabel>),
|
||||||
Resolved(AssistLabel, AssistAction),
|
Resolved(Vec<(AssistLabel, AssistAction)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `AssistCtx` allows to apply an assist or check if it could be applied.
|
/// `AssistCtx` allows to apply an assist or check if it could be applied.
|
||||||
|
@ -50,6 +51,7 @@ pub(crate) struct AssistCtx<'a, DB> {
|
||||||
pub(crate) frange: FileRange,
|
pub(crate) frange: FileRange,
|
||||||
source_file: &'a SourceFile,
|
source_file: &'a SourceFile,
|
||||||
should_compute_edit: bool,
|
should_compute_edit: bool,
|
||||||
|
assist: Assist,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, DB> Clone for AssistCtx<'a, DB> {
|
impl<'a, DB> Clone for AssistCtx<'a, DB> {
|
||||||
|
@ -59,6 +61,7 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> {
|
||||||
frange: self.frange,
|
frange: self.frange,
|
||||||
source_file: self.source_file,
|
source_file: self.source_file,
|
||||||
should_compute_edit: self.should_compute_edit,
|
should_compute_edit: self.should_compute_edit,
|
||||||
|
assist: self.assist.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,25 +72,35 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
|
||||||
F: FnOnce(AssistCtx<DB>) -> T,
|
F: FnOnce(AssistCtx<DB>) -> T,
|
||||||
{
|
{
|
||||||
let source_file = &db.parse(frange.file_id);
|
let source_file = &db.parse(frange.file_id);
|
||||||
let ctx = AssistCtx { db, frange, source_file, should_compute_edit };
|
let assist =
|
||||||
|
if should_compute_edit { Assist::Resolved(vec![]) } else { Assist::Unresolved(vec![]) };
|
||||||
|
|
||||||
|
let ctx = AssistCtx { db, frange, source_file, should_compute_edit, assist };
|
||||||
f(ctx)
|
f(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build(
|
pub(crate) fn add_action(
|
||||||
self,
|
&mut self,
|
||||||
label: impl Into<String>,
|
label: impl Into<String>,
|
||||||
f: impl FnOnce(&mut AssistBuilder),
|
f: impl FnOnce(&mut AssistBuilder),
|
||||||
) -> Option<Assist> {
|
) -> &mut Self {
|
||||||
let label = AssistLabel { label: label.into() };
|
let label = AssistLabel { label: label.into() };
|
||||||
if !self.should_compute_edit {
|
match &mut self.assist {
|
||||||
return Some(Assist::Unresolved(label));
|
Assist::Unresolved(labels) => labels.push(label),
|
||||||
}
|
Assist::Resolved(labels_actions) => {
|
||||||
let action = {
|
let action = {
|
||||||
let mut edit = AssistBuilder::default();
|
let mut edit = AssistBuilder::default();
|
||||||
f(&mut edit);
|
f(&mut edit);
|
||||||
edit.build()
|
edit.build()
|
||||||
};
|
};
|
||||||
Some(Assist::Resolved(label, action))
|
labels_actions.push((label, action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build(self) -> Option<Assist> {
|
||||||
|
Some(self.assist)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
|
pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
|
||||||
|
|
|
@ -480,7 +480,7 @@ fn make_assist_add_nested_import(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let node = ctx.covering_node();
|
let node = ctx.covering_node();
|
||||||
let current_file = node.ancestors().find_map(ast::SourceFile::cast)?;
|
let current_file = node.ancestors().find_map(ast::SourceFile::cast)?;
|
||||||
|
|
||||||
|
@ -495,7 +495,7 @@ pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.build(format!("import {} in the current file", fmt_segments(&segments)), |edit| {
|
ctx.add_action(format!("import {} in the current file", fmt_segments(&segments)), |edit| {
|
||||||
let action = best_action_for_target(current_file.syntax(), path, &segments);
|
let action = best_action_for_target(current_file.syntax(), path, &segments);
|
||||||
make_assist(&action, segments.as_slice(), edit);
|
make_assist(&action, segments.as_slice(), edit);
|
||||||
if let Some(last_segment) = path.segment() {
|
if let Some(last_segment) = path.segment() {
|
||||||
|
@ -506,7 +506,9 @@ pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
last_segment.syntax().range().start(),
|
last_segment.syntax().range().start(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
|
||||||
add_vis(ctx)
|
add_vis(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() {
|
let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() {
|
||||||
FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
|
FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
@ -41,11 +41,13 @@ fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
(vis_offset(field.syntax()), ident.range())
|
(vis_offset(field.syntax()), ident.range())
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.build("make pub(crate)", |edit| {
|
ctx.add_action("make pub(crate)", |edit| {
|
||||||
edit.target(target);
|
edit.target(target);
|
||||||
edit.insert(offset, "pub(crate) ");
|
edit.insert(offset, "pub(crate) ");
|
||||||
edit.set_cursor(offset);
|
edit.set_cursor(offset);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vis_offset(node: &SyntaxNode) -> TextUnit {
|
fn vis_offset(node: &SyntaxNode) -> TextUnit {
|
||||||
|
@ -59,20 +61,24 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
|
||||||
.unwrap_or(node.range().start())
|
.unwrap_or(node.range().start())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
|
fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
|
||||||
if vis.syntax().text() == "pub" {
|
if vis.syntax().text() == "pub" {
|
||||||
return ctx.build("change to pub(crate)", |edit| {
|
ctx.add_action("change to pub(crate)", |edit| {
|
||||||
edit.target(vis.syntax().range());
|
edit.target(vis.syntax().range());
|
||||||
edit.replace(vis.syntax().range(), "pub(crate)");
|
edit.replace(vis.syntax().range(), "pub(crate)");
|
||||||
edit.set_cursor(vis.syntax().range().start());
|
edit.set_cursor(vis.syntax().range().start())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return ctx.build();
|
||||||
}
|
}
|
||||||
if vis.syntax().text() == "pub(crate)" {
|
if vis.syntax().text() == "pub(crate)" {
|
||||||
return ctx.build("change to pub", |edit| {
|
ctx.add_action("change to pub", |edit| {
|
||||||
edit.target(vis.syntax().range());
|
edit.target(vis.syntax().range());
|
||||||
edit.replace(vis.syntax().range(), "pub");
|
edit.replace(vis.syntax().range(), "pub");
|
||||||
edit.set_cursor(vis.syntax().range().start());
|
edit.set_cursor(vis.syntax().range().start());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return ctx.build();
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use ra_syntax::ast::{self, AstNode};
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
|
let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
|
||||||
|
|
||||||
// We already have some match arms, so we don't provide any assists.
|
// We already have some match arms, so we don't provide any assists.
|
||||||
|
@ -33,7 +33,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
|
||||||
let enum_name = enum_def.name(ctx.db)?;
|
let enum_name = enum_def.name(ctx.db)?;
|
||||||
let db = ctx.db;
|
let db = ctx.db;
|
||||||
|
|
||||||
ctx.build("fill match arms", |edit| {
|
ctx.add_action("fill match arms", |edit| {
|
||||||
let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
|
let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
|
||||||
let variants = enum_def.variants(db);
|
let variants = enum_def.variants(db);
|
||||||
for variant in variants {
|
for variant in variants {
|
||||||
|
@ -68,7 +68,9 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
|
||||||
edit.target(match_expr.syntax().range());
|
edit.target(match_expr.syntax().range());
|
||||||
edit.set_cursor(expr.syntax().range().start());
|
edit.set_cursor(expr.syntax().range().start());
|
||||||
edit.replace_node_and_indent(match_expr.syntax(), buf);
|
edit.replace_node_and_indent(match_expr.syntax(), buf);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -6,15 +6,17 @@ use ra_syntax::{
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist, non_trivia_sibling};
|
use crate::{AssistCtx, Assist, non_trivia_sibling};
|
||||||
|
|
||||||
pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?;
|
let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?;
|
||||||
let prev = non_trivia_sibling(comma, Direction::Prev)?;
|
let prev = non_trivia_sibling(comma, Direction::Prev)?;
|
||||||
let next = non_trivia_sibling(comma, Direction::Next)?;
|
let next = non_trivia_sibling(comma, Direction::Next)?;
|
||||||
ctx.build("flip comma", |edit| {
|
ctx.add_action("flip comma", |edit| {
|
||||||
edit.target(comma.range());
|
edit.target(comma.range());
|
||||||
edit.replace(prev.range(), next.text());
|
edit.replace(prev.range(), next.text());
|
||||||
edit.replace(next.range(), prev.text());
|
edit.replace(next.range(), prev.text());
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use ra_syntax::{
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let node = ctx.covering_node();
|
let node = ctx.covering_node();
|
||||||
if !valid_covering_node(node) {
|
if !valid_covering_node(node) {
|
||||||
return None;
|
return None;
|
||||||
|
@ -19,7 +19,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Ass
|
||||||
if indent.kind() != WHITESPACE {
|
if indent.kind() != WHITESPACE {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
ctx.build("introduce variable", move |edit| {
|
ctx.add_action("introduce variable", move |edit| {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
let cursor_offset = if wrap_in_block {
|
let cursor_offset = if wrap_in_block {
|
||||||
|
@ -68,7 +68,9 @@ pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Ass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
|
edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_covering_node(node: &SyntaxNode) -> bool {
|
fn valid_covering_node(node: &SyntaxNode) -> bool {
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
mod assist_ctx;
|
mod assist_ctx;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use ra_text_edit::TextEdit;
|
use ra_text_edit::TextEdit;
|
||||||
use ra_syntax::{TextRange, TextUnit, SyntaxNode, Direction};
|
use ra_syntax::{TextRange, TextUnit, SyntaxNode, Direction};
|
||||||
use ra_db::FileRange;
|
use ra_db::FileRange;
|
||||||
|
@ -14,12 +16,13 @@ use hir::db::HirDatabase;
|
||||||
|
|
||||||
pub(crate) use crate::assist_ctx::{AssistCtx, Assist};
|
pub(crate) use crate::assist_ctx::{AssistCtx, Assist};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AssistLabel {
|
pub struct AssistLabel {
|
||||||
/// Short description of the assist, as shown in the UI.
|
/// Short description of the assist, as shown in the UI.
|
||||||
pub label: String,
|
pub label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct AssistAction {
|
pub struct AssistAction {
|
||||||
pub edit: TextEdit,
|
pub edit: TextEdit,
|
||||||
pub cursor_position: Option<TextUnit>,
|
pub cursor_position: Option<TextUnit>,
|
||||||
|
@ -39,10 +42,10 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|f| f(ctx.clone()))
|
.filter_map(|f| f(ctx.clone()))
|
||||||
.map(|a| match a {
|
.map(|a| match a {
|
||||||
Assist::Unresolved(label) => label,
|
Assist::Unresolved(labels) => labels,
|
||||||
Assist::Resolved(..) => unreachable!(),
|
Assist::Resolved(..) => unreachable!(),
|
||||||
})
|
})
|
||||||
.collect()
|
.concat()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +64,10 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|f| f(ctx.clone()))
|
.filter_map(|f| f(ctx.clone()))
|
||||||
.map(|a| match a {
|
.map(|a| match a {
|
||||||
Assist::Resolved(label, action) => (label, action),
|
Assist::Resolved(labels_actions) => labels_actions,
|
||||||
Assist::Unresolved(..) => unreachable!(),
|
Assist::Unresolved(..) => unreachable!(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<(AssistLabel, AssistAction)>>();
|
.concat();
|
||||||
a.sort_by(|a, b| match (a.1.target, b.1.target) {
|
a.sort_by(|a, b| match (a.1.target, b.1.target) {
|
||||||
(Some(a), Some(b)) => a.len().cmp(&b.len()),
|
(Some(a), Some(b)) => a.len().cmp(&b.len()),
|
||||||
(Some(_), None) => Ordering::Less,
|
(Some(_), None) => Ordering::Less,
|
||||||
|
@ -118,6 +121,39 @@ mod helpers {
|
||||||
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
before: &str,
|
before: &str,
|
||||||
after: &str,
|
after: &str,
|
||||||
|
) {
|
||||||
|
check_assist_nth_action(assist, before, after, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_assist_range(
|
||||||
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
|
before: &str,
|
||||||
|
after: &str,
|
||||||
|
) {
|
||||||
|
check_assist_range_nth_action(assist, before, after, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_assist_target(
|
||||||
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
|
before: &str,
|
||||||
|
target: &str,
|
||||||
|
) {
|
||||||
|
check_assist_target_nth_action(assist, before, target, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_assist_range_target(
|
||||||
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
|
before: &str,
|
||||||
|
target: &str,
|
||||||
|
) {
|
||||||
|
check_assist_range_target_nth_action(assist, before, target, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_assist_nth_action(
|
||||||
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
|
before: &str,
|
||||||
|
after: &str,
|
||||||
|
index: usize,
|
||||||
) {
|
) {
|
||||||
let (before_cursor_pos, before) = extract_offset(before);
|
let (before_cursor_pos, before) = extract_offset(before);
|
||||||
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
||||||
|
@ -125,11 +161,12 @@ mod helpers {
|
||||||
FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
|
FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
|
||||||
let assist =
|
let assist =
|
||||||
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
||||||
let action = match assist {
|
let labels_actions = match assist {
|
||||||
Assist::Unresolved(_) => unreachable!(),
|
Assist::Unresolved(_) => unreachable!(),
|
||||||
Assist::Resolved(_, it) => it,
|
Assist::Resolved(labels_actions) => labels_actions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (_, action) = labels_actions.get(index).expect("expect assist action at index");
|
||||||
let actual = action.edit.apply(&before);
|
let actual = action.edit.apply(&before);
|
||||||
let actual_cursor_pos = match action.cursor_position {
|
let actual_cursor_pos = match action.cursor_position {
|
||||||
None => action
|
None => action
|
||||||
|
@ -142,21 +179,23 @@ mod helpers {
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_assist_range(
|
pub(crate) fn check_assist_range_nth_action(
|
||||||
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
before: &str,
|
before: &str,
|
||||||
after: &str,
|
after: &str,
|
||||||
|
index: usize,
|
||||||
) {
|
) {
|
||||||
let (range, before) = extract_range(before);
|
let (range, before) = extract_range(before);
|
||||||
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
||||||
let frange = FileRange { file_id, range };
|
let frange = FileRange { file_id, range };
|
||||||
let assist =
|
let assist =
|
||||||
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
||||||
let action = match assist {
|
let labels_actions = match assist {
|
||||||
Assist::Unresolved(_) => unreachable!(),
|
Assist::Unresolved(_) => unreachable!(),
|
||||||
Assist::Resolved(_, it) => it,
|
Assist::Resolved(labels_actions) => labels_actions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (_, action) = labels_actions.get(index).expect("expect assist action at index");
|
||||||
let mut actual = action.edit.apply(&before);
|
let mut actual = action.edit.apply(&before);
|
||||||
if let Some(pos) = action.cursor_position {
|
if let Some(pos) = action.cursor_position {
|
||||||
actual = add_cursor(&actual, pos);
|
actual = add_cursor(&actual, pos);
|
||||||
|
@ -164,10 +203,11 @@ mod helpers {
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_assist_target(
|
pub(crate) fn check_assist_target_nth_action(
|
||||||
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
before: &str,
|
before: &str,
|
||||||
target: &str,
|
target: &str,
|
||||||
|
index: usize,
|
||||||
) {
|
) {
|
||||||
let (before_cursor_pos, before) = extract_offset(before);
|
let (before_cursor_pos, before) = extract_offset(before);
|
||||||
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
||||||
|
@ -175,30 +215,33 @@ mod helpers {
|
||||||
FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
|
FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
|
||||||
let assist =
|
let assist =
|
||||||
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
||||||
let action = match assist {
|
let labels_actions = match assist {
|
||||||
Assist::Unresolved(_) => unreachable!(),
|
Assist::Unresolved(_) => unreachable!(),
|
||||||
Assist::Resolved(_, it) => it,
|
Assist::Resolved(labels_actions) => labels_actions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (_, action) = labels_actions.get(index).expect("expect assist action at index");
|
||||||
let range = action.target.expect("expected target on action");
|
let range = action.target.expect("expected target on action");
|
||||||
assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
|
assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_assist_range_target(
|
pub(crate) fn check_assist_range_target_nth_action(
|
||||||
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
|
||||||
before: &str,
|
before: &str,
|
||||||
target: &str,
|
target: &str,
|
||||||
|
index: usize,
|
||||||
) {
|
) {
|
||||||
let (range, before) = extract_range(before);
|
let (range, before) = extract_range(before);
|
||||||
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
|
||||||
let frange = FileRange { file_id, range };
|
let frange = FileRange { file_id, range };
|
||||||
let assist =
|
let assist =
|
||||||
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
|
||||||
let action = match assist {
|
let labels_actions = match assist {
|
||||||
Assist::Unresolved(_) => unreachable!(),
|
Assist::Unresolved(_) => unreachable!(),
|
||||||
Assist::Resolved(_, it) => it,
|
Assist::Resolved(labels_actions) => labels_actions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (_, action) = labels_actions.get(index).expect("expect assist action at index");
|
||||||
let range = action.target.expect("expected target on action");
|
let range = action.target.expect("expected target on action");
|
||||||
assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
|
assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use ra_syntax::{
|
||||||
};
|
};
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let macro_call = ctx.node_at_offset::<ast::MacroCall>()?;
|
let macro_call = ctx.node_at_offset::<ast::MacroCall>()?;
|
||||||
|
|
||||||
if !is_valid_macrocall(macro_call, "dbg")? {
|
if !is_valid_macrocall(macro_call, "dbg")? {
|
||||||
|
@ -46,11 +46,13 @@ pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
macro_args.text().slice(start..end).to_string()
|
macro_args.text().slice(start..end).to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.build("remove dbg!()", |edit| {
|
ctx.add_action("remove dbg!()", |edit| {
|
||||||
edit.target(macro_call.syntax().range());
|
edit.target(macro_call.syntax().range());
|
||||||
edit.replace(macro_range, macro_content);
|
edit.replace(macro_range, macro_content);
|
||||||
edit.set_cursor(cursor_pos);
|
edit.set_cursor(cursor_pos);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies that the given macro_call actually matches the given name
|
/// Verifies that the given macro_call actually matches the given name
|
||||||
|
|
|
@ -4,7 +4,7 @@ use hir::db::HirDatabase;
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let if_expr: &ast::IfExpr = ctx.node_at_offset()?;
|
let if_expr: &ast::IfExpr = ctx.node_at_offset()?;
|
||||||
let cond = if_expr.condition()?;
|
let cond = if_expr.condition()?;
|
||||||
let pat = cond.pat()?;
|
let pat = cond.pat()?;
|
||||||
|
@ -15,12 +15,14 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Opt
|
||||||
ast::ElseBranchFlavor::IfExpr(_) => return None,
|
ast::ElseBranchFlavor::IfExpr(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.build("replace with match", |edit| {
|
ctx.add_action("replace with match", |edit| {
|
||||||
let match_expr = build_match_expr(expr, pat, then_block, else_block);
|
let match_expr = build_match_expr(expr, pat, then_block, else_block);
|
||||||
edit.target(if_expr.syntax().range());
|
edit.target(if_expr.syntax().range());
|
||||||
edit.replace_node_and_indent(if_expr.syntax(), match_expr);
|
edit.replace_node_and_indent(if_expr.syntax(), match_expr);
|
||||||
edit.set_cursor(if_expr.syntax().range().start())
|
edit.set_cursor(if_expr.syntax().range().start())
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_match_expr(
|
fn build_match_expr(
|
||||||
|
|
|
@ -7,7 +7,7 @@ use ra_syntax::{
|
||||||
|
|
||||||
use crate::{AssistCtx, Assist};
|
use crate::{AssistCtx, Assist};
|
||||||
|
|
||||||
pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
let colon_colon = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COLONCOLON)?;
|
let colon_colon = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COLONCOLON)?;
|
||||||
let path = colon_colon.parent().and_then(ast::Path::cast)?;
|
let path = colon_colon.parent().and_then(ast::Path::cast)?;
|
||||||
let top_path = generate(Some(path), |it| it.parent_path()).last()?;
|
let top_path = generate(Some(path), |it| it.parent_path()).last()?;
|
||||||
|
@ -23,12 +23,14 @@ pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
|
||||||
None => top_path.syntax().range().end(),
|
None => top_path.syntax().range().end(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.build("split import", |edit| {
|
ctx.add_action("split import", |edit| {
|
||||||
edit.target(colon_colon.range());
|
edit.target(colon_colon.range());
|
||||||
edit.insert(l_curly, "{");
|
edit.insert(l_curly, "{");
|
||||||
edit.insert(r_curly, "}");
|
edit.insert(r_curly, "}");
|
||||||
edit.set_cursor(l_curly + TextUnit::of_str("{"));
|
edit.set_cursor(l_curly + TextUnit::of_str("{"));
|
||||||
})
|
});
|
||||||
|
|
||||||
|
ctx.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in a new issue