aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs8
-rw-r--r--crates/ra_assists/src/assists/add_derive.rs4
-rw-r--r--crates/ra_assists/src/assists/add_explicit_type.rs2
-rw-r--r--crates/ra_assists/src/assists/add_impl.rs2
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs46
-rw-r--r--crates/ra_assists/src/assists/apply_demorgan.rs102
-rw-r--r--crates/ra_assists/src/assists/auto_import.rs5
-rw-r--r--crates/ra_assists/src/assists/change_visibility.rs2
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs2
-rw-r--r--crates/ra_assists/src/assists/flip_binexpr.rs2
-rw-r--r--crates/ra_assists/src/assists/flip_comma.rs2
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs2
-rw-r--r--crates/ra_assists/src/assists/introduce_variable.rs2
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs2
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs14
-rw-r--r--crates/ra_assists/src/assists/move_guard.rs2
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs2
-rw-r--r--crates/ra_assists/src/assists/remove_dbg.rs2
-rw-r--r--crates/ra_assists/src/assists/replace_if_let_with_match.rs2
-rw-r--r--crates/ra_assists/src/assists/split_import.rs8
-rw-r--r--crates/ra_assists/src/ast_editor.rs315
-rw-r--r--crates/ra_assists/src/lib.rs3
-rw-r--r--crates/ra_assists/src/marks.rs2
23 files changed, 178 insertions, 355 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index c45262efa..189cad7d0 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -1,8 +1,10 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_db::FileRange; 4use ra_db::FileRange;
3use ra_fmt::{leading_indent, reindent}; 5use ra_fmt::{leading_indent, reindent};
4use ra_syntax::{ 6use ra_syntax::{
5 algo::{find_covering_element, find_node_at_offset}, 7 algo::{self, find_covering_element, find_node_at_offset},
6 AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, 8 AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit,
7 TokenAtOffset, 9 TokenAtOffset,
8}; 10};
@@ -177,6 +179,10 @@ impl AssistBuilder {
177 &mut self.edit 179 &mut self.edit
178 } 180 }
179 181
182 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
183 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
184 }
185
180 fn build(self) -> AssistAction { 186 fn build(self) -> AssistAction {
181 AssistAction { 187 AssistAction {
182 edit: self.edit.finish(), 188 edit: self.edit.finish(),
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs
index 9c88644df..77ecc33c9 100644
--- a/crates/ra_assists/src/assists/add_derive.rs
+++ b/crates/ra_assists/src/assists/add_derive.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner}, 5 ast::{self, AstNode, AttrsOwner},
@@ -13,7 +15,7 @@ pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
13 ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { 15 ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| {
14 let derive_attr = nominal 16 let derive_attr = nominal
15 .attrs() 17 .attrs()
16 .filter_map(|x| x.as_call()) 18 .filter_map(|x| x.as_simple_call())
17 .filter(|(name, _arg)| name == "derive") 19 .filter(|(name, _arg)| name == "derive")
18 .map(|(_name, arg)| arg) 20 .map(|(_name, arg)| arg)
19 .next(); 21 .next();
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs
index 78f0f7f28..8c83dc987 100644
--- a/crates/ra_assists/src/assists/add_explicit_type.rs
+++ b/crates/ra_assists/src/assists/add_explicit_type.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::{db::HirDatabase, HirDisplay, Ty}; 3use hir::{db::HirDatabase, HirDisplay, Ty};
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner}, 5 ast::{self, AstNode, LetStmt, NameOwner},
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs
index 4b61f4031..94801fbc9 100644
--- a/crates/ra_assists/src/assists/add_impl.rs
+++ b/crates/ra_assists/src/assists/add_impl.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use format_buf::format; 3use format_buf::format;
2use hir::db::HirDatabase; 4use hir::db::HirDatabase;
3use join_to_string::join; 5use join_to_string::join;
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
index 23da1e65f..565b96fb5 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -1,10 +1,12 @@
1//! FIXME: write short doc here
2
1use hir::{db::HirDatabase, HasSource}; 3use hir::{db::HirDatabase, HasSource};
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, make, AstNode, NameOwner}, 5 ast::{self, edit, make, AstNode, NameOwner},
4 SmolStr, 6 SmolStr,
5}; 7};
6 8
7use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId}; 9use crate::{Assist, AssistCtx, AssistId};
8 10
9#[derive(PartialEq)] 11#[derive(PartialEq)]
10enum AddMissingImplMembersMode { 12enum AddMissingImplMembersMode {
@@ -75,37 +77,32 @@ fn add_missing_impl_members_inner(
75 77
76 ctx.add_action(AssistId(assist_id), label, |edit| { 78 ctx.add_action(AssistId(assist_id), label, |edit| {
77 let n_existing_items = impl_item_list.impl_items().count(); 79 let n_existing_items = impl_item_list.impl_items().count();
78 let items = missing_items.into_iter().map(|it| match it { 80 let items = missing_items
79 ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()), 81 .into_iter()
80 _ => strip_docstring(it), 82 .map(|it| match it {
81 }); 83 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
82 let mut ast_editor = AstEditor::new(impl_item_list); 84 _ => it,
83 85 })
84 ast_editor.append_items(items); 86 .map(|it| edit::strip_attrs_and_docs(&it));
85 87 let new_impl_item_list = impl_item_list.append_items(items);
86 let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); 88 let cursor_position = {
87 let cursor_position = first_new_item.syntax().text_range().start(); 89 let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap();
88 ast_editor.into_text_edit(edit.text_edit_builder()); 90 first_new_item.syntax().text_range().start()
89 91 };
92
93 edit.replace_ast(impl_item_list, new_impl_item_list);
90 edit.set_cursor(cursor_position); 94 edit.set_cursor(cursor_position);
91 }); 95 });
92 96
93 ctx.build() 97 ctx.build()
94} 98}
95 99
96fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem {
97 let mut ast_editor = AstEditor::new(item);
98 ast_editor.strip_attrs_and_docs();
99 ast_editor.ast().to_owned()
100}
101
102fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 100fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
103 let mut ast_editor = AstEditor::new(fn_def.clone());
104 if fn_def.body().is_none() { 101 if fn_def.body().is_none() {
105 let body = make::block_from_expr(make::expr_unimplemented()); 102 fn_def.with_body(make::block_from_expr(make::expr_unimplemented()))
106 ast_editor.set_body(&body); 103 } else {
104 fn_def
107 } 105 }
108 ast_editor.ast().to_owned()
109} 106}
110 107
111/// Given an `ast::ImplBlock`, resolves the target trait (the one being 108/// Given an `ast::ImplBlock`, resolves the target trait (the one being
@@ -332,5 +329,4 @@ impl Foo for S {
332}", 329}",
333 ) 330 )
334 } 331 }
335
336} 332}
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs
new file mode 100644
index 000000000..5f2b0dd18
--- /dev/null
+++ b/crates/ra_assists/src/assists/apply_demorgan.rs
@@ -0,0 +1,102 @@
1//! This contains the functions associated with the demorgan assist.
2//! This assist transforms boolean expressions of the form `!a || !b` into
3//! `!(a && b)`.
4use hir::db::HirDatabase;
5use ra_syntax::ast::{self, AstNode};
6use ra_syntax::SyntaxNode;
7
8use crate::{Assist, AssistCtx, AssistId};
9
10/// Assist for applying demorgan's law
11///
12/// This transforms expressions of the form `!l || !r` into `!(l && r)`.
13/// This also works with `&&`. This assist can only be applied with the cursor
14/// on either `||` or `&&`, with both operands being a negation of some kind.
15/// This means something of the form `!x` or `x != y`.
16pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
17 let expr = ctx.node_at_offset::<ast::BinExpr>()?;
18 let op = expr.op_kind()?;
19 let op_range = expr.op_token()?.text_range();
20 let opposite_op = opposite_logic_op(op)?;
21 let cursor_in_range = ctx.frange.range.is_subrange(&op_range);
22 if !cursor_in_range {
23 return None;
24 }
25 let lhs = expr.lhs()?.syntax().clone();
26 let lhs_range = lhs.text_range();
27 let rhs = expr.rhs()?.syntax().clone();
28 let rhs_range = rhs.text_range();
29 let not_lhs = undo_negation(lhs)?;
30 let not_rhs = undo_negation(rhs)?;
31
32 ctx.add_action(AssistId("apply_demorgan"), "apply demorgan's law", |edit| {
33 edit.target(op_range);
34 edit.replace(op_range, opposite_op);
35 edit.replace(lhs_range, format!("!({}", not_lhs));
36 edit.replace(rhs_range, format!("{})", not_rhs));
37 });
38 ctx.build()
39}
40
41// Return the opposite text for a given logical operator, if it makes sense
42fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
43 match kind {
44 ast::BinOp::BooleanOr => Some("&&"),
45 ast::BinOp::BooleanAnd => Some("||"),
46 _ => None,
47 }
48}
49
50// This function tries to undo unary negation, or inequality
51fn undo_negation(node: SyntaxNode) -> Option<String> {
52 match ast::Expr::cast(node)? {
53 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
54 ast::BinOp::NegatedEqualityTest => {
55 let lhs = bin.lhs()?.syntax().text();
56 let rhs = bin.rhs()?.syntax().text();
57 Some(format!("{} == {}", lhs, rhs))
58 }
59 _ => None,
60 },
61 ast::Expr::PrefixExpr(pe) => match pe.op_kind()? {
62 ast::PrefixOp::Not => {
63 let child = pe.expr()?.syntax().text();
64 Some(String::from(child))
65 }
66 _ => None,
67 },
68 _ => None,
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 use crate::helpers::{check_assist, check_assist_not_applicable};
77
78 #[test]
79 fn demorgan_turns_and_into_or() {
80 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x ||<|> x) }")
81 }
82
83 #[test]
84 fn demorgan_turns_or_into_and() {
85 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x &&<|> x) }")
86 }
87
88 #[test]
89 fn demorgan_removes_inequality() {
90 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x &&<|> x) }")
91 }
92
93 #[test]
94 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
95 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
96 }
97
98 #[test]
99 fn demorgan_doesnt_apply_when_operands_arent_negated_already() {
100 check_assist_not_applicable(apply_demorgan, "fn f() { x ||<|> x }")
101 }
102}
diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/auto_import.rs
index 5aae98546..02c58e7c6 100644
--- a/crates/ra_assists/src/assists/auto_import.rs
+++ b/crates/ra_assists/src/assists/auto_import.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::{self, db::HirDatabase}; 3use hir::{self, db::HirDatabase};
2use ra_text_edit::TextEditBuilder; 4use ra_text_edit::TextEditBuilder;
3 5
@@ -448,7 +450,6 @@ fn make_assist_add_in_tree_list(
448 fmt_segments_raw(target, &mut buf); 450 fmt_segments_raw(target, &mut buf);
449 edit.insert(offset, buf); 451 edit.insert(offset, buf);
450 } else { 452 } else {
451
452 } 453 }
453} 454}
454 455
@@ -512,7 +513,7 @@ pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
512 hir::PathKind::Plain => {} 513 hir::PathKind::Plain => {}
513 hir::PathKind::Self_ => ps.push("self".into()), 514 hir::PathKind::Self_ => ps.push("self".into()),
514 hir::PathKind::Super => ps.push("super".into()), 515 hir::PathKind::Super => ps.push("super".into()),
515 hir::PathKind::Type(_) => return None, 516 hir::PathKind::Type(_) | hir::PathKind::DollarCrate(_) => return None,
516 } 517 }
517 for s in path.segments.iter() { 518 for s in path.segments.iter() {
518 ps.push(s.name.to_string().into()); 519 ps.push(s.name.to_string().into());
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs
index 60c74debc..df92c6b67 100644
--- a/crates/ra_assists/src/assists/change_visibility.rs
+++ b/crates/ra_assists/src/assists/change_visibility.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, NameOwner, VisibilityOwner}, 5 ast::{self, NameOwner, VisibilityOwner},
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs
index db82db89a..7335cce09 100644
--- a/crates/ra_assists/src/assists/fill_match_arms.rs
+++ b/crates/ra_assists/src/assists/fill_match_arms.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use std::iter; 3use std::iter;
2 4
3use hir::{db::HirDatabase, Adt, HasSource}; 5use hir::{db::HirDatabase, Adt, HasSource};
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs
index b55b36a8e..c51035282 100644
--- a/crates/ra_assists/src/assists/flip_binexpr.rs
+++ b/crates/ra_assists/src/assists/flip_binexpr.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 4use ra_syntax::ast::{AstNode, BinExpr, BinOp};
3 5
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs
index 5ee7561bc..e31cc5e7d 100644
--- a/crates/ra_assists/src/assists/flip_comma.rs
+++ b/crates/ra_assists/src/assists/flip_comma.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 4use ra_syntax::{algo::non_trivia_sibling, Direction, T};
3 5
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs
index eedb29199..9bd64decc 100644
--- a/crates/ra_assists/src/assists/inline_local_variable.rs
+++ b/crates/ra_assists/src/assists/inline_local_variable.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, AstNode, AstToken}, 5 ast::{self, AstNode, AstToken},
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs
index 470ffe120..43378c4b0 100644
--- a/crates/ra_assists/src/assists/introduce_variable.rs
+++ b/crates/ra_assists/src/assists/introduce_variable.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use format_buf::format; 3use format_buf::format;
2use hir::db::HirDatabase; 4use hir::db::HirDatabase;
3use ra_syntax::{ 5use ra_syntax::{
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs
index 3b6a99895..17baa98f9 100644
--- a/crates/ra_assists/src/assists/merge_match_arms.rs
+++ b/crates/ra_assists/src/assists/merge_match_arms.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; 3use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
2use hir::db::HirDatabase; 4use hir::db::HirDatabase;
3use ra_syntax::ast::{AstNode, MatchArm}; 5use ra_syntax::ast::{AstNode, MatchArm};
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs
index fd4bdc55c..f791d22b0 100644
--- a/crates/ra_assists/src/assists/move_bounds.rs
+++ b/crates/ra_assists/src/assists/move_bounds.rs
@@ -1,11 +1,13 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, make, AstNode, NameOwner, TypeBoundsOwner}, 5 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner},
4 SyntaxElement, 6 SyntaxElement,
5 SyntaxKind::*, 7 SyntaxKind::*,
6}; 8};
7 9
8use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId}; 10use crate::{Assist, AssistCtx, AssistId};
9 11
10pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 12pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; 13 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?;
@@ -39,14 +41,12 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>)
39 .type_params() 41 .type_params()
40 .filter(|it| it.type_bound_list().is_some()) 42 .filter(|it| it.type_bound_list().is_some())
41 .map(|type_param| { 43 .map(|type_param| {
42 let without_bounds = 44 let without_bounds = type_param.remove_bounds();
43 AstEditor::new(type_param.clone()).remove_bounds().ast().clone();
44 (type_param, without_bounds) 45 (type_param, without_bounds)
45 }); 46 });
46 47
47 let mut ast_editor = AstEditor::new(type_param_list.clone()); 48 let new_type_param_list = edit::replace_descendants(&type_param_list, new_params);
48 ast_editor.replace_descendants(new_params); 49 edit.replace_ast(type_param_list.clone(), new_type_param_list);
49 ast_editor.into_text_edit(edit.text_edit_builder());
50 50
51 let where_clause = { 51 let where_clause = {
52 let predicates = type_param_list.type_params().filter_map(build_predicate); 52 let predicates = type_param_list.type_params().filter_map(build_predicate);
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs
index 699221e33..51aea6334 100644
--- a/crates/ra_assists/src/assists/move_guard.rs
+++ b/crates/ra_assists/src/assists/move_guard.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{ 4use ra_syntax::{
3 ast, 5 ast,
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs
index 200aaa59a..2d2e31e51 100644
--- a/crates/ra_assists/src/assists/raw_string.rs
+++ b/crates/ra_assists/src/assists/raw_string.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; 4use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};
3use rustc_lexer; 5use rustc_lexer;
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs
index 870133fda..1a7e2b305 100644
--- a/crates/ra_assists/src/assists/remove_dbg.rs
+++ b/crates/ra_assists/src/assists/remove_dbg.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use crate::{Assist, AssistCtx, AssistId}; 3use crate::{Assist, AssistCtx, AssistId};
2use hir::db::HirDatabase; 4use hir::db::HirDatabase;
3use ra_syntax::{ 5use ra_syntax::{
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
index 401835c57..749ff338a 100644
--- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use format_buf::format; 3use format_buf::format;
2use hir::db::HirDatabase; 4use hir::db::HirDatabase;
3use ra_fmt::extract_trivial_expression; 5use ra_fmt::extract_trivial_expression;
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs
index 2c1edddb9..fe3e64af5 100644
--- a/crates/ra_assists/src/assists/split_import.rs
+++ b/crates/ra_assists/src/assists/split_import.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use std::iter::successors; 3use std::iter::successors;
2 4
3use hir::db::HirDatabase; 5use hir::db::HirDatabase;
@@ -49,13 +51,13 @@ mod tests {
49 fn split_import_works_with_trees() { 51 fn split_import_works_with_trees() {
50 check_assist( 52 check_assist(
51 split_import, 53 split_import,
52 "use algo:<|>:visitor::{Visitor, visit}", 54 "use crate:<|>:db::{RootDatabase, FileSymbol}",
53 "use algo::{<|>visitor::{Visitor, visit}}", 55 "use crate::{<|>db::{RootDatabase, FileSymbol}}",
54 ) 56 )
55 } 57 }
56 58
57 #[test] 59 #[test]
58 fn split_import_target() { 60 fn split_import_target() {
59 check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::"); 61 check_assist_target(split_import, "use crate::<|>db::{RootDatabase, FileSymbol}", "::");
60 } 62 }
61} 63}
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
deleted file mode 100644
index 2a685f26e..000000000
--- a/crates/ra_assists/src/ast_editor.rs
+++ /dev/null
@@ -1,315 +0,0 @@
1use std::{iter, ops::RangeInclusive};
2
3use arrayvec::ArrayVec;
4use rustc_hash::FxHashMap;
5
6use ra_fmt::leading_indent;
7use ra_syntax::{
8 algo,
9 ast::{self, TypeBoundsOwner},
10 AstNode, Direction, InsertPosition, SyntaxElement,
11 SyntaxKind::*,
12 T,
13};
14use ra_text_edit::TextEditBuilder;
15
16pub struct AstEditor<N: AstNode> {
17 original_ast: N,
18 ast: N,
19}
20
21impl<N: AstNode> AstEditor<N> {
22 pub fn new(node: N) -> AstEditor<N>
23 where
24 N: Clone,
25 {
26 AstEditor { original_ast: node.clone(), ast: node }
27 }
28
29 pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
30 for (from, to) in algo::diff(&self.original_ast.syntax(), self.ast().syntax()) {
31 builder.replace(from.text_range(), to.to_string())
32 }
33 }
34
35 pub fn ast(&self) -> &N {
36 &self.ast
37 }
38
39 pub fn replace_descendants<T: AstNode>(
40 &mut self,
41 replacement_map: impl Iterator<Item = (T, T)>,
42 ) -> &mut Self {
43 let map = replacement_map
44 .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
45 .collect::<FxHashMap<_, _>>();
46 let new_syntax = algo::replace_descendants(self.ast.syntax(), &map);
47 self.ast = N::cast(new_syntax).unwrap();
48 self
49 }
50
51 #[must_use]
52 fn insert_children(
53 &self,
54 position: InsertPosition<SyntaxElement>,
55 mut to_insert: impl Iterator<Item = SyntaxElement>,
56 ) -> N {
57 let new_syntax = algo::insert_children(self.ast().syntax(), position, &mut to_insert);
58 N::cast(new_syntax).unwrap()
59 }
60
61 #[must_use]
62 fn replace_children(
63 &self,
64 to_delete: RangeInclusive<SyntaxElement>,
65 mut to_insert: impl Iterator<Item = SyntaxElement>,
66 ) -> N {
67 let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert);
68 N::cast(new_syntax).unwrap()
69 }
70
71 fn do_make_multiline(&mut self) {
72 let l_curly =
73 match self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
74 Some(it) => it,
75 None => return,
76 };
77 let sibling = match l_curly.next_sibling_or_token() {
78 Some(it) => it,
79 None => return,
80 };
81 let existing_ws = match sibling.as_token() {
82 None => None,
83 Some(tok) if tok.kind() != WHITESPACE => None,
84 Some(ws) => {
85 if ws.text().contains('\n') {
86 return;
87 }
88 Some(ws.clone())
89 }
90 };
91
92 let indent = leading_indent(self.ast().syntax()).unwrap_or("".into());
93 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
94 let to_insert = iter::once(ws.ws().into());
95 self.ast = match existing_ws {
96 None => self.insert_children(InsertPosition::After(l_curly), to_insert),
97 Some(ws) => {
98 self.replace_children(RangeInclusive::new(ws.clone().into(), ws.into()), to_insert)
99 }
100 };
101 }
102}
103
104impl AstEditor<ast::RecordFieldList> {
105 pub fn append_field(&mut self, field: &ast::RecordField) {
106 self.insert_field(InsertPosition::Last, field)
107 }
108
109 pub fn insert_field(
110 &mut self,
111 position: InsertPosition<&'_ ast::RecordField>,
112 field: &ast::RecordField,
113 ) {
114 let is_multiline = self.ast().syntax().text().contains_char('\n');
115 let ws;
116 let space = if is_multiline {
117 ws = tokens::WsBuilder::new(&format!(
118 "\n{} ",
119 leading_indent(self.ast().syntax()).unwrap_or("".into())
120 ));
121 ws.ws()
122 } else {
123 tokens::single_space()
124 };
125
126 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
127 to_insert.push(space.into());
128 to_insert.push(field.syntax().clone().into());
129 to_insert.push(tokens::comma().into());
130
131 macro_rules! after_l_curly {
132 () => {{
133 let anchor = match self.l_curly() {
134 Some(it) => it,
135 None => return,
136 };
137 InsertPosition::After(anchor)
138 }};
139 }
140
141 macro_rules! after_field {
142 ($anchor:expr) => {
143 if let Some(comma) = $anchor
144 .syntax()
145 .siblings_with_tokens(Direction::Next)
146 .find(|it| it.kind() == T![,])
147 {
148 InsertPosition::After(comma)
149 } else {
150 to_insert.insert(0, tokens::comma().into());
151 InsertPosition::After($anchor.syntax().clone().into())
152 }
153 };
154 };
155
156 let position = match position {
157 InsertPosition::First => after_l_curly!(),
158 InsertPosition::Last => {
159 if !is_multiline {
160 // don't insert comma before curly
161 to_insert.pop();
162 }
163 match self.ast().fields().last() {
164 Some(it) => after_field!(it),
165 None => after_l_curly!(),
166 }
167 }
168 InsertPosition::Before(anchor) => {
169 InsertPosition::Before(anchor.syntax().clone().into())
170 }
171 InsertPosition::After(anchor) => after_field!(anchor),
172 };
173
174 self.ast = self.insert_children(position, to_insert.iter().cloned());
175 }
176
177 fn l_curly(&self) -> Option<SyntaxElement> {
178 self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
179 }
180}
181
182impl AstEditor<ast::ItemList> {
183 pub fn append_items(&mut self, items: impl Iterator<Item = ast::ImplItem>) {
184 if !self.ast().syntax().text().contains_char('\n') {
185 self.do_make_multiline();
186 }
187 items.for_each(|it| self.append_item(it));
188 }
189
190 pub fn append_item(&mut self, item: ast::ImplItem) {
191 let (indent, position) = match self.ast().impl_items().last() {
192 Some(it) => (
193 leading_indent(it.syntax()).unwrap_or_default().to_string(),
194 InsertPosition::After(it.syntax().clone().into()),
195 ),
196 None => match self.l_curly() {
197 Some(it) => (
198 " ".to_string() + &leading_indent(self.ast().syntax()).unwrap_or_default(),
199 InsertPosition::After(it),
200 ),
201 None => return,
202 },
203 };
204 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
205 let to_insert: ArrayVec<[SyntaxElement; 2]> =
206 [ws.ws().into(), item.syntax().clone().into()].into();
207 self.ast = self.insert_children(position, to_insert.into_iter());
208 }
209
210 fn l_curly(&self) -> Option<SyntaxElement> {
211 self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
212 }
213}
214
215impl AstEditor<ast::ImplItem> {
216 pub fn strip_attrs_and_docs(&mut self) {
217 while let Some(start) = self
218 .ast()
219 .syntax()
220 .children_with_tokens()
221 .find(|it| it.kind() == ATTR || it.kind() == COMMENT)
222 {
223 let end = match &start.next_sibling_or_token() {
224 Some(el) if el.kind() == WHITESPACE => el.clone(),
225 Some(_) | None => start.clone(),
226 };
227 self.ast = self.replace_children(RangeInclusive::new(start, end), iter::empty());
228 }
229 }
230}
231
232impl AstEditor<ast::FnDef> {
233 pub fn set_body(&mut self, body: &ast::Block) {
234 let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
235 let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.ast().body() {
236 old_body.syntax().clone().into()
237 } else if let Some(semi) = self.ast().semicolon_token() {
238 to_insert.push(tokens::single_space().into());
239 semi.into()
240 } else {
241 to_insert.push(tokens::single_space().into());
242 to_insert.push(body.syntax().clone().into());
243 self.ast = self.insert_children(InsertPosition::Last, to_insert.into_iter());
244 return;
245 };
246 to_insert.push(body.syntax().clone().into());
247 let replace_range = RangeInclusive::new(old_body_or_semi.clone(), old_body_or_semi);
248 self.ast = self.replace_children(replace_range, to_insert.into_iter())
249 }
250}
251
252impl AstEditor<ast::TypeParam> {
253 pub fn remove_bounds(&mut self) -> &mut Self {
254 let colon = match self.ast.colon_token() {
255 Some(it) => it,
256 None => return self,
257 };
258 let end = match self.ast.type_bound_list() {
259 Some(it) => it.syntax().clone().into(),
260 None => colon.clone().into(),
261 };
262 self.ast = self.replace_children(RangeInclusive::new(colon.into(), end), iter::empty());
263 self
264 }
265}
266
267mod tokens {
268 use once_cell::sync::Lazy;
269 use ra_syntax::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T};
270
271 static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| SourceFile::parse(",\n; ;"));
272
273 pub(crate) fn comma() -> SyntaxToken {
274 SOURCE_FILE
275 .tree()
276 .syntax()
277 .descendants_with_tokens()
278 .filter_map(|it| it.into_token())
279 .find(|it| it.kind() == T![,])
280 .unwrap()
281 }
282
283 pub(crate) fn single_space() -> SyntaxToken {
284 SOURCE_FILE
285 .tree()
286 .syntax()
287 .descendants_with_tokens()
288 .filter_map(|it| it.into_token())
289 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ")
290 .unwrap()
291 }
292
293 #[allow(unused)]
294 pub(crate) fn single_newline() -> SyntaxToken {
295 SOURCE_FILE
296 .tree()
297 .syntax()
298 .descendants_with_tokens()
299 .filter_map(|it| it.into_token())
300 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n")
301 .unwrap()
302 }
303
304 pub(crate) struct WsBuilder(SourceFile);
305
306 impl WsBuilder {
307 pub(crate) fn new(text: &str) -> WsBuilder {
308 WsBuilder(SourceFile::parse(text).ok().unwrap())
309 }
310 pub(crate) fn ws(&self) -> SyntaxToken {
311 self.0.syntax().first_child_or_token().unwrap().into_token().unwrap()
312 }
313 }
314
315}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 3ca3320f7..d2376c475 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -7,7 +7,6 @@
7 7
8mod assist_ctx; 8mod assist_ctx;
9mod marks; 9mod marks;
10pub mod ast_editor;
11 10
12use hir::db::HirDatabase; 11use hir::db::HirDatabase;
13use itertools::Itertools; 12use itertools::Itertools;
@@ -93,6 +92,7 @@ mod assists {
93 mod add_derive; 92 mod add_derive;
94 mod add_explicit_type; 93 mod add_explicit_type;
95 mod add_impl; 94 mod add_impl;
95 mod apply_demorgan;
96 mod flip_comma; 96 mod flip_comma;
97 mod flip_binexpr; 97 mod flip_binexpr;
98 mod change_visibility; 98 mod change_visibility;
@@ -114,6 +114,7 @@ mod assists {
114 add_derive::add_derive, 114 add_derive::add_derive,
115 add_explicit_type::add_explicit_type, 115 add_explicit_type::add_explicit_type,
116 add_impl::add_impl, 116 add_impl::add_impl,
117 apply_demorgan::apply_demorgan,
117 change_visibility::change_visibility, 118 change_visibility::change_visibility,
118 fill_match_arms::fill_match_arms, 119 fill_match_arms::fill_match_arms,
119 merge_match_arms::merge_match_arms, 120 merge_match_arms::merge_match_arms,
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
index a29f9f658..c20e4db9e 100644
--- a/crates/ra_assists/src/marks.rs
+++ b/crates/ra_assists/src/marks.rs
@@ -1,3 +1,5 @@
1//! See test_utils/src/marks.rs
2
1test_utils::marks!( 3test_utils::marks!(
2 introduce_var_in_comment_is_not_applicable 4 introduce_var_in_comment_is_not_applicable
3 test_introduce_var_expr_stmt 5 test_introduce_var_expr_stmt