diff options
Diffstat (limited to 'crates/ra_assists/src')
25 files changed, 1521 insertions, 492 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index e270c5d60..1908bdec9 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -1,11 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. |
2 | 2 | ||
3 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
4 | use ra_db::FileRange; | 4 | use ra_db::FileRange; |
5 | use ra_fmt::{leading_indent, reindent}; | 5 | use ra_fmt::{leading_indent, reindent}; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{self, find_covering_element, find_node_at_offset}, | 7 | algo::{self, find_covering_element, find_node_at_offset}, |
8 | AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, | 8 | AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit, |
9 | TokenAtOffset, | 9 | TokenAtOffset, |
10 | }; | 10 | }; |
11 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
@@ -14,8 +14,8 @@ use crate::{AssistAction, AssistId, AssistLabel}; | |||
14 | 14 | ||
15 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
16 | pub(crate) enum Assist { | 16 | pub(crate) enum Assist { |
17 | Unresolved(Vec<AssistLabel>), | 17 | Unresolved { label: AssistLabel }, |
18 | Resolved(Vec<(AssistLabel, AssistAction)>), | 18 | Resolved { label: AssistLabel, action: AssistAction }, |
19 | } | 19 | } |
20 | 20 | ||
21 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | 21 | /// `AssistCtx` allows to apply an assist or check if it could be applied. |
@@ -54,7 +54,6 @@ pub(crate) struct AssistCtx<'a, DB> { | |||
54 | pub(crate) frange: FileRange, | 54 | pub(crate) frange: FileRange, |
55 | source_file: SourceFile, | 55 | source_file: SourceFile, |
56 | should_compute_edit: bool, | 56 | should_compute_edit: bool, |
57 | assist: Assist, | ||
58 | } | 57 | } |
59 | 58 | ||
60 | impl<'a, DB> Clone for AssistCtx<'a, DB> { | 59 | impl<'a, DB> Clone for AssistCtx<'a, DB> { |
@@ -64,7 +63,6 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> { | |||
64 | frange: self.frange, | 63 | frange: self.frange, |
65 | source_file: self.source_file.clone(), | 64 | source_file: self.source_file.clone(), |
66 | should_compute_edit: self.should_compute_edit, | 65 | should_compute_edit: self.should_compute_edit, |
67 | assist: self.assist.clone(), | ||
68 | } | 66 | } |
69 | } | 67 | } |
70 | } | 68 | } |
@@ -75,43 +73,41 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
75 | F: FnOnce(AssistCtx<DB>) -> T, | 73 | F: FnOnce(AssistCtx<DB>) -> T, |
76 | { | 74 | { |
77 | let parse = db.parse(frange.file_id); | 75 | let parse = db.parse(frange.file_id); |
78 | let assist = | ||
79 | if should_compute_edit { Assist::Resolved(vec![]) } else { Assist::Unresolved(vec![]) }; | ||
80 | 76 | ||
81 | let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit, assist }; | 77 | let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit }; |
82 | f(ctx) | 78 | f(ctx) |
83 | } | 79 | } |
84 | 80 | ||
85 | pub(crate) fn add_action( | 81 | pub(crate) fn add_assist( |
86 | &mut self, | 82 | self, |
87 | id: AssistId, | 83 | id: AssistId, |
88 | label: impl Into<String>, | 84 | label: impl Into<String>, |
89 | f: impl FnOnce(&mut AssistBuilder), | 85 | f: impl FnOnce(&mut AssistBuilder), |
90 | ) -> &mut Self { | 86 | ) -> Option<Assist> { |
91 | let label = AssistLabel { label: label.into(), id }; | 87 | let label = AssistLabel { label: label.into(), id }; |
92 | match &mut self.assist { | 88 | let assist = if self.should_compute_edit { |
93 | Assist::Unresolved(labels) => labels.push(label), | 89 | let action = { |
94 | Assist::Resolved(labels_actions) => { | 90 | let mut edit = AssistBuilder::default(); |
95 | let action = { | 91 | f(&mut edit); |
96 | let mut edit = AssistBuilder::default(); | 92 | edit.build() |
97 | f(&mut edit); | 93 | }; |
98 | edit.build() | 94 | Assist::Resolved { label, action } |
99 | }; | 95 | } else { |
100 | labels_actions.push((label, action)); | 96 | Assist::Unresolved { label } |
101 | } | 97 | }; |
102 | } | ||
103 | self | ||
104 | } | ||
105 | 98 | ||
106 | pub(crate) fn build(self) -> Option<Assist> { | 99 | Some(assist) |
107 | Some(self.assist) | ||
108 | } | 100 | } |
109 | 101 | ||
110 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | 102 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { |
111 | self.source_file.syntax().token_at_offset(self.frange.range.start()) | 103 | self.source_file.syntax().token_at_offset(self.frange.range.start()) |
112 | } | 104 | } |
113 | 105 | ||
114 | pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<N> { | 106 | pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { |
107 | self.token_at_offset().find(|it| it.kind() == kind) | ||
108 | } | ||
109 | |||
110 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { | ||
115 | find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) | 111 | find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) |
116 | } | 112 | } |
117 | pub(crate) fn covering_element(&self) -> SyntaxElement { | 113 | pub(crate) fn covering_element(&self) -> SyntaxElement { |
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs index 77ecc33c9..764b17bd8 100644 --- a/crates/ra_assists/src/assists/add_derive.rs +++ b/crates/ra_assists/src/assists/add_derive.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, AstNode, AttrsOwner}, | 3 | ast::{self, AstNode, AttrsOwner}, |
@@ -9,10 +7,28 @@ use ra_syntax::{ | |||
9 | 7 | ||
10 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
11 | 9 | ||
12 | pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // Assist: add_derive |
13 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | 11 | // |
12 | // Adds a new `#[derive()]` clause to a struct or enum. | ||
13 | // | ||
14 | // ``` | ||
15 | // struct Point { | ||
16 | // x: u32, | ||
17 | // y: u32,<|> | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // #[derive()] | ||
23 | // struct Point { | ||
24 | // x: u32, | ||
25 | // y: u32, | ||
26 | // } | ||
27 | // ``` | ||
28 | pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | ||
14 | let node_start = derive_insertion_offset(&nominal)?; | 30 | let node_start = derive_insertion_offset(&nominal)?; |
15 | ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { | 31 | ctx.add_assist(AssistId("add_derive"), "add `#[derive]`", |edit| { |
16 | let derive_attr = nominal | 32 | let derive_attr = nominal |
17 | .attrs() | 33 | .attrs() |
18 | .filter_map(|x| x.as_simple_call()) | 34 | .filter_map(|x| x.as_simple_call()) |
@@ -28,9 +44,7 @@ pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
28 | }; | 44 | }; |
29 | edit.target(nominal.syntax().text_range()); | 45 | edit.target(nominal.syntax().text_range()); |
30 | edit.set_cursor(offset) | 46 | edit.set_cursor(offset) |
31 | }); | 47 | }) |
32 | |||
33 | ctx.build() | ||
34 | } | 48 | } |
35 | 49 | ||
36 | // Insert `derive` after doc comments. | 50 | // Insert `derive` after doc comments. |
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs index 8c83dc987..ddda1a0f2 100644 --- a/crates/ra_assists/src/assists/add_explicit_type.rs +++ b/crates/ra_assists/src/assists/add_explicit_type.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{db::HirDatabase, HirDisplay, Ty}; | 1 | use hir::{db::HirDatabase, HirDisplay, Ty}; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, AstNode, LetStmt, NameOwner}, | 3 | ast::{self, AstNode, LetStmt, NameOwner}, |
@@ -8,9 +6,23 @@ use ra_syntax::{ | |||
8 | 6 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{Assist, AssistCtx, AssistId}; |
10 | 8 | ||
11 | /// Add explicit type assist. | 9 | // Assist: add_explicit_type |
12 | pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // |
13 | let stmt = ctx.node_at_offset::<LetStmt>()?; | 11 | // Specify type for a let binding. |
12 | // | ||
13 | // ``` | ||
14 | // fn main() { | ||
15 | // let x<|> = 92; | ||
16 | // } | ||
17 | // ``` | ||
18 | // -> | ||
19 | // ``` | ||
20 | // fn main() { | ||
21 | // let x: i32 = 92; | ||
22 | // } | ||
23 | // ``` | ||
24 | pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; | ||
14 | let expr = stmt.initializer()?; | 26 | let expr = stmt.initializer()?; |
15 | let pat = stmt.pat()?; | 27 | let pat = stmt.pat()?; |
16 | // Must be a binding | 28 | // Must be a binding |
@@ -35,11 +47,10 @@ pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option< | |||
35 | return None; | 47 | return None; |
36 | } | 48 | } |
37 | 49 | ||
38 | ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { | 50 | ctx.add_assist(AssistId("add_explicit_type"), "add explicit type", |edit| { |
39 | edit.target(pat_range); | 51 | edit.target(pat_range); |
40 | edit.insert(name_range.end(), format!(": {}", ty.display(db))); | 52 | edit.insert(name_range.end(), format!(": {}", ty.display(db))); |
41 | }); | 53 | }) |
42 | ctx.build() | ||
43 | } | 54 | } |
44 | 55 | ||
45 | /// Returns true if any type parameter is unknown | 56 | /// Returns true if any type parameter is unknown |
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs index 94801fbc9..7da0cfd0d 100644 --- a/crates/ra_assists/src/assists/add_impl.rs +++ b/crates/ra_assists/src/assists/add_impl.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use format_buf::format; | 1 | use format_buf::format; |
4 | use hir::db::HirDatabase; | 2 | use hir::db::HirDatabase; |
5 | use join_to_string::join; | 3 | use join_to_string::join; |
@@ -10,10 +8,29 @@ use ra_syntax::{ | |||
10 | 8 | ||
11 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{Assist, AssistCtx, AssistId}; |
12 | 10 | ||
13 | pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 11 | // Assist: add_impl |
14 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | 12 | // |
13 | // Adds a new inherent impl for a type. | ||
14 | // | ||
15 | // ``` | ||
16 | // struct Ctx<T: Clone> { | ||
17 | // data: T,<|> | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // struct Ctx<T: Clone> { | ||
23 | // data: T, | ||
24 | // } | ||
25 | // | ||
26 | // impl<T: Clone> Ctx<T> { | ||
27 | // | ||
28 | // } | ||
29 | // ``` | ||
30 | pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
31 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | ||
15 | let name = nominal.name()?; | 32 | let name = nominal.name()?; |
16 | ctx.add_action(AssistId("add_impl"), "add impl", |edit| { | 33 | ctx.add_assist(AssistId("add_impl"), "add impl", |edit| { |
17 | edit.target(nominal.syntax().text_range()); | 34 | edit.target(nominal.syntax().text_range()); |
18 | let type_params = nominal.type_param_list(); | 35 | let type_params = nominal.type_param_list(); |
19 | let start_offset = nominal.syntax().text_range().end(); | 36 | let start_offset = nominal.syntax().text_range().end(); |
@@ -37,9 +54,7 @@ pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
37 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); | 54 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); |
38 | buf.push_str("\n}"); | 55 | buf.push_str("\n}"); |
39 | edit.insert(start_offset, buf); | 56 | edit.insert(start_offset, buf); |
40 | }); | 57 | }) |
41 | |||
42 | ctx.build() | ||
43 | } | 58 | } |
44 | 59 | ||
45 | #[cfg(test)] | 60 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/add_import.rs index 02c58e7c6..363ade016 100644 --- a/crates/ra_assists/src/assists/auto_import.rs +++ b/crates/ra_assists/src/assists/add_import.rs | |||
@@ -1,18 +1,81 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{self, db::HirDatabase}; | 1 | use hir::{self, db::HirDatabase}; |
4 | use ra_text_edit::TextEditBuilder; | ||
5 | |||
6 | use crate::{ | ||
7 | assist_ctx::{Assist, AssistCtx}, | ||
8 | AssistId, | ||
9 | }; | ||
10 | use ra_syntax::{ | 2 | use ra_syntax::{ |
11 | ast::{self, NameOwner}, | 3 | ast::{self, NameOwner}, |
12 | AstNode, Direction, SmolStr, | 4 | AstNode, Direction, SmolStr, |
13 | SyntaxKind::{PATH, PATH_SEGMENT}, | 5 | SyntaxKind::{PATH, PATH_SEGMENT}, |
14 | SyntaxNode, TextRange, T, | 6 | SyntaxNode, TextRange, T, |
15 | }; | 7 | }; |
8 | use ra_text_edit::TextEditBuilder; | ||
9 | |||
10 | use crate::{ | ||
11 | assist_ctx::{Assist, AssistCtx}, | ||
12 | AssistId, | ||
13 | }; | ||
14 | |||
15 | /// This function produces sequence of text edits into edit | ||
16 | /// to import the target path in the most appropriate scope given | ||
17 | /// the cursor position | ||
18 | pub fn auto_import_text_edit( | ||
19 | // Ideally the position of the cursor, used to | ||
20 | position: &SyntaxNode, | ||
21 | // The statement to use as anchor (last resort) | ||
22 | anchor: &SyntaxNode, | ||
23 | // The path to import as a sequence of strings | ||
24 | target: &[SmolStr], | ||
25 | edit: &mut TextEditBuilder, | ||
26 | ) { | ||
27 | let container = position.ancestors().find_map(|n| { | ||
28 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
29 | return module.item_list().map(|it| it.syntax().clone()); | ||
30 | } | ||
31 | ast::SourceFile::cast(n).map(|it| it.syntax().clone()) | ||
32 | }); | ||
33 | |||
34 | if let Some(container) = container { | ||
35 | let action = best_action_for_target(container, anchor.clone(), target); | ||
36 | make_assist(&action, target, edit); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | // Assist: add_import | ||
41 | // | ||
42 | // Adds a use statement for a given fully-qualified path. | ||
43 | // | ||
44 | // ``` | ||
45 | // fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
46 | // ``` | ||
47 | // -> | ||
48 | // ``` | ||
49 | // use std::collections::HashMap; | ||
50 | // | ||
51 | // fn process(map: HashMap<String, String>) {} | ||
52 | // ``` | ||
53 | pub(crate) fn add_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
54 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
55 | // We don't want to mess with use statements | ||
56 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | ||
57 | return None; | ||
58 | } | ||
59 | |||
60 | let hir_path = hir::Path::from_ast(path.clone())?; | ||
61 | let segments = collect_hir_path_segments(&hir_path)?; | ||
62 | if segments.len() < 2 { | ||
63 | return None; | ||
64 | } | ||
65 | |||
66 | let module = path.syntax().ancestors().find_map(ast::Module::cast); | ||
67 | let position = match module.and_then(|it| it.item_list()) { | ||
68 | Some(item_list) => item_list.syntax().clone(), | ||
69 | None => { | ||
70 | let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
71 | current_file.syntax().clone() | ||
72 | } | ||
73 | }; | ||
74 | |||
75 | ctx.add_assist(AssistId("add_import"), format!("import {}", fmt_segments(&segments)), |edit| { | ||
76 | apply_auto_import(&position, &path, &segments, edit.text_edit_builder()); | ||
77 | }) | ||
78 | } | ||
16 | 79 | ||
17 | fn collect_path_segments_raw( | 80 | fn collect_path_segments_raw( |
18 | segments: &mut Vec<ast::PathSegment>, | 81 | segments: &mut Vec<ast::PathSegment>, |
@@ -61,9 +124,9 @@ fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { | |||
61 | } | 124 | } |
62 | } | 125 | } |
63 | 126 | ||
64 | // Returns the numeber of common segments. | 127 | /// Returns the number of common segments. |
65 | fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { | 128 | fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { |
66 | left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count() | 129 | left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count() |
67 | } | 130 | } |
68 | 131 | ||
69 | fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { | 132 | fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { |
@@ -84,7 +147,7 @@ fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { | |||
84 | a == b.text() | 147 | a == b.text() |
85 | } | 148 | } |
86 | 149 | ||
87 | #[derive(Clone)] | 150 | #[derive(Clone, Debug)] |
88 | enum ImportAction { | 151 | enum ImportAction { |
89 | Nothing, | 152 | Nothing, |
90 | // Add a brand new use statement. | 153 | // Add a brand new use statement. |
@@ -154,9 +217,17 @@ impl ImportAction { | |||
154 | ( | 217 | ( |
155 | ImportAction::AddNestedImport { common_segments: n, .. }, | 218 | ImportAction::AddNestedImport { common_segments: n, .. }, |
156 | ImportAction::AddInTreeList { common_segments: m, .. }, | 219 | ImportAction::AddInTreeList { common_segments: m, .. }, |
157 | ) => n > m, | 220 | ) |
158 | ( | 221 | | ( |
222 | ImportAction::AddInTreeList { common_segments: n, .. }, | ||
223 | ImportAction::AddNestedImport { common_segments: m, .. }, | ||
224 | ) | ||
225 | | ( | ||
159 | ImportAction::AddInTreeList { common_segments: n, .. }, | 226 | ImportAction::AddInTreeList { common_segments: n, .. }, |
227 | ImportAction::AddInTreeList { common_segments: m, .. }, | ||
228 | ) | ||
229 | | ( | ||
230 | ImportAction::AddNestedImport { common_segments: n, .. }, | ||
160 | ImportAction::AddNestedImport { common_segments: m, .. }, | 231 | ImportAction::AddNestedImport { common_segments: m, .. }, |
161 | ) => n > m, | 232 | ) => n > m, |
162 | (ImportAction::AddInTreeList { .. }, _) => true, | 233 | (ImportAction::AddInTreeList { .. }, _) => true, |
@@ -226,7 +297,7 @@ fn walk_use_tree_for_best_action( | |||
226 | common if common == left.len() && left.len() == right.len() => { | 297 | common if common == left.len() && left.len() == right.len() => { |
227 | // e.g: target is std::fmt and we can have | 298 | // e.g: target is std::fmt and we can have |
228 | // 1- use std::fmt; | 299 | // 1- use std::fmt; |
229 | // 2- use std::fmt:{ ... } | 300 | // 2- use std::fmt::{ ... } |
230 | if let Some(list) = tree_list { | 301 | if let Some(list) = tree_list { |
231 | // In case 2 we need to add self to the nested list | 302 | // In case 2 we need to add self to the nested list |
232 | // unless it's already there | 303 | // unless it's already there |
@@ -474,7 +545,7 @@ fn make_assist_add_nested_import( | |||
474 | if add_colon_colon { | 545 | if add_colon_colon { |
475 | buf.push_str("::"); | 546 | buf.push_str("::"); |
476 | } | 547 | } |
477 | buf.push_str("{ "); | 548 | buf.push_str("{"); |
478 | if add_self { | 549 | if add_self { |
479 | buf.push_str("self, "); | 550 | buf.push_str("self, "); |
480 | } | 551 | } |
@@ -505,7 +576,7 @@ fn apply_auto_import( | |||
505 | } | 576 | } |
506 | } | 577 | } |
507 | 578 | ||
508 | pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | 579 | fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { |
509 | let mut ps = Vec::<SmolStr>::with_capacity(10); | 580 | let mut ps = Vec::<SmolStr>::with_capacity(10); |
510 | match path.kind { | 581 | match path.kind { |
511 | hir::PathKind::Abs => ps.push("".into()), | 582 | hir::PathKind::Abs => ps.push("".into()), |
@@ -521,87 +592,16 @@ pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | |||
521 | Some(ps) | 592 | Some(ps) |
522 | } | 593 | } |
523 | 594 | ||
524 | // This function produces sequence of text edits into edit | ||
525 | // to import the target path in the most appropriate scope given | ||
526 | // the cursor position | ||
527 | pub fn auto_import_text_edit( | ||
528 | // Ideally the position of the cursor, used to | ||
529 | position: &SyntaxNode, | ||
530 | // The statement to use as anchor (last resort) | ||
531 | anchor: &SyntaxNode, | ||
532 | // The path to import as a sequence of strings | ||
533 | target: &[SmolStr], | ||
534 | edit: &mut TextEditBuilder, | ||
535 | ) { | ||
536 | let container = position.ancestors().find_map(|n| { | ||
537 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
538 | return module.item_list().map(|it| it.syntax().clone()); | ||
539 | } | ||
540 | ast::SourceFile::cast(n).map(|it| it.syntax().clone()) | ||
541 | }); | ||
542 | |||
543 | if let Some(container) = container { | ||
544 | let action = best_action_for_target(container, anchor.clone(), target); | ||
545 | make_assist(&action, target, edit); | ||
546 | } | ||
547 | } | ||
548 | |||
549 | pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
550 | let path: ast::Path = ctx.node_at_offset()?; | ||
551 | // We don't want to mess with use statements | ||
552 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | ||
553 | return None; | ||
554 | } | ||
555 | |||
556 | let hir_path = hir::Path::from_ast(path.clone())?; | ||
557 | let segments = collect_hir_path_segments(&hir_path)?; | ||
558 | if segments.len() < 2 { | ||
559 | return None; | ||
560 | } | ||
561 | |||
562 | if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { | ||
563 | if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { | ||
564 | ctx.add_action( | ||
565 | AssistId("auto_import"), | ||
566 | format!("import {} in mod {}", fmt_segments(&segments), name.text()), | ||
567 | |edit| { | ||
568 | apply_auto_import( | ||
569 | item_list.syntax(), | ||
570 | &path, | ||
571 | &segments, | ||
572 | edit.text_edit_builder(), | ||
573 | ); | ||
574 | }, | ||
575 | ); | ||
576 | } | ||
577 | } else { | ||
578 | let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
579 | ctx.add_action( | ||
580 | AssistId("auto_import"), | ||
581 | format!("import {} in the current file", fmt_segments(&segments)), | ||
582 | |edit| { | ||
583 | apply_auto_import( | ||
584 | current_file.syntax(), | ||
585 | &path, | ||
586 | &segments, | ||
587 | edit.text_edit_builder(), | ||
588 | ); | ||
589 | }, | ||
590 | ); | ||
591 | } | ||
592 | |||
593 | ctx.build() | ||
594 | } | ||
595 | |||
596 | #[cfg(test)] | 595 | #[cfg(test)] |
597 | mod tests { | 596 | mod tests { |
598 | use super::*; | ||
599 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 597 | use crate::helpers::{check_assist, check_assist_not_applicable}; |
600 | 598 | ||
599 | use super::*; | ||
600 | |||
601 | #[test] | 601 | #[test] |
602 | fn test_auto_import_add_use_no_anchor() { | 602 | fn test_auto_import_add_use_no_anchor() { |
603 | check_assist( | 603 | check_assist( |
604 | auto_import, | 604 | add_import, |
605 | " | 605 | " |
606 | std::fmt::Debug<|> | 606 | std::fmt::Debug<|> |
607 | ", | 607 | ", |
@@ -615,7 +615,7 @@ Debug<|> | |||
615 | #[test] | 615 | #[test] |
616 | fn test_auto_import_add_use_no_anchor_with_item_below() { | 616 | fn test_auto_import_add_use_no_anchor_with_item_below() { |
617 | check_assist( | 617 | check_assist( |
618 | auto_import, | 618 | add_import, |
619 | " | 619 | " |
620 | std::fmt::Debug<|> | 620 | std::fmt::Debug<|> |
621 | 621 | ||
@@ -636,7 +636,7 @@ fn main() { | |||
636 | #[test] | 636 | #[test] |
637 | fn test_auto_import_add_use_no_anchor_with_item_above() { | 637 | fn test_auto_import_add_use_no_anchor_with_item_above() { |
638 | check_assist( | 638 | check_assist( |
639 | auto_import, | 639 | add_import, |
640 | " | 640 | " |
641 | fn main() { | 641 | fn main() { |
642 | } | 642 | } |
@@ -657,7 +657,7 @@ Debug<|> | |||
657 | #[test] | 657 | #[test] |
658 | fn test_auto_import_add_use_no_anchor_2seg() { | 658 | fn test_auto_import_add_use_no_anchor_2seg() { |
659 | check_assist( | 659 | check_assist( |
660 | auto_import, | 660 | add_import, |
661 | " | 661 | " |
662 | std::fmt<|>::Debug | 662 | std::fmt<|>::Debug |
663 | ", | 663 | ", |
@@ -672,7 +672,7 @@ fmt<|>::Debug | |||
672 | #[test] | 672 | #[test] |
673 | fn test_auto_import_add_use() { | 673 | fn test_auto_import_add_use() { |
674 | check_assist( | 674 | check_assist( |
675 | auto_import, | 675 | add_import, |
676 | " | 676 | " |
677 | use stdx; | 677 | use stdx; |
678 | 678 | ||
@@ -692,7 +692,7 @@ impl Debug<|> for Foo { | |||
692 | #[test] | 692 | #[test] |
693 | fn test_auto_import_file_use_other_anchor() { | 693 | fn test_auto_import_file_use_other_anchor() { |
694 | check_assist( | 694 | check_assist( |
695 | auto_import, | 695 | add_import, |
696 | " | 696 | " |
697 | impl std::fmt::Debug<|> for Foo { | 697 | impl std::fmt::Debug<|> for Foo { |
698 | } | 698 | } |
@@ -709,7 +709,7 @@ impl Debug<|> for Foo { | |||
709 | #[test] | 709 | #[test] |
710 | fn test_auto_import_add_use_other_anchor_indent() { | 710 | fn test_auto_import_add_use_other_anchor_indent() { |
711 | check_assist( | 711 | check_assist( |
712 | auto_import, | 712 | add_import, |
713 | " | 713 | " |
714 | impl std::fmt::Debug<|> for Foo { | 714 | impl std::fmt::Debug<|> for Foo { |
715 | } | 715 | } |
@@ -726,7 +726,7 @@ impl Debug<|> for Foo { | |||
726 | #[test] | 726 | #[test] |
727 | fn test_auto_import_split_different() { | 727 | fn test_auto_import_split_different() { |
728 | check_assist( | 728 | check_assist( |
729 | auto_import, | 729 | add_import, |
730 | " | 730 | " |
731 | use std::fmt; | 731 | use std::fmt; |
732 | 732 | ||
@@ -734,7 +734,7 @@ impl std::io<|> for Foo { | |||
734 | } | 734 | } |
735 | ", | 735 | ", |
736 | " | 736 | " |
737 | use std::{ io, fmt}; | 737 | use std::{io, fmt}; |
738 | 738 | ||
739 | impl io<|> for Foo { | 739 | impl io<|> for Foo { |
740 | } | 740 | } |
@@ -745,7 +745,7 @@ impl io<|> for Foo { | |||
745 | #[test] | 745 | #[test] |
746 | fn test_auto_import_split_self_for_use() { | 746 | fn test_auto_import_split_self_for_use() { |
747 | check_assist( | 747 | check_assist( |
748 | auto_import, | 748 | add_import, |
749 | " | 749 | " |
750 | use std::fmt; | 750 | use std::fmt; |
751 | 751 | ||
@@ -753,7 +753,7 @@ impl std::fmt::Debug<|> for Foo { | |||
753 | } | 753 | } |
754 | ", | 754 | ", |
755 | " | 755 | " |
756 | use std::fmt::{ self, Debug, }; | 756 | use std::fmt::{self, Debug, }; |
757 | 757 | ||
758 | impl Debug<|> for Foo { | 758 | impl Debug<|> for Foo { |
759 | } | 759 | } |
@@ -764,7 +764,7 @@ impl Debug<|> for Foo { | |||
764 | #[test] | 764 | #[test] |
765 | fn test_auto_import_split_self_for_target() { | 765 | fn test_auto_import_split_self_for_target() { |
766 | check_assist( | 766 | check_assist( |
767 | auto_import, | 767 | add_import, |
768 | " | 768 | " |
769 | use std::fmt::Debug; | 769 | use std::fmt::Debug; |
770 | 770 | ||
@@ -772,7 +772,7 @@ impl std::fmt<|> for Foo { | |||
772 | } | 772 | } |
773 | ", | 773 | ", |
774 | " | 774 | " |
775 | use std::fmt::{ self, Debug}; | 775 | use std::fmt::{self, Debug}; |
776 | 776 | ||
777 | impl fmt<|> for Foo { | 777 | impl fmt<|> for Foo { |
778 | } | 778 | } |
@@ -783,7 +783,7 @@ impl fmt<|> for Foo { | |||
783 | #[test] | 783 | #[test] |
784 | fn test_auto_import_add_to_nested_self_nested() { | 784 | fn test_auto_import_add_to_nested_self_nested() { |
785 | check_assist( | 785 | check_assist( |
786 | auto_import, | 786 | add_import, |
787 | " | 787 | " |
788 | use std::fmt::{Debug, nested::{Display}}; | 788 | use std::fmt::{Debug, nested::{Display}}; |
789 | 789 | ||
@@ -802,7 +802,7 @@ impl nested<|> for Foo { | |||
802 | #[test] | 802 | #[test] |
803 | fn test_auto_import_add_to_nested_self_already_included() { | 803 | fn test_auto_import_add_to_nested_self_already_included() { |
804 | check_assist( | 804 | check_assist( |
805 | auto_import, | 805 | add_import, |
806 | " | 806 | " |
807 | use std::fmt::{Debug, nested::{self, Display}}; | 807 | use std::fmt::{Debug, nested::{self, Display}}; |
808 | 808 | ||
@@ -821,7 +821,7 @@ impl nested<|> for Foo { | |||
821 | #[test] | 821 | #[test] |
822 | fn test_auto_import_add_to_nested_nested() { | 822 | fn test_auto_import_add_to_nested_nested() { |
823 | check_assist( | 823 | check_assist( |
824 | auto_import, | 824 | add_import, |
825 | " | 825 | " |
826 | use std::fmt::{Debug, nested::{Display}}; | 826 | use std::fmt::{Debug, nested::{Display}}; |
827 | 827 | ||
@@ -840,7 +840,7 @@ impl Debug<|> for Foo { | |||
840 | #[test] | 840 | #[test] |
841 | fn test_auto_import_split_common_target_longer() { | 841 | fn test_auto_import_split_common_target_longer() { |
842 | check_assist( | 842 | check_assist( |
843 | auto_import, | 843 | add_import, |
844 | " | 844 | " |
845 | use std::fmt::Debug; | 845 | use std::fmt::Debug; |
846 | 846 | ||
@@ -848,7 +848,7 @@ impl std::fmt::nested::Display<|> for Foo { | |||
848 | } | 848 | } |
849 | ", | 849 | ", |
850 | " | 850 | " |
851 | use std::fmt::{ nested::Display, Debug}; | 851 | use std::fmt::{nested::Display, Debug}; |
852 | 852 | ||
853 | impl Display<|> for Foo { | 853 | impl Display<|> for Foo { |
854 | } | 854 | } |
@@ -859,7 +859,7 @@ impl Display<|> for Foo { | |||
859 | #[test] | 859 | #[test] |
860 | fn test_auto_import_split_common_use_longer() { | 860 | fn test_auto_import_split_common_use_longer() { |
861 | check_assist( | 861 | check_assist( |
862 | auto_import, | 862 | add_import, |
863 | " | 863 | " |
864 | use std::fmt::nested::Debug; | 864 | use std::fmt::nested::Debug; |
865 | 865 | ||
@@ -867,7 +867,7 @@ impl std::fmt::Display<|> for Foo { | |||
867 | } | 867 | } |
868 | ", | 868 | ", |
869 | " | 869 | " |
870 | use std::fmt::{ Display, nested::Debug}; | 870 | use std::fmt::{Display, nested::Debug}; |
871 | 871 | ||
872 | impl Display<|> for Foo { | 872 | impl Display<|> for Foo { |
873 | } | 873 | } |
@@ -876,9 +876,32 @@ impl Display<|> for Foo { | |||
876 | } | 876 | } |
877 | 877 | ||
878 | #[test] | 878 | #[test] |
879 | fn test_auto_import_use_nested_import() { | ||
880 | check_assist( | ||
881 | add_import, | ||
882 | " | ||
883 | use crate::{ | ||
884 | ty::{Substs, Ty}, | ||
885 | AssocItem, | ||
886 | }; | ||
887 | |||
888 | fn foo() { crate::ty::lower<|>::trait_env() } | ||
889 | ", | ||
890 | " | ||
891 | use crate::{ | ||
892 | ty::{Substs, Ty, lower}, | ||
893 | AssocItem, | ||
894 | }; | ||
895 | |||
896 | fn foo() { lower<|>::trait_env() } | ||
897 | ", | ||
898 | ); | ||
899 | } | ||
900 | |||
901 | #[test] | ||
879 | fn test_auto_import_alias() { | 902 | fn test_auto_import_alias() { |
880 | check_assist( | 903 | check_assist( |
881 | auto_import, | 904 | add_import, |
882 | " | 905 | " |
883 | use std::fmt as foo; | 906 | use std::fmt as foo; |
884 | 907 | ||
@@ -897,7 +920,7 @@ impl Debug<|> for Foo { | |||
897 | #[test] | 920 | #[test] |
898 | fn test_auto_import_not_applicable_one_segment() { | 921 | fn test_auto_import_not_applicable_one_segment() { |
899 | check_assist_not_applicable( | 922 | check_assist_not_applicable( |
900 | auto_import, | 923 | add_import, |
901 | " | 924 | " |
902 | impl foo<|> for Foo { | 925 | impl foo<|> for Foo { |
903 | } | 926 | } |
@@ -908,7 +931,7 @@ impl foo<|> for Foo { | |||
908 | #[test] | 931 | #[test] |
909 | fn test_auto_import_not_applicable_in_use() { | 932 | fn test_auto_import_not_applicable_in_use() { |
910 | check_assist_not_applicable( | 933 | check_assist_not_applicable( |
911 | auto_import, | 934 | add_import, |
912 | " | 935 | " |
913 | use std::fmt<|>; | 936 | use std::fmt<|>; |
914 | ", | 937 | ", |
@@ -918,7 +941,7 @@ use std::fmt<|>; | |||
918 | #[test] | 941 | #[test] |
919 | fn test_auto_import_add_use_no_anchor_in_mod_mod() { | 942 | fn test_auto_import_add_use_no_anchor_in_mod_mod() { |
920 | check_assist( | 943 | check_assist( |
921 | auto_import, | 944 | add_import, |
922 | " | 945 | " |
923 | mod foo { | 946 | mod foo { |
924 | mod bar { | 947 | mod bar { |
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 565b96fb5..41de23921 100644 --- a/crates/ra_assists/src/assists/add_missing_impl_members.rs +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{db::HirDatabase, HasSource}; | 1 | use hir::{db::HirDatabase, HasSource}; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, edit, make, AstNode, NameOwner}, | 3 | ast::{self, edit, make, AstNode, NameOwner}, |
@@ -14,6 +12,34 @@ enum AddMissingImplMembersMode { | |||
14 | NoDefaultMethods, | 12 | NoDefaultMethods, |
15 | } | 13 | } |
16 | 14 | ||
15 | // Assist: add_impl_missing_members | ||
16 | // | ||
17 | // Adds scaffold for required impl members. | ||
18 | // | ||
19 | // ``` | ||
20 | // trait T { | ||
21 | // Type X; | ||
22 | // fn foo(&self); | ||
23 | // fn bar(&self) {} | ||
24 | // } | ||
25 | // | ||
26 | // impl T for () {<|> | ||
27 | // | ||
28 | // } | ||
29 | // ``` | ||
30 | // -> | ||
31 | // ``` | ||
32 | // trait T { | ||
33 | // Type X; | ||
34 | // fn foo(&self); | ||
35 | // fn bar(&self) {} | ||
36 | // } | ||
37 | // | ||
38 | // impl T for () { | ||
39 | // fn foo(&self) { unimplemented!() } | ||
40 | // | ||
41 | // } | ||
42 | // ``` | ||
17 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 43 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
18 | add_missing_impl_members_inner( | 44 | add_missing_impl_members_inner( |
19 | ctx, | 45 | ctx, |
@@ -23,6 +49,38 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti | |||
23 | ) | 49 | ) |
24 | } | 50 | } |
25 | 51 | ||
52 | // Assist: add_impl_default_members | ||
53 | // | ||
54 | // Adds scaffold for overriding default impl members. | ||
55 | // | ||
56 | // ``` | ||
57 | // trait T { | ||
58 | // Type X; | ||
59 | // fn foo(&self); | ||
60 | // fn bar(&self) {} | ||
61 | // } | ||
62 | // | ||
63 | // impl T for () { | ||
64 | // Type X = (); | ||
65 | // fn foo(&self) {}<|> | ||
66 | // | ||
67 | // } | ||
68 | // ``` | ||
69 | // -> | ||
70 | // ``` | ||
71 | // trait T { | ||
72 | // Type X; | ||
73 | // fn foo(&self); | ||
74 | // fn bar(&self) {} | ||
75 | // } | ||
76 | // | ||
77 | // impl T for () { | ||
78 | // Type X = (); | ||
79 | // fn foo(&self) {} | ||
80 | // fn bar(&self) {} | ||
81 | // | ||
82 | // } | ||
83 | // ``` | ||
26 | pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 84 | pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
27 | add_missing_impl_members_inner( | 85 | add_missing_impl_members_inner( |
28 | ctx, | 86 | ctx, |
@@ -33,12 +91,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> O | |||
33 | } | 91 | } |
34 | 92 | ||
35 | fn add_missing_impl_members_inner( | 93 | fn add_missing_impl_members_inner( |
36 | mut ctx: AssistCtx<impl HirDatabase>, | 94 | ctx: AssistCtx<impl HirDatabase>, |
37 | mode: AddMissingImplMembersMode, | 95 | mode: AddMissingImplMembersMode, |
38 | assist_id: &'static str, | 96 | assist_id: &'static str, |
39 | label: &'static str, | 97 | label: &'static str, |
40 | ) -> Option<Assist> { | 98 | ) -> Option<Assist> { |
41 | let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?; | 99 | let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?; |
42 | let impl_item_list = impl_node.item_list()?; | 100 | let impl_item_list = impl_node.item_list()?; |
43 | 101 | ||
44 | let trait_def = { | 102 | let trait_def = { |
@@ -75,7 +133,7 @@ fn add_missing_impl_members_inner( | |||
75 | return None; | 133 | return None; |
76 | } | 134 | } |
77 | 135 | ||
78 | ctx.add_action(AssistId(assist_id), label, |edit| { | 136 | ctx.add_assist(AssistId(assist_id), label, |edit| { |
79 | let n_existing_items = impl_item_list.impl_items().count(); | 137 | let n_existing_items = impl_item_list.impl_items().count(); |
80 | let items = missing_items | 138 | let items = missing_items |
81 | .into_iter() | 139 | .into_iter() |
@@ -92,9 +150,7 @@ fn add_missing_impl_members_inner( | |||
92 | 150 | ||
93 | edit.replace_ast(impl_item_list, new_impl_item_list); | 151 | edit.replace_ast(impl_item_list, new_impl_item_list); |
94 | edit.set_cursor(cursor_position); | 152 | edit.set_cursor(cursor_position); |
95 | }); | 153 | }) |
96 | |||
97 | ctx.build() | ||
98 | } | 154 | } |
99 | 155 | ||
100 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | 156 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { |
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 5f2b0dd18..068da1774 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs | |||
@@ -1,20 +1,30 @@ | |||
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)`. | ||
4 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
5 | use ra_syntax::ast::{self, AstNode}; | 2 | use ra_syntax::ast::{self, AstNode}; |
6 | use ra_syntax::SyntaxNode; | 3 | use ra_syntax::SyntaxNode; |
7 | 4 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 6 | ||
10 | /// Assist for applying demorgan's law | 7 | // Assist: apply_demorgan |
11 | /// | 8 | // |
12 | /// This transforms expressions of the form `!l || !r` into `!(l && r)`. | 9 | // Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). |
13 | /// This also works with `&&`. This assist can only be applied with the cursor | 10 | // This transforms expressions of the form `!l || !r` into `!(l && r)`. |
14 | /// on either `||` or `&&`, with both operands being a negation of some kind. | 11 | // This also works with `&&`. This assist can only be applied with the cursor |
15 | /// This means something of the form `!x` or `x != y`. | 12 | // on either `||` or `&&`, with both operands being a negation of some kind. |
16 | pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 13 | // This means something of the form `!x` or `x != y`. |
17 | let expr = ctx.node_at_offset::<ast::BinExpr>()?; | 14 | // |
15 | // ``` | ||
16 | // fn main() { | ||
17 | // if x != 4 ||<|> !y {} | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // fn main() { | ||
23 | // if !(x == 4 && y) {} | ||
24 | // } | ||
25 | // ``` | ||
26 | pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
27 | let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; | ||
18 | let op = expr.op_kind()?; | 28 | let op = expr.op_kind()?; |
19 | let op_range = expr.op_token()?.text_range(); | 29 | let op_range = expr.op_token()?.text_range(); |
20 | let opposite_op = opposite_logic_op(op)?; | 30 | let opposite_op = opposite_logic_op(op)?; |
@@ -29,13 +39,12 @@ pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Ass | |||
29 | let not_lhs = undo_negation(lhs)?; | 39 | let not_lhs = undo_negation(lhs)?; |
30 | let not_rhs = undo_negation(rhs)?; | 40 | let not_rhs = undo_negation(rhs)?; |
31 | 41 | ||
32 | ctx.add_action(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { | 42 | ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { |
33 | edit.target(op_range); | 43 | edit.target(op_range); |
34 | edit.replace(op_range, opposite_op); | 44 | edit.replace(op_range, opposite_op); |
35 | edit.replace(lhs_range, format!("!({}", not_lhs)); | 45 | edit.replace(lhs_range, format!("!({}", not_lhs)); |
36 | edit.replace(rhs_range, format!("{})", not_rhs)); | 46 | edit.replace(rhs_range, format!("{})", not_rhs)); |
37 | }); | 47 | }) |
38 | ctx.build() | ||
39 | } | 48 | } |
40 | 49 | ||
41 | // Return the opposite text for a given logical operator, if it makes sense | 50 | // Return the opposite text for a given logical operator, if it makes sense |
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs index df92c6b67..132c9dc1d 100644 --- a/crates/ra_assists/src/assists/change_visibility.rs +++ b/crates/ra_assists/src/assists/change_visibility.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, NameOwner, VisibilityOwner}, | 3 | ast::{self, NameOwner, VisibilityOwner}, |
@@ -13,14 +11,25 @@ use ra_syntax::{ | |||
13 | 11 | ||
14 | use crate::{Assist, AssistCtx, AssistId}; | 12 | use crate::{Assist, AssistCtx, AssistId}; |
15 | 13 | ||
14 | // Assist: change_visibility | ||
15 | // | ||
16 | // Adds or changes existing visibility specifier. | ||
17 | // | ||
18 | // ``` | ||
19 | // <|>fn frobnicate() {} | ||
20 | // ``` | ||
21 | // -> | ||
22 | // ``` | ||
23 | // pub(crate) fn frobnicate() {} | ||
24 | // ``` | ||
16 | pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 25 | pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
17 | if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { | 26 | if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { |
18 | return change_vis(ctx, vis); | 27 | return change_vis(ctx, vis); |
19 | } | 28 | } |
20 | add_vis(ctx) | 29 | add_vis(ctx) |
21 | } | 30 | } |
22 | 31 | ||
23 | fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 32 | fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
24 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { | 33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { |
25 | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, | 34 | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, |
26 | _ => false, | 35 | _ => false, |
@@ -48,13 +57,11 @@ fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
48 | (vis_offset(field.syntax()), ident.text_range()) | 57 | (vis_offset(field.syntax()), ident.text_range()) |
49 | }; | 58 | }; |
50 | 59 | ||
51 | ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { | 60 | ctx.add_assist(AssistId("change_visibility"), "make pub(crate)", |edit| { |
52 | edit.target(target); | 61 | edit.target(target); |
53 | edit.insert(offset, "pub(crate) "); | 62 | edit.insert(offset, "pub(crate) "); |
54 | edit.set_cursor(offset); | 63 | edit.set_cursor(offset); |
55 | }); | 64 | }) |
56 | |||
57 | ctx.build() | ||
58 | } | 65 | } |
59 | 66 | ||
60 | fn vis_offset(node: &SyntaxNode) -> TextUnit { | 67 | fn vis_offset(node: &SyntaxNode) -> TextUnit { |
@@ -68,24 +75,20 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit { | |||
68 | .unwrap_or_else(|| node.text_range().start()) | 75 | .unwrap_or_else(|| node.text_range().start()) |
69 | } | 76 | } |
70 | 77 | ||
71 | fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { | 78 | fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { |
72 | if vis.syntax().text() == "pub" { | 79 | if vis.syntax().text() == "pub" { |
73 | ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { | 80 | return ctx.add_assist(AssistId("change_visibility"), "change to pub(crate)", |edit| { |
74 | edit.target(vis.syntax().text_range()); | 81 | edit.target(vis.syntax().text_range()); |
75 | edit.replace(vis.syntax().text_range(), "pub(crate)"); | 82 | edit.replace(vis.syntax().text_range(), "pub(crate)"); |
76 | edit.set_cursor(vis.syntax().text_range().start()) | 83 | edit.set_cursor(vis.syntax().text_range().start()) |
77 | }); | 84 | }); |
78 | |||
79 | return ctx.build(); | ||
80 | } | 85 | } |
81 | if vis.syntax().text() == "pub(crate)" { | 86 | if vis.syntax().text() == "pub(crate)" { |
82 | ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { | 87 | return ctx.add_assist(AssistId("change_visibility"), "change to pub", |edit| { |
83 | edit.target(vis.syntax().text_range()); | 88 | edit.target(vis.syntax().text_range()); |
84 | edit.replace(vis.syntax().text_range(), "pub"); | 89 | edit.replace(vis.syntax().text_range(), "pub"); |
85 | edit.set_cursor(vis.syntax().text_range().start()); | 90 | edit.set_cursor(vis.syntax().text_range().start()); |
86 | }); | 91 | }); |
87 | |||
88 | return ctx.build(); | ||
89 | } | 92 | } |
90 | None | 93 | None |
91 | } | 94 | } |
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs index f7d7e12e7..ad6c5695a 100644 --- a/crates/ra_assists/src/assists/early_return.rs +++ b/crates/ra_assists/src/assists/early_return.rs | |||
@@ -1,26 +1,3 @@ | |||
1 | //! Assist: `convert_to_guarded_return` | ||
2 | //! | ||
3 | //! Replace a large conditional with a guarded return. | ||
4 | //! | ||
5 | //! ```text | ||
6 | //! fn <|>main() { | ||
7 | //! if cond { | ||
8 | //! foo(); | ||
9 | //! bar(); | ||
10 | //! } | ||
11 | //! } | ||
12 | //! ``` | ||
13 | //! -> | ||
14 | //! ```text | ||
15 | //! fn main() { | ||
16 | //! if !cond { | ||
17 | //! return; | ||
18 | //! } | ||
19 | //! foo(); | ||
20 | //! bar(); | ||
21 | //! } | ||
22 | //! ``` | ||
23 | |||
24 | use std::ops::RangeInclusive; | 1 | use std::ops::RangeInclusive; |
25 | 2 | ||
26 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
@@ -36,8 +13,30 @@ use crate::{ | |||
36 | AssistId, | 13 | AssistId, |
37 | }; | 14 | }; |
38 | 15 | ||
39 | pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 16 | // Assist: convert_to_guarded_return |
40 | let if_expr: ast::IfExpr = ctx.node_at_offset()?; | 17 | // |
18 | // Replace a large conditional with a guarded return. | ||
19 | // | ||
20 | // ``` | ||
21 | // fn main() { | ||
22 | // <|>if cond { | ||
23 | // foo(); | ||
24 | // bar(); | ||
25 | // } | ||
26 | // } | ||
27 | // ``` | ||
28 | // -> | ||
29 | // ``` | ||
30 | // fn main() { | ||
31 | // if !cond { | ||
32 | // return; | ||
33 | // } | ||
34 | // foo(); | ||
35 | // bar(); | ||
36 | // } | ||
37 | // ``` | ||
38 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
39 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | ||
41 | let expr = if_expr.condition()?.expr()?; | 40 | let expr = if_expr.condition()?.expr()?; |
42 | let then_block = if_expr.then_branch()?.block()?; | 41 | let then_block = if_expr.then_branch()?.block()?; |
43 | if if_expr.else_branch().is_some() { | 42 | if if_expr.else_branch().is_some() { |
@@ -51,7 +50,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
51 | } | 50 | } |
52 | 51 | ||
53 | // check for early return and continue | 52 | // check for early return and continue |
54 | let first_in_then_block = then_block.syntax().first_child()?.clone(); | 53 | let first_in_then_block = then_block.syntax().first_child()?; |
55 | if ast::ReturnExpr::can_cast(first_in_then_block.kind()) | 54 | if ast::ReturnExpr::can_cast(first_in_then_block.kind()) |
56 | || ast::ContinueExpr::can_cast(first_in_then_block.kind()) | 55 | || ast::ContinueExpr::can_cast(first_in_then_block.kind()) |
57 | || first_in_then_block | 56 | || first_in_then_block |
@@ -76,7 +75,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
76 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | 75 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; |
77 | let cursor_position = ctx.frange.range.start(); | 76 | let cursor_position = ctx.frange.range.start(); |
78 | 77 | ||
79 | ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { | 78 | ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { |
80 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 79 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
81 | let new_if_expr = | 80 | let new_if_expr = |
82 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); | 81 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); |
@@ -106,8 +105,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
106 | edit.target(if_expr.syntax().text_range()); | 105 | edit.target(if_expr.syntax().text_range()); |
107 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); | 106 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); |
108 | edit.set_cursor(cursor_position); | 107 | edit.set_cursor(cursor_position); |
109 | }); | 108 | }) |
110 | ctx.build() | ||
111 | } | 109 | } |
112 | 110 | ||
113 | #[cfg(test)] | 111 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index e3f30b5de..2b74f355c 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs | |||
@@ -7,8 +7,32 @@ use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; | |||
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 9 | ||
10 | pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // Assist: fill_match_arms |
11 | let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; | 11 | // |
12 | // Adds missing clauses to a `match` expression. | ||
13 | // | ||
14 | // ``` | ||
15 | // enum Action { Move { distance: u32 }, Stop } | ||
16 | // | ||
17 | // fn handle(action: Action) { | ||
18 | // match action { | ||
19 | // <|> | ||
20 | // } | ||
21 | // } | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // enum Action { Move { distance: u32 }, Stop } | ||
26 | // | ||
27 | // fn handle(action: Action) { | ||
28 | // match action { | ||
29 | // Action::Move { distance } => (), | ||
30 | // Action::Stop => (), | ||
31 | // } | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
35 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; | ||
12 | let match_arm_list = match_expr.match_arm_list()?; | 36 | let match_arm_list = match_expr.match_arm_list()?; |
13 | 37 | ||
14 | // We already have some match arms, so we don't provide any assists. | 38 | // We already have some match arms, so we don't provide any assists. |
@@ -29,7 +53,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
29 | }; | 53 | }; |
30 | let variant_list = enum_def.variant_list()?; | 54 | let variant_list = enum_def.variant_list()?; |
31 | 55 | ||
32 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { | 56 | ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| { |
33 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); | 57 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); |
34 | 58 | ||
35 | let new_arm_list = { | 59 | let new_arm_list = { |
@@ -43,9 +67,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
43 | edit.target(match_expr.syntax().text_range()); | 67 | edit.target(match_expr.syntax().text_range()); |
44 | edit.set_cursor(expr.syntax().text_range().start()); | 68 | edit.set_cursor(expr.syntax().text_range().start()); |
45 | edit.replace_ast(match_arm_list, new_arm_list); | 69 | edit.replace_ast(match_arm_list, new_arm_list); |
46 | }); | 70 | }) |
47 | |||
48 | ctx.build() | ||
49 | } | 71 | } |
50 | 72 | ||
51 | fn is_trivial(arm: &ast::MatchArm) -> bool { | 73 | fn is_trivial(arm: &ast::MatchArm) -> bool { |
@@ -130,7 +152,7 @@ mod tests { | |||
130 | A::Bs => (), | 152 | A::Bs => (), |
131 | A::Cs(_) => (), | 153 | A::Cs(_) => (), |
132 | A::Ds(_, _) => (), | 154 | A::Ds(_, _) => (), |
133 | A::Es{ x, y } => (), | 155 | A::Es { x, y } => (), |
134 | } | 156 | } |
135 | } | 157 | } |
136 | "#, | 158 | "#, |
@@ -183,7 +205,7 @@ mod tests { | |||
183 | 205 | ||
184 | fn foo(a: &mut A) { | 206 | fn foo(a: &mut A) { |
185 | match <|>a { | 207 | match <|>a { |
186 | A::Es{ x, y } => (), | 208 | A::Es { x, y } => (), |
187 | } | 209 | } |
188 | } | 210 | } |
189 | "#, | 211 | "#, |
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs index c51035282..386045eb0 100644 --- a/crates/ra_assists/src/assists/flip_binexpr.rs +++ b/crates/ra_assists/src/assists/flip_binexpr.rs | |||
@@ -1,13 +1,25 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | 2 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; |
5 | 3 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 4 | use crate::{Assist, AssistCtx, AssistId}; |
7 | 5 | ||
8 | /// Flip binary expression assist. | 6 | // Assist: flip_binexpr |
9 | pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 7 | // |
10 | let expr = ctx.node_at_offset::<BinExpr>()?; | 8 | // Flips operands of a binary expression. |
9 | // | ||
10 | // ``` | ||
11 | // fn main() { | ||
12 | // let _ = 90 +<|> 2; | ||
13 | // } | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // fn main() { | ||
18 | // let _ = 2 + 90; | ||
19 | // } | ||
20 | // ``` | ||
21 | pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | let expr = ctx.find_node_at_offset::<BinExpr>()?; | ||
11 | let lhs = expr.lhs()?.syntax().clone(); | 23 | let lhs = expr.lhs()?.syntax().clone(); |
12 | let rhs = expr.rhs()?.syntax().clone(); | 24 | let rhs = expr.rhs()?.syntax().clone(); |
13 | let op_range = expr.op_token()?.text_range(); | 25 | let op_range = expr.op_token()?.text_range(); |
@@ -22,16 +34,14 @@ pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis | |||
22 | return None; | 34 | return None; |
23 | } | 35 | } |
24 | 36 | ||
25 | ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| { | 37 | ctx.add_assist(AssistId("flip_binexpr"), "flip binary expression", |edit| { |
26 | edit.target(op_range); | 38 | edit.target(op_range); |
27 | if let FlipAction::FlipAndReplaceOp(new_op) = action { | 39 | if let FlipAction::FlipAndReplaceOp(new_op) = action { |
28 | edit.replace(op_range, new_op); | 40 | edit.replace(op_range, new_op); |
29 | } | 41 | } |
30 | edit.replace(lhs.text_range(), rhs.text()); | 42 | edit.replace(lhs.text_range(), rhs.text()); |
31 | edit.replace(rhs.text_range(), lhs.text()); | 43 | edit.replace(rhs.text_range(), lhs.text()); |
32 | }); | 44 | }) |
33 | |||
34 | ctx.build() | ||
35 | } | 45 | } |
36 | 46 | ||
37 | enum FlipAction { | 47 | enum FlipAction { |
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs index e31cc5e7d..9be1c1dc6 100644 --- a/crates/ra_assists/src/assists/flip_comma.rs +++ b/crates/ra_assists/src/assists/flip_comma.rs | |||
@@ -1,12 +1,25 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; | 2 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; |
5 | 3 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 4 | use crate::{Assist, AssistCtx, AssistId}; |
7 | 5 | ||
8 | pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 6 | // Assist: flip_comma |
9 | let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; | 7 | // |
8 | // Flips two comma-separated items. | ||
9 | // | ||
10 | // ``` | ||
11 | // fn main() { | ||
12 | // ((1, 2),<|> (3, 4)); | ||
13 | // } | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // fn main() { | ||
18 | // ((3, 4), (1, 2)); | ||
19 | // } | ||
20 | // ``` | ||
21 | pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | let comma = ctx.find_token_at_offset(T![,])?; | ||
10 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; | 23 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; |
11 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; | 24 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; |
12 | 25 | ||
@@ -16,13 +29,11 @@ pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
16 | return None; | 29 | return None; |
17 | } | 30 | } |
18 | 31 | ||
19 | ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { | 32 | ctx.add_assist(AssistId("flip_comma"), "flip comma", |edit| { |
20 | edit.target(comma.text_range()); | 33 | edit.target(comma.text_range()); |
21 | edit.replace(prev.text_range(), next.to_string()); | 34 | edit.replace(prev.text_range(), next.to_string()); |
22 | edit.replace(next.text_range(), prev.to_string()); | 35 | edit.replace(next.text_range(), prev.to_string()); |
23 | }); | 36 | }) |
24 | |||
25 | ctx.build() | ||
26 | } | 37 | } |
27 | 38 | ||
28 | #[cfg(test)] | 39 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/flip_trait_bound.rs b/crates/ra_assists/src/assists/flip_trait_bound.rs new file mode 100644 index 000000000..6017b39dd --- /dev/null +++ b/crates/ra_assists/src/assists/flip_trait_bound.rs | |||
@@ -0,0 +1,117 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | algo::non_trivia_sibling, | ||
4 | ast::{self, AstNode}, | ||
5 | Direction, T, | ||
6 | }; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | // Assist: flip_trait_bound | ||
11 | // | ||
12 | // Flips two trait bounds. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn foo<T: Clone +<|> Copy>() { } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // fn foo<T: Copy + Clone>() { } | ||
20 | // ``` | ||
21 | pub(crate) fn flip_trait_bound(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | // We want to replicate the behavior of `flip_binexpr` by only suggesting | ||
23 | // the assist when the cursor is on a `+` | ||
24 | let plus = ctx.find_token_at_offset(T![+])?; | ||
25 | |||
26 | // Make sure we're in a `TypeBoundList` | ||
27 | if ast::TypeBoundList::cast(plus.parent()).is_none() { | ||
28 | return None; | ||
29 | } | ||
30 | |||
31 | let (before, after) = ( | ||
32 | non_trivia_sibling(plus.clone().into(), Direction::Prev)?, | ||
33 | non_trivia_sibling(plus.clone().into(), Direction::Next)?, | ||
34 | ); | ||
35 | |||
36 | ctx.add_assist(AssistId("flip_trait_bound"), "flip trait bound", |edit| { | ||
37 | edit.target(plus.text_range()); | ||
38 | edit.replace(before.text_range(), after.to_string()); | ||
39 | edit.replace(after.text_range(), before.to_string()); | ||
40 | }) | ||
41 | } | ||
42 | |||
43 | #[cfg(test)] | ||
44 | mod tests { | ||
45 | use super::*; | ||
46 | |||
47 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
48 | |||
49 | #[test] | ||
50 | fn flip_trait_bound_assist_available() { | ||
51 | check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+") | ||
52 | } | ||
53 | |||
54 | #[test] | ||
55 | fn flip_trait_bound_not_applicable_for_single_trait_bound() { | ||
56 | check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }") | ||
57 | } | ||
58 | |||
59 | #[test] | ||
60 | fn flip_trait_bound_works_for_struct() { | ||
61 | check_assist( | ||
62 | flip_trait_bound, | ||
63 | "struct S<T> where T: A <|>+ B { }", | ||
64 | "struct S<T> where T: B <|>+ A { }", | ||
65 | ) | ||
66 | } | ||
67 | |||
68 | #[test] | ||
69 | fn flip_trait_bound_works_for_trait_impl() { | ||
70 | check_assist( | ||
71 | flip_trait_bound, | ||
72 | "impl X for S<T> where T: A +<|> B { }", | ||
73 | "impl X for S<T> where T: B +<|> A { }", | ||
74 | ) | ||
75 | } | ||
76 | |||
77 | #[test] | ||
78 | fn flip_trait_bound_works_for_fn() { | ||
79 | check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }") | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn flip_trait_bound_works_for_fn_where_clause() { | ||
84 | check_assist( | ||
85 | flip_trait_bound, | ||
86 | "fn f<T>(t: T) where T: A +<|> B { }", | ||
87 | "fn f<T>(t: T) where T: B +<|> A { }", | ||
88 | ) | ||
89 | } | ||
90 | |||
91 | #[test] | ||
92 | fn flip_trait_bound_works_for_lifetime() { | ||
93 | check_assist( | ||
94 | flip_trait_bound, | ||
95 | "fn f<T>(t: T) where T: A <|>+ 'static { }", | ||
96 | "fn f<T>(t: T) where T: 'static <|>+ A { }", | ||
97 | ) | ||
98 | } | ||
99 | |||
100 | #[test] | ||
101 | fn flip_trait_bound_works_for_complex_bounds() { | ||
102 | check_assist( | ||
103 | flip_trait_bound, | ||
104 | "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", | ||
105 | "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }", | ||
106 | ) | ||
107 | } | ||
108 | |||
109 | #[test] | ||
110 | fn flip_trait_bound_works_for_long_bounds() { | ||
111 | check_assist( | ||
112 | flip_trait_bound, | ||
113 | "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", | ||
114 | "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }", | ||
115 | ) | ||
116 | } | ||
117 | } | ||
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs index 9bd64decc..a7fd9b6d2 100644 --- a/crates/ra_assists/src/assists/inline_local_variable.rs +++ b/crates/ra_assists/src/assists/inline_local_variable.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, AstNode, AstToken}, | 3 | ast::{self, AstNode, AstToken}, |
@@ -9,8 +7,24 @@ use ra_syntax::{ | |||
9 | use crate::assist_ctx::AssistBuilder; | 7 | use crate::assist_ctx::AssistBuilder; |
10 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
11 | 9 | ||
12 | pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // Assist: inline_local_variable |
13 | let let_stmt = ctx.node_at_offset::<ast::LetStmt>()?; | 11 | // |
12 | // Inlines local variable. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn main() { | ||
16 | // let x<|> = 1 + 2; | ||
17 | // x * 4; | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // fn main() { | ||
23 | // (1 + 2) * 4; | ||
24 | // } | ||
25 | // ``` | ||
26 | pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
27 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | ||
14 | let bind_pat = match let_stmt.pat()? { | 28 | let bind_pat = match let_stmt.pat()? { |
15 | ast::Pat::BindPat(pat) => pat, | 29 | ast::Pat::BindPat(pat) => pat, |
16 | _ => return None, | 30 | _ => return None, |
@@ -37,10 +51,8 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
37 | let mut wrap_in_parens = vec![true; refs.len()]; | 51 | let mut wrap_in_parens = vec![true; refs.len()]; |
38 | 52 | ||
39 | for (i, desc) in refs.iter().enumerate() { | 53 | for (i, desc) in refs.iter().enumerate() { |
40 | let usage_node = ctx | 54 | let usage_node = |
41 | .covering_node_for_range(desc.range) | 55 | ctx.covering_node_for_range(desc.range).ancestors().find_map(ast::PathExpr::cast)?; |
42 | .ancestors() | ||
43 | .find_map(|node| ast::PathExpr::cast(node))?; | ||
44 | let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); | 56 | let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); |
45 | let usage_parent = match usage_parent_option { | 57 | let usage_parent = match usage_parent_option { |
46 | Some(u) => u, | 58 | Some(u) => u, |
@@ -79,7 +91,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
79 | let init_str = initializer_expr.syntax().text().to_string(); | 91 | let init_str = initializer_expr.syntax().text().to_string(); |
80 | let init_in_paren = format!("({})", &init_str); | 92 | let init_in_paren = format!("({})", &init_str); |
81 | 93 | ||
82 | ctx.add_action( | 94 | ctx.add_assist( |
83 | AssistId("inline_local_variable"), | 95 | AssistId("inline_local_variable"), |
84 | "inline local variable", | 96 | "inline local variable", |
85 | move |edit: &mut AssistBuilder| { | 97 | move |edit: &mut AssistBuilder| { |
@@ -93,9 +105,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
93 | } | 105 | } |
94 | edit.set_cursor(delete_range.start()) | 106 | edit.set_cursor(delete_range.start()) |
95 | }, | 107 | }, |
96 | ); | 108 | ) |
97 | |||
98 | ctx.build() | ||
99 | } | 109 | } |
100 | 110 | ||
101 | #[cfg(test)] | 111 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs index 43378c4b0..0623d4475 100644 --- a/crates/ra_assists/src/assists/introduce_variable.rs +++ b/crates/ra_assists/src/assists/introduce_variable.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use format_buf::format; | 1 | use format_buf::format; |
4 | use hir::db::HirDatabase; | 2 | use hir::db::HirDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -14,7 +12,23 @@ use test_utils::tested_by; | |||
14 | 12 | ||
15 | use crate::{Assist, AssistCtx, AssistId}; | 13 | use crate::{Assist, AssistCtx, AssistId}; |
16 | 14 | ||
17 | pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 15 | // Assist: introduce_variable |
16 | // | ||
17 | // Extracts subexpression into a variable. | ||
18 | // | ||
19 | // ``` | ||
20 | // fn main() { | ||
21 | // <|>(1 + 2)<|> * 4; | ||
22 | // } | ||
23 | // ``` | ||
24 | // -> | ||
25 | // ``` | ||
26 | // fn main() { | ||
27 | // let var_name = (1 + 2); | ||
28 | // var_name * 4; | ||
29 | // } | ||
30 | // ``` | ||
31 | pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
18 | if ctx.frange.range.is_empty() { | 32 | if ctx.frange.range.is_empty() { |
19 | return None; | 33 | return None; |
20 | } | 34 | } |
@@ -29,7 +43,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option | |||
29 | if indent.kind() != WHITESPACE { | 43 | if indent.kind() != WHITESPACE { |
30 | return None; | 44 | return None; |
31 | } | 45 | } |
32 | ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { | 46 | ctx.add_assist(AssistId("introduce_variable"), "introduce variable", move |edit| { |
33 | let mut buf = String::new(); | 47 | let mut buf = String::new(); |
34 | 48 | ||
35 | let cursor_offset = if wrap_in_block { | 49 | let cursor_offset = if wrap_in_block { |
@@ -74,9 +88,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option | |||
74 | } | 88 | } |
75 | } | 89 | } |
76 | edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); | 90 | edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); |
77 | }); | 91 | }) |
78 | |||
79 | ctx.build() | ||
80 | } | 92 | } |
81 | 93 | ||
82 | /// Check whether the node is a valid expression which can be extracted to a variable. | 94 | /// Check whether the node is a valid expression which can be extracted to a variable. |
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs index 17baa98f9..e9f2cae91 100644 --- a/crates/ra_assists/src/assists/merge_match_arms.rs +++ b/crates/ra_assists/src/assists/merge_match_arms.rs | |||
@@ -1,11 +1,33 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; | 1 | use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; |
4 | use hir::db::HirDatabase; | 2 | use hir::db::HirDatabase; |
5 | use ra_syntax::ast::{AstNode, MatchArm}; | 3 | use ra_syntax::ast::{AstNode, MatchArm}; |
6 | 4 | ||
7 | pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 5 | // Assist: merge_match_arms |
8 | let current_arm = ctx.node_at_offset::<MatchArm>()?; | 6 | // |
7 | // Merges identical match arms. | ||
8 | // | ||
9 | // ``` | ||
10 | // enum Action { Move { distance: u32 }, Stop } | ||
11 | // | ||
12 | // fn handle(action: Action) { | ||
13 | // match action { | ||
14 | // <|>Action::Move(..) => foo(), | ||
15 | // Action::Stop => foo(), | ||
16 | // } | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // enum Action { Move { distance: u32 }, Stop } | ||
22 | // | ||
23 | // fn handle(action: Action) { | ||
24 | // match action { | ||
25 | // Action::Move(..) | Action::Stop => foo(), | ||
26 | // } | ||
27 | // } | ||
28 | // ``` | ||
29 | pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
30 | let current_arm = ctx.find_node_at_offset::<MatchArm>()?; | ||
9 | 31 | ||
10 | // We check if the following match arm matches this one. We could, but don't, | 32 | // We check if the following match arm matches this one. We could, but don't, |
11 | // compare to the previous match arm as well. | 33 | // compare to the previous match arm as well. |
@@ -30,7 +52,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<A | |||
30 | 52 | ||
31 | let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); | 53 | let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); |
32 | 54 | ||
33 | ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { | 55 | ctx.add_assist(AssistId("merge_match_arms"), "merge match arms", |edit| { |
34 | fn contains_placeholder(a: &MatchArm) -> bool { | 56 | fn contains_placeholder(a: &MatchArm) -> bool { |
35 | a.pats().any(|x| match x { | 57 | a.pats().any(|x| match x { |
36 | ra_syntax::ast::Pat::PlaceholderPat(..) => true, | 58 | ra_syntax::ast::Pat::PlaceholderPat(..) => true, |
@@ -58,9 +80,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<A | |||
58 | edit.target(current_arm.syntax().text_range()); | 80 | edit.target(current_arm.syntax().text_range()); |
59 | edit.replace(TextRange::from_to(start, end), arm); | 81 | edit.replace(TextRange::from_to(start, end), arm); |
60 | edit.set_cursor(start + offset); | 82 | edit.set_cursor(start + offset); |
61 | }); | 83 | }) |
62 | |||
63 | ctx.build() | ||
64 | } | 84 | } |
65 | 85 | ||
66 | #[cfg(test)] | 86 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs index d2444b6b9..3145d7625 100644 --- a/crates/ra_assists/src/assists/move_bounds.rs +++ b/crates/ra_assists/src/assists/move_bounds.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner}, | 3 | ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner}, |
@@ -9,8 +7,23 @@ use ra_syntax::{ | |||
9 | 7 | ||
10 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
11 | 9 | ||
12 | pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // Assist: move_bounds_to_where_clause |
13 | let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; | 11 | // |
12 | // Moves inline type bounds to a where clause. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { | ||
16 | // f(x) | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U { | ||
22 | // f(x) | ||
23 | // } | ||
24 | // ``` | ||
25 | pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
26 | let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; | ||
14 | 27 | ||
15 | let mut type_params = type_param_list.type_params(); | 28 | let mut type_params = type_param_list.type_params(); |
16 | if type_params.all(|p| p.type_bound_list().is_none()) { | 29 | if type_params.all(|p| p.type_bound_list().is_none()) { |
@@ -33,38 +46,30 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) | |||
33 | _ => return None, | 46 | _ => return None, |
34 | }; | 47 | }; |
35 | 48 | ||
36 | ctx.add_action( | 49 | ctx.add_assist(AssistId("move_bounds_to_where_clause"), "move_bounds_to_where_clause", |edit| { |
37 | AssistId("move_bounds_to_where_clause"), | 50 | let new_params = type_param_list |
38 | "move_bounds_to_where_clause", | 51 | .type_params() |
39 | |edit| { | 52 | .filter(|it| it.type_bound_list().is_some()) |
40 | let new_params = type_param_list | 53 | .map(|type_param| { |
41 | .type_params() | 54 | let without_bounds = type_param.remove_bounds(); |
42 | .filter(|it| it.type_bound_list().is_some()) | 55 | (type_param, without_bounds) |
43 | .map(|type_param| { | 56 | }); |
44 | let without_bounds = type_param.remove_bounds(); | 57 | |
45 | (type_param, without_bounds) | 58 | let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); |
46 | }); | 59 | edit.replace_ast(type_param_list.clone(), new_type_param_list); |
47 | 60 | ||
48 | let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); | 61 | let where_clause = { |
49 | edit.replace_ast(type_param_list.clone(), new_type_param_list); | 62 | let predicates = type_param_list.type_params().filter_map(build_predicate); |
50 | 63 | make::where_clause(predicates) | |
51 | let where_clause = { | 64 | }; |
52 | let predicates = type_param_list.type_params().filter_map(build_predicate); | 65 | |
53 | make::where_clause(predicates) | 66 | let to_insert = match anchor.prev_sibling_or_token() { |
54 | }; | 67 | Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), |
55 | 68 | _ => format!(" {}", where_clause.syntax()), | |
56 | let to_insert = match anchor.prev_sibling_or_token() { | 69 | }; |
57 | Some(ref elem) if elem.kind() == WHITESPACE => { | 70 | edit.insert(anchor.text_range().start(), to_insert); |
58 | format!("{} ", where_clause.syntax()) | 71 | edit.target(type_param_list.syntax().text_range()); |
59 | } | 72 | }) |
60 | _ => format!(" {}", where_clause.syntax()), | ||
61 | }; | ||
62 | edit.insert(anchor.text_range().start(), to_insert); | ||
63 | edit.target(type_param_list.syntax().text_range()); | ||
64 | }, | ||
65 | ); | ||
66 | |||
67 | ctx.build() | ||
68 | } | 73 | } |
69 | 74 | ||
70 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { | 75 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { |
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs index 51aea6334..b49ec6172 100644 --- a/crates/ra_assists/src/assists/move_guard.rs +++ b/crates/ra_assists/src/assists/move_guard.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast, | 3 | ast, |
@@ -9,8 +7,33 @@ use ra_syntax::{ | |||
9 | 7 | ||
10 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
11 | 9 | ||
12 | pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // Assist: move_guard_to_arm_body |
13 | let match_arm = ctx.node_at_offset::<MatchArm>()?; | 11 | // |
12 | // Moves match guard into match arm body. | ||
13 | // | ||
14 | // ``` | ||
15 | // enum Action { Move { distance: u32 }, Stop } | ||
16 | // | ||
17 | // fn handle(action: Action) { | ||
18 | // match action { | ||
19 | // Action::Move { distance } <|>if distance > 10 => foo(), | ||
20 | // _ => (), | ||
21 | // } | ||
22 | // } | ||
23 | // ``` | ||
24 | // -> | ||
25 | // ``` | ||
26 | // enum Action { Move { distance: u32 }, Stop } | ||
27 | // | ||
28 | // fn handle(action: Action) { | ||
29 | // match action { | ||
30 | // Action::Move { distance } => if distance > 10 { foo() }, | ||
31 | // _ => (), | ||
32 | // } | ||
33 | // } | ||
34 | // ``` | ||
35 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
36 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; | ||
14 | let guard = match_arm.guard()?; | 37 | let guard = match_arm.guard()?; |
15 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | 38 | let space_before_guard = guard.syntax().prev_sibling_or_token(); |
16 | 39 | ||
@@ -18,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op | |||
18 | let arm_expr = match_arm.expr()?; | 41 | let arm_expr = match_arm.expr()?; |
19 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | 42 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); |
20 | 43 | ||
21 | ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { | 44 | ctx.add_assist(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { |
22 | edit.target(guard.syntax().text_range()); | 45 | edit.target(guard.syntax().text_range()); |
23 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { | 46 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { |
24 | Some(tok) => { | 47 | Some(tok) => { |
@@ -38,12 +61,36 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op | |||
38 | edit.set_cursor( | 61 | edit.set_cursor( |
39 | arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, | 62 | arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, |
40 | ); | 63 | ); |
41 | }); | 64 | }) |
42 | ctx.build() | ||
43 | } | 65 | } |
44 | 66 | ||
45 | pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 67 | // Assist: move_arm_cond_to_match_guard |
46 | let match_arm: MatchArm = ctx.node_at_offset::<MatchArm>()?; | 68 | // |
69 | // Moves if expression from match arm body into a guard. | ||
70 | // | ||
71 | // ``` | ||
72 | // enum Action { Move { distance: u32 }, Stop } | ||
73 | // | ||
74 | // fn handle(action: Action) { | ||
75 | // match action { | ||
76 | // Action::Move { distance } => <|>if distance > 10 { foo() }, | ||
77 | // _ => (), | ||
78 | // } | ||
79 | // } | ||
80 | // ``` | ||
81 | // -> | ||
82 | // ``` | ||
83 | // enum Action { Move { distance: u32 }, Stop } | ||
84 | // | ||
85 | // fn handle(action: Action) { | ||
86 | // match action { | ||
87 | // Action::Move { distance } if distance > 10 => foo(), | ||
88 | // _ => (), | ||
89 | // } | ||
90 | // } | ||
91 | // ``` | ||
92 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
93 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; | ||
47 | let last_match_pat = match_arm.pats().last()?; | 94 | let last_match_pat = match_arm.pats().last()?; |
48 | 95 | ||
49 | let arm_body = match_arm.expr()?; | 96 | let arm_body = match_arm.expr()?; |
@@ -62,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) | |||
62 | 109 | ||
63 | let buf = format!(" if {}", cond.syntax().text()); | 110 | let buf = format!(" if {}", cond.syntax().text()); |
64 | 111 | ||
65 | ctx.add_action( | 112 | ctx.add_assist( |
66 | AssistId("move_arm_cond_to_match_guard"), | 113 | AssistId("move_arm_cond_to_match_guard"), |
67 | "move condition to match guard", | 114 | "move condition to match guard", |
68 | |edit| { | 115 | |edit| { |
@@ -79,8 +126,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) | |||
79 | edit.insert(last_match_pat.syntax().text_range().end(), buf); | 126 | edit.insert(last_match_pat.syntax().text_range().end(), buf); |
80 | edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); | 127 | edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); |
81 | }, | 128 | }, |
82 | ); | 129 | ) |
83 | ctx.build() | ||
84 | } | 130 | } |
85 | 131 | ||
86 | #[cfg(test)] | 132 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index 2d2e31e51..58f7157ae 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs | |||
@@ -1,17 +1,29 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; | 2 | use ra_syntax::{ |
3 | SyntaxKind::{RAW_STRING, STRING}, | ||
4 | TextRange, TextUnit, | ||
5 | }; | ||
5 | use rustc_lexer; | 6 | use rustc_lexer; |
6 | 7 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
8 | 9 | ||
9 | pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | // Assist: make_raw_string |
10 | let literal = ctx.node_at_offset::<Literal>()?; | 11 | // |
11 | if literal.token().kind() != ra_syntax::SyntaxKind::STRING { | 12 | // Adds `r#` to a plain string literal. |
12 | return None; | 13 | // |
13 | } | 14 | // ``` |
14 | let token = literal.token(); | 15 | // fn main() { |
16 | // "Hello,<|> World!"; | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // fn main() { | ||
22 | // r#"Hello, World!"#; | ||
23 | // } | ||
24 | // ``` | ||
25 | pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
26 | let token = ctx.find_token_at_offset(STRING)?; | ||
15 | let text = token.text().as_str(); | 27 | let text = token.text().as_str(); |
16 | let usual_string_range = find_usual_string_range(text)?; | 28 | let usual_string_range = find_usual_string_range(text)?; |
17 | let start_of_inside = usual_string_range.start().to_usize() + 1; | 29 | let start_of_inside = usual_string_range.start().to_usize() + 1; |
@@ -29,97 +41,131 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
29 | if error.is_err() { | 41 | if error.is_err() { |
30 | return None; | 42 | return None; |
31 | } | 43 | } |
32 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { | 44 | ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| { |
33 | edit.target(literal.syntax().text_range()); | 45 | edit.target(token.text_range()); |
34 | let max_hash_streak = count_hashes(&unescaped); | 46 | let max_hash_streak = count_hashes(&unescaped); |
35 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 47 | let mut hashes = String::with_capacity(max_hash_streak + 1); |
36 | for _ in 0..hashes.capacity() { | 48 | for _ in 0..hashes.capacity() { |
37 | hashes.push('#'); | 49 | hashes.push('#'); |
38 | } | 50 | } |
39 | edit.replace( | 51 | edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes)); |
40 | literal.syntax().text_range(), | 52 | }) |
41 | format!("r{}\"{}\"{}", hashes, unescaped, hashes), | ||
42 | ); | ||
43 | }); | ||
44 | ctx.build() | ||
45 | } | ||
46 | |||
47 | fn count_hashes(s: &str) -> usize { | ||
48 | let mut max_hash_streak = 0usize; | ||
49 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | ||
50 | let (_, sub) = s.split_at(idx + 1); | ||
51 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | ||
52 | if nb_hash > max_hash_streak { | ||
53 | max_hash_streak = nb_hash; | ||
54 | } | ||
55 | } | ||
56 | max_hash_streak | ||
57 | } | ||
58 | |||
59 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
60 | Some(TextRange::from_to( | ||
61 | TextUnit::from(s.find('"')? as u32), | ||
62 | TextUnit::from(s.rfind('"')? as u32), | ||
63 | )) | ||
64 | } | 53 | } |
65 | 54 | ||
66 | pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 55 | // Assist: make_usual_string |
67 | let literal = ctx.node_at_offset::<Literal>()?; | 56 | // |
68 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | 57 | // Turns a raw string into a plain string. |
69 | return None; | 58 | // |
70 | } | 59 | // ``` |
71 | let token = literal.token(); | 60 | // fn main() { |
61 | // r#"Hello,<|> "World!""#; | ||
62 | // } | ||
63 | // ``` | ||
64 | // -> | ||
65 | // ``` | ||
66 | // fn main() { | ||
67 | // "Hello, \"World!\""; | ||
68 | // } | ||
69 | // ``` | ||
70 | pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
71 | let token = ctx.find_token_at_offset(RAW_STRING)?; | ||
72 | let text = token.text().as_str(); | 72 | let text = token.text().as_str(); |
73 | let usual_string_range = find_usual_string_range(text)?; | 73 | let usual_string_range = find_usual_string_range(text)?; |
74 | ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { | 74 | ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| { |
75 | edit.target(literal.syntax().text_range()); | 75 | edit.target(token.text_range()); |
76 | // parse inside string to escape `"` | 76 | // parse inside string to escape `"` |
77 | let start_of_inside = usual_string_range.start().to_usize() + 1; | 77 | let start_of_inside = usual_string_range.start().to_usize() + 1; |
78 | let end_of_inside = usual_string_range.end().to_usize(); | 78 | let end_of_inside = usual_string_range.end().to_usize(); |
79 | let inside_str = &text[start_of_inside..end_of_inside]; | 79 | let inside_str = &text[start_of_inside..end_of_inside]; |
80 | let escaped = inside_str.escape_default().to_string(); | 80 | let escaped = inside_str.escape_default().to_string(); |
81 | edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); | 81 | edit.replace(token.text_range(), format!("\"{}\"", escaped)); |
82 | }); | 82 | }) |
83 | ctx.build() | ||
84 | } | 83 | } |
85 | 84 | ||
86 | pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 85 | // Assist: add_hash |
87 | let literal = ctx.node_at_offset::<Literal>()?; | 86 | // |
88 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | 87 | // Adds a hash to a raw string literal. |
89 | return None; | 88 | // |
90 | } | 89 | // ``` |
91 | ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { | 90 | // fn main() { |
92 | edit.target(literal.syntax().text_range()); | 91 | // r#"Hello,<|> World!"#; |
93 | edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); | 92 | // } |
94 | edit.insert(literal.syntax().text_range().end(), "#"); | 93 | // ``` |
95 | }); | 94 | // -> |
96 | ctx.build() | 95 | // ``` |
96 | // fn main() { | ||
97 | // r##"Hello, World!"##; | ||
98 | // } | ||
99 | // ``` | ||
100 | pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
101 | let token = ctx.find_token_at_offset(RAW_STRING)?; | ||
102 | ctx.add_assist(AssistId("add_hash"), "add hash to raw string", |edit| { | ||
103 | edit.target(token.text_range()); | ||
104 | edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#"); | ||
105 | edit.insert(token.text_range().end(), "#"); | ||
106 | }) | ||
97 | } | 107 | } |
98 | 108 | ||
99 | pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 109 | // Assist: remove_hash |
100 | let literal = ctx.node_at_offset::<Literal>()?; | 110 | // |
101 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | 111 | // Removes a hash from a raw string literal. |
102 | return None; | 112 | // |
103 | } | 113 | // ``` |
104 | let token = literal.token(); | 114 | // fn main() { |
115 | // r#"Hello,<|> World!"#; | ||
116 | // } | ||
117 | // ``` | ||
118 | // -> | ||
119 | // ``` | ||
120 | // fn main() { | ||
121 | // r"Hello, World!"; | ||
122 | // } | ||
123 | // ``` | ||
124 | pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
125 | let token = ctx.find_token_at_offset(RAW_STRING)?; | ||
105 | let text = token.text().as_str(); | 126 | let text = token.text().as_str(); |
106 | if text.starts_with("r\"") { | 127 | if text.starts_with("r\"") { |
107 | // no hash to remove | 128 | // no hash to remove |
108 | return None; | 129 | return None; |
109 | } | 130 | } |
110 | ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { | 131 | ctx.add_assist(AssistId("remove_hash"), "remove hash from raw string", |edit| { |
111 | edit.target(literal.syntax().text_range()); | 132 | edit.target(token.text_range()); |
112 | let result = &text[2..text.len() - 1]; | 133 | let result = &text[2..text.len() - 1]; |
113 | let result = if result.starts_with("\"") { | 134 | let result = if result.starts_with('\"') { |
114 | // no more hash, escape | 135 | // no more hash, escape |
115 | let internal_str = &result[1..result.len() - 1]; | 136 | let internal_str = &result[1..result.len() - 1]; |
116 | format!("\"{}\"", internal_str.escape_default().to_string()) | 137 | format!("\"{}\"", internal_str.escape_default().to_string()) |
117 | } else { | 138 | } else { |
118 | result.to_owned() | 139 | result.to_owned() |
119 | }; | 140 | }; |
120 | edit.replace(literal.syntax().text_range(), format!("r{}", result)); | 141 | edit.replace(token.text_range(), format!("r{}", result)); |
121 | }); | 142 | }) |
122 | ctx.build() | 143 | } |
144 | |||
145 | fn count_hashes(s: &str) -> usize { | ||
146 | let mut max_hash_streak = 0usize; | ||
147 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | ||
148 | let (_, sub) = s.split_at(idx + 1); | ||
149 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | ||
150 | if nb_hash > max_hash_streak { | ||
151 | max_hash_streak = nb_hash; | ||
152 | } | ||
153 | } | ||
154 | max_hash_streak | ||
155 | } | ||
156 | |||
157 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
158 | let left_quote = s.find('"')?; | ||
159 | let right_quote = s.rfind('"')?; | ||
160 | if left_quote == right_quote { | ||
161 | // `s` only contains one quote | ||
162 | None | ||
163 | } else { | ||
164 | Some(TextRange::from_to( | ||
165 | TextUnit::from(left_quote as u32), | ||
166 | TextUnit::from(right_quote as u32), | ||
167 | )) | ||
168 | } | ||
123 | } | 169 | } |
124 | 170 | ||
125 | #[cfg(test)] | 171 | #[cfg(test)] |
@@ -159,6 +205,23 @@ string"#; | |||
159 | } | 205 | } |
160 | 206 | ||
161 | #[test] | 207 | #[test] |
208 | fn make_raw_string_works_inside_macros() { | ||
209 | check_assist( | ||
210 | make_raw_string, | ||
211 | r#" | ||
212 | fn f() { | ||
213 | format!(<|>"x = {}", 92) | ||
214 | } | ||
215 | "#, | ||
216 | r##" | ||
217 | fn f() { | ||
218 | format!(<|>r#"x = {}"#, 92) | ||
219 | } | ||
220 | "##, | ||
221 | ) | ||
222 | } | ||
223 | |||
224 | #[test] | ||
162 | fn make_raw_string_hashes_inside_works() { | 225 | fn make_raw_string_hashes_inside_works() { |
163 | check_assist( | 226 | check_assist( |
164 | make_raw_string, | 227 | make_raw_string, |
@@ -212,6 +275,30 @@ string"###; | |||
212 | } | 275 | } |
213 | 276 | ||
214 | #[test] | 277 | #[test] |
278 | fn make_raw_string_not_works_on_partial_string() { | ||
279 | check_assist_not_applicable( | ||
280 | make_raw_string, | ||
281 | r#" | ||
282 | fn f() { | ||
283 | let s = "foo<|> | ||
284 | } | ||
285 | "#, | ||
286 | ) | ||
287 | } | ||
288 | |||
289 | #[test] | ||
290 | fn make_usual_string_not_works_on_partial_string() { | ||
291 | check_assist_not_applicable( | ||
292 | make_usual_string, | ||
293 | r#" | ||
294 | fn main() { | ||
295 | let s = r#"bar<|> | ||
296 | } | ||
297 | "#, | ||
298 | ) | ||
299 | } | ||
300 | |||
301 | #[test] | ||
215 | fn add_hash_target() { | 302 | fn add_hash_target() { |
216 | check_assist_target( | 303 | check_assist_target( |
217 | add_hash, | 304 | add_hash, |
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs index 1a7e2b305..aedf8747f 100644 --- a/crates/ra_assists/src/assists/remove_dbg.rs +++ b/crates/ra_assists/src/assists/remove_dbg.rs | |||
@@ -1,14 +1,28 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{Assist, AssistCtx, AssistId}; | ||
4 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
5 | use ra_syntax::{ | 2 | use ra_syntax::{ |
6 | ast::{self, AstNode}, | 3 | ast::{self, AstNode}, |
7 | TextUnit, T, | 4 | TextUnit, T, |
8 | }; | 5 | }; |
9 | 6 | ||
10 | pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 7 | use crate::{Assist, AssistCtx, AssistId}; |
11 | let macro_call = ctx.node_at_offset::<ast::MacroCall>()?; | 8 | |
9 | // Assist: remove_dbg | ||
10 | // | ||
11 | // Removes `dbg!()` macro call. | ||
12 | // | ||
13 | // ``` | ||
14 | // fn main() { | ||
15 | // <|>dbg!(92); | ||
16 | // } | ||
17 | // ``` | ||
18 | // -> | ||
19 | // ``` | ||
20 | // fn main() { | ||
21 | // 92; | ||
22 | // } | ||
23 | // ``` | ||
24 | pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
25 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | ||
12 | 26 | ||
13 | if !is_valid_macrocall(¯o_call, "dbg")? { | 27 | if !is_valid_macrocall(¯o_call, "dbg")? { |
14 | return None; | 28 | return None; |
@@ -44,13 +58,11 @@ pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
44 | text.slice(without_parens).to_string() | 58 | text.slice(without_parens).to_string() |
45 | }; | 59 | }; |
46 | 60 | ||
47 | ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { | 61 | ctx.add_assist(AssistId("remove_dbg"), "remove dbg!()", |edit| { |
48 | edit.target(macro_call.syntax().text_range()); | 62 | edit.target(macro_call.syntax().text_range()); |
49 | edit.replace(macro_range, macro_content); | 63 | edit.replace(macro_range, macro_content); |
50 | edit.set_cursor(cursor_pos); | 64 | edit.set_cursor(cursor_pos); |
51 | }); | 65 | }) |
52 | |||
53 | ctx.build() | ||
54 | } | 66 | } |
55 | 67 | ||
56 | /// Verifies that the given macro_call actually matches the given name | 68 | /// Verifies that the given macro_call actually matches the given name |
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 749ff338a..dff84d865 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,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use format_buf::format; | 1 | use format_buf::format; |
4 | use hir::db::HirDatabase; | 2 | use hir::db::HirDatabase; |
5 | use ra_fmt::extract_trivial_expression; | 3 | use ra_fmt::extract_trivial_expression; |
@@ -7,8 +5,34 @@ use ra_syntax::{ast, AstNode}; | |||
7 | 5 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 7 | ||
10 | pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 8 | // Assist: replace_if_let_with_match |
11 | let if_expr: ast::IfExpr = ctx.node_at_offset()?; | 9 | // |
10 | // Replaces `if let` with an else branch with a `match` expression. | ||
11 | // | ||
12 | // ``` | ||
13 | // enum Action { Move { distance: u32 }, Stop } | ||
14 | // | ||
15 | // fn handle(action: Action) { | ||
16 | // <|>if let Action::Move { distance } = action { | ||
17 | // foo(distance) | ||
18 | // } else { | ||
19 | // bar() | ||
20 | // } | ||
21 | // } | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // enum Action { Move { distance: u32 }, Stop } | ||
26 | // | ||
27 | // fn handle(action: Action) { | ||
28 | // match action { | ||
29 | // Action::Move { distance } => foo(distance), | ||
30 | // _ => bar(), | ||
31 | // } | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
35 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | ||
12 | let cond = if_expr.condition()?; | 36 | let cond = if_expr.condition()?; |
13 | let pat = cond.pat()?; | 37 | let pat = cond.pat()?; |
14 | let expr = cond.expr()?; | 38 | let expr = cond.expr()?; |
@@ -18,14 +42,12 @@ pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
18 | ast::ElseBranch::IfExpr(_) => return None, | 42 | ast::ElseBranch::IfExpr(_) => return None, |
19 | }; | 43 | }; |
20 | 44 | ||
21 | ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| { | 45 | ctx.add_assist(AssistId("replace_if_let_with_match"), "replace with match", |edit| { |
22 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | 46 | let match_expr = build_match_expr(expr, pat, then_block, else_block); |
23 | edit.target(if_expr.syntax().text_range()); | 47 | edit.target(if_expr.syntax().text_range()); |
24 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | 48 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); |
25 | edit.set_cursor(if_expr.syntax().text_range().start()) | 49 | edit.set_cursor(if_expr.syntax().text_range().start()) |
26 | }); | 50 | }) |
27 | |||
28 | ctx.build() | ||
29 | } | 51 | } |
30 | 52 | ||
31 | fn build_match_expr( | 53 | fn build_match_expr( |
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs index fe3e64af5..5f8d6b0be 100644 --- a/crates/ra_assists/src/assists/split_import.rs +++ b/crates/ra_assists/src/assists/split_import.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::iter::successors; | 1 | use std::iter::successors; |
4 | 2 | ||
5 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
@@ -7,8 +5,19 @@ use ra_syntax::{ast, AstNode, TextUnit, T}; | |||
7 | 5 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 7 | ||
10 | pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 8 | // Assist: split_import |
11 | let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?; | 9 | // |
10 | // Wraps the tail of import into braces. | ||
11 | // | ||
12 | // ``` | ||
13 | // use std::<|>collections::HashMap; | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // use std::{collections::HashMap}; | ||
18 | // ``` | ||
19 | pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
20 | let colon_colon = ctx.find_token_at_offset(T![::])?; | ||
12 | let path = ast::Path::cast(colon_colon.parent())?; | 21 | let path = ast::Path::cast(colon_colon.parent())?; |
13 | let top_path = successors(Some(path), |it| it.parent_path()).last()?; | 22 | let top_path = successors(Some(path), |it| it.parent_path()).last()?; |
14 | 23 | ||
@@ -23,14 +32,12 @@ pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis | |||
23 | None => top_path.syntax().text_range().end(), | 32 | None => top_path.syntax().text_range().end(), |
24 | }; | 33 | }; |
25 | 34 | ||
26 | ctx.add_action(AssistId("split_import"), "split import", |edit| { | 35 | ctx.add_assist(AssistId("split_import"), "split import", |edit| { |
27 | edit.target(colon_colon.text_range()); | 36 | edit.target(colon_colon.text_range()); |
28 | edit.insert(l_curly, "{"); | 37 | edit.insert(l_curly, "{"); |
29 | edit.insert(r_curly, "}"); | 38 | edit.insert(r_curly, "}"); |
30 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | 39 | edit.set_cursor(l_curly + TextUnit::of_str("{")); |
31 | }); | 40 | }) |
32 | |||
33 | ctx.build() | ||
34 | } | 41 | } |
35 | 42 | ||
36 | #[cfg(test)] | 43 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs new file mode 100644 index 000000000..6e1e3de84 --- /dev/null +++ b/crates/ra_assists/src/doc_tests.rs | |||
@@ -0,0 +1,34 @@ | |||
1 | //! Each assist definition has a special comment, which specifies docs and | ||
2 | //! example. | ||
3 | //! | ||
4 | //! We collect all the example and write the as tests in this module. | ||
5 | |||
6 | mod generated; | ||
7 | |||
8 | use hir::mock::MockDatabase; | ||
9 | use ra_db::FileRange; | ||
10 | use test_utils::{assert_eq_text, extract_range_or_offset}; | ||
11 | |||
12 | fn check(assist_id: &str, before: &str, after: &str) { | ||
13 | let (selection, before) = extract_range_or_offset(before); | ||
14 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
15 | let frange = FileRange { file_id, range: selection.into() }; | ||
16 | |||
17 | let (_assist_id, action) = crate::assists(&db, frange) | ||
18 | .into_iter() | ||
19 | .find(|(id, _)| id.id.0 == assist_id) | ||
20 | .unwrap_or_else(|| { | ||
21 | panic!( | ||
22 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | ||
23 | assist_id, | ||
24 | crate::assists(&db, frange) | ||
25 | .into_iter() | ||
26 | .map(|(id, _)| id.id.0) | ||
27 | .collect::<Vec<_>>() | ||
28 | .join(", ") | ||
29 | ) | ||
30 | }); | ||
31 | |||
32 | let actual = action.edit.apply(&before); | ||
33 | assert_eq_text!(after, &actual); | ||
34 | } | ||
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs new file mode 100644 index 000000000..1bee76f59 --- /dev/null +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -0,0 +1,526 @@ | |||
1 | //! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` | ||
2 | |||
3 | use super::check; | ||
4 | |||
5 | #[test] | ||
6 | fn doctest_add_derive() { | ||
7 | check( | ||
8 | "add_derive", | ||
9 | r#####" | ||
10 | struct Point { | ||
11 | x: u32, | ||
12 | y: u32,<|> | ||
13 | } | ||
14 | "#####, | ||
15 | r#####" | ||
16 | #[derive()] | ||
17 | struct Point { | ||
18 | x: u32, | ||
19 | y: u32, | ||
20 | } | ||
21 | "#####, | ||
22 | ) | ||
23 | } | ||
24 | |||
25 | #[test] | ||
26 | fn doctest_add_explicit_type() { | ||
27 | check( | ||
28 | "add_explicit_type", | ||
29 | r#####" | ||
30 | fn main() { | ||
31 | let x<|> = 92; | ||
32 | } | ||
33 | "#####, | ||
34 | r#####" | ||
35 | fn main() { | ||
36 | let x: i32 = 92; | ||
37 | } | ||
38 | "#####, | ||
39 | ) | ||
40 | } | ||
41 | |||
42 | #[test] | ||
43 | fn doctest_add_hash() { | ||
44 | check( | ||
45 | "add_hash", | ||
46 | r#####" | ||
47 | fn main() { | ||
48 | r#"Hello,<|> World!"#; | ||
49 | } | ||
50 | "#####, | ||
51 | r#####" | ||
52 | fn main() { | ||
53 | r##"Hello, World!"##; | ||
54 | } | ||
55 | "#####, | ||
56 | ) | ||
57 | } | ||
58 | |||
59 | #[test] | ||
60 | fn doctest_add_impl() { | ||
61 | check( | ||
62 | "add_impl", | ||
63 | r#####" | ||
64 | struct Ctx<T: Clone> { | ||
65 | data: T,<|> | ||
66 | } | ||
67 | "#####, | ||
68 | r#####" | ||
69 | struct Ctx<T: Clone> { | ||
70 | data: T, | ||
71 | } | ||
72 | |||
73 | impl<T: Clone> Ctx<T> { | ||
74 | |||
75 | } | ||
76 | "#####, | ||
77 | ) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn doctest_add_impl_default_members() { | ||
82 | check( | ||
83 | "add_impl_default_members", | ||
84 | r#####" | ||
85 | trait T { | ||
86 | Type X; | ||
87 | fn foo(&self); | ||
88 | fn bar(&self) {} | ||
89 | } | ||
90 | |||
91 | impl T for () { | ||
92 | Type X = (); | ||
93 | fn foo(&self) {}<|> | ||
94 | |||
95 | } | ||
96 | "#####, | ||
97 | r#####" | ||
98 | trait T { | ||
99 | Type X; | ||
100 | fn foo(&self); | ||
101 | fn bar(&self) {} | ||
102 | } | ||
103 | |||
104 | impl T for () { | ||
105 | Type X = (); | ||
106 | fn foo(&self) {} | ||
107 | fn bar(&self) {} | ||
108 | |||
109 | } | ||
110 | "#####, | ||
111 | ) | ||
112 | } | ||
113 | |||
114 | #[test] | ||
115 | fn doctest_add_impl_missing_members() { | ||
116 | check( | ||
117 | "add_impl_missing_members", | ||
118 | r#####" | ||
119 | trait T { | ||
120 | Type X; | ||
121 | fn foo(&self); | ||
122 | fn bar(&self) {} | ||
123 | } | ||
124 | |||
125 | impl T for () {<|> | ||
126 | |||
127 | } | ||
128 | "#####, | ||
129 | r#####" | ||
130 | trait T { | ||
131 | Type X; | ||
132 | fn foo(&self); | ||
133 | fn bar(&self) {} | ||
134 | } | ||
135 | |||
136 | impl T for () { | ||
137 | fn foo(&self) { unimplemented!() } | ||
138 | |||
139 | } | ||
140 | "#####, | ||
141 | ) | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn doctest_add_import() { | ||
146 | check( | ||
147 | "add_import", | ||
148 | r#####" | ||
149 | fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
150 | "#####, | ||
151 | r#####" | ||
152 | use std::collections::HashMap; | ||
153 | |||
154 | fn process(map: HashMap<String, String>) {} | ||
155 | "#####, | ||
156 | ) | ||
157 | } | ||
158 | |||
159 | #[test] | ||
160 | fn doctest_apply_demorgan() { | ||
161 | check( | ||
162 | "apply_demorgan", | ||
163 | r#####" | ||
164 | fn main() { | ||
165 | if x != 4 ||<|> !y {} | ||
166 | } | ||
167 | "#####, | ||
168 | r#####" | ||
169 | fn main() { | ||
170 | if !(x == 4 && y) {} | ||
171 | } | ||
172 | "#####, | ||
173 | ) | ||
174 | } | ||
175 | |||
176 | #[test] | ||
177 | fn doctest_change_visibility() { | ||
178 | check( | ||
179 | "change_visibility", | ||
180 | r#####" | ||
181 | <|>fn frobnicate() {} | ||
182 | "#####, | ||
183 | r#####" | ||
184 | pub(crate) fn frobnicate() {} | ||
185 | "#####, | ||
186 | ) | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn doctest_convert_to_guarded_return() { | ||
191 | check( | ||
192 | "convert_to_guarded_return", | ||
193 | r#####" | ||
194 | fn main() { | ||
195 | <|>if cond { | ||
196 | foo(); | ||
197 | bar(); | ||
198 | } | ||
199 | } | ||
200 | "#####, | ||
201 | r#####" | ||
202 | fn main() { | ||
203 | if !cond { | ||
204 | return; | ||
205 | } | ||
206 | foo(); | ||
207 | bar(); | ||
208 | } | ||
209 | "#####, | ||
210 | ) | ||
211 | } | ||
212 | |||
213 | #[test] | ||
214 | fn doctest_fill_match_arms() { | ||
215 | check( | ||
216 | "fill_match_arms", | ||
217 | r#####" | ||
218 | enum Action { Move { distance: u32 }, Stop } | ||
219 | |||
220 | fn handle(action: Action) { | ||
221 | match action { | ||
222 | <|> | ||
223 | } | ||
224 | } | ||
225 | "#####, | ||
226 | r#####" | ||
227 | enum Action { Move { distance: u32 }, Stop } | ||
228 | |||
229 | fn handle(action: Action) { | ||
230 | match action { | ||
231 | Action::Move { distance } => (), | ||
232 | Action::Stop => (), | ||
233 | } | ||
234 | } | ||
235 | "#####, | ||
236 | ) | ||
237 | } | ||
238 | |||
239 | #[test] | ||
240 | fn doctest_flip_binexpr() { | ||
241 | check( | ||
242 | "flip_binexpr", | ||
243 | r#####" | ||
244 | fn main() { | ||
245 | let _ = 90 +<|> 2; | ||
246 | } | ||
247 | "#####, | ||
248 | r#####" | ||
249 | fn main() { | ||
250 | let _ = 2 + 90; | ||
251 | } | ||
252 | "#####, | ||
253 | ) | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn doctest_flip_comma() { | ||
258 | check( | ||
259 | "flip_comma", | ||
260 | r#####" | ||
261 | fn main() { | ||
262 | ((1, 2),<|> (3, 4)); | ||
263 | } | ||
264 | "#####, | ||
265 | r#####" | ||
266 | fn main() { | ||
267 | ((3, 4), (1, 2)); | ||
268 | } | ||
269 | "#####, | ||
270 | ) | ||
271 | } | ||
272 | |||
273 | #[test] | ||
274 | fn doctest_flip_trait_bound() { | ||
275 | check( | ||
276 | "flip_trait_bound", | ||
277 | r#####" | ||
278 | fn foo<T: Clone +<|> Copy>() { } | ||
279 | "#####, | ||
280 | r#####" | ||
281 | fn foo<T: Copy + Clone>() { } | ||
282 | "#####, | ||
283 | ) | ||
284 | } | ||
285 | |||
286 | #[test] | ||
287 | fn doctest_inline_local_variable() { | ||
288 | check( | ||
289 | "inline_local_variable", | ||
290 | r#####" | ||
291 | fn main() { | ||
292 | let x<|> = 1 + 2; | ||
293 | x * 4; | ||
294 | } | ||
295 | "#####, | ||
296 | r#####" | ||
297 | fn main() { | ||
298 | (1 + 2) * 4; | ||
299 | } | ||
300 | "#####, | ||
301 | ) | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn doctest_introduce_variable() { | ||
306 | check( | ||
307 | "introduce_variable", | ||
308 | r#####" | ||
309 | fn main() { | ||
310 | <|>(1 + 2)<|> * 4; | ||
311 | } | ||
312 | "#####, | ||
313 | r#####" | ||
314 | fn main() { | ||
315 | let var_name = (1 + 2); | ||
316 | var_name * 4; | ||
317 | } | ||
318 | "#####, | ||
319 | ) | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn doctest_make_raw_string() { | ||
324 | check( | ||
325 | "make_raw_string", | ||
326 | r#####" | ||
327 | fn main() { | ||
328 | "Hello,<|> World!"; | ||
329 | } | ||
330 | "#####, | ||
331 | r#####" | ||
332 | fn main() { | ||
333 | r#"Hello, World!"#; | ||
334 | } | ||
335 | "#####, | ||
336 | ) | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn doctest_make_usual_string() { | ||
341 | check( | ||
342 | "make_usual_string", | ||
343 | r#####" | ||
344 | fn main() { | ||
345 | r#"Hello,<|> "World!""#; | ||
346 | } | ||
347 | "#####, | ||
348 | r#####" | ||
349 | fn main() { | ||
350 | "Hello, \"World!\""; | ||
351 | } | ||
352 | "#####, | ||
353 | ) | ||
354 | } | ||
355 | |||
356 | #[test] | ||
357 | fn doctest_merge_match_arms() { | ||
358 | check( | ||
359 | "merge_match_arms", | ||
360 | r#####" | ||
361 | enum Action { Move { distance: u32 }, Stop } | ||
362 | |||
363 | fn handle(action: Action) { | ||
364 | match action { | ||
365 | <|>Action::Move(..) => foo(), | ||
366 | Action::Stop => foo(), | ||
367 | } | ||
368 | } | ||
369 | "#####, | ||
370 | r#####" | ||
371 | enum Action { Move { distance: u32 }, Stop } | ||
372 | |||
373 | fn handle(action: Action) { | ||
374 | match action { | ||
375 | Action::Move(..) | Action::Stop => foo(), | ||
376 | } | ||
377 | } | ||
378 | "#####, | ||
379 | ) | ||
380 | } | ||
381 | |||
382 | #[test] | ||
383 | fn doctest_move_arm_cond_to_match_guard() { | ||
384 | check( | ||
385 | "move_arm_cond_to_match_guard", | ||
386 | r#####" | ||
387 | enum Action { Move { distance: u32 }, Stop } | ||
388 | |||
389 | fn handle(action: Action) { | ||
390 | match action { | ||
391 | Action::Move { distance } => <|>if distance > 10 { foo() }, | ||
392 | _ => (), | ||
393 | } | ||
394 | } | ||
395 | "#####, | ||
396 | r#####" | ||
397 | enum Action { Move { distance: u32 }, Stop } | ||
398 | |||
399 | fn handle(action: Action) { | ||
400 | match action { | ||
401 | Action::Move { distance } if distance > 10 => foo(), | ||
402 | _ => (), | ||
403 | } | ||
404 | } | ||
405 | "#####, | ||
406 | ) | ||
407 | } | ||
408 | |||
409 | #[test] | ||
410 | fn doctest_move_bounds_to_where_clause() { | ||
411 | check( | ||
412 | "move_bounds_to_where_clause", | ||
413 | r#####" | ||
414 | fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { | ||
415 | f(x) | ||
416 | } | ||
417 | "#####, | ||
418 | r#####" | ||
419 | fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U { | ||
420 | f(x) | ||
421 | } | ||
422 | "#####, | ||
423 | ) | ||
424 | } | ||
425 | |||
426 | #[test] | ||
427 | fn doctest_move_guard_to_arm_body() { | ||
428 | check( | ||
429 | "move_guard_to_arm_body", | ||
430 | r#####" | ||
431 | enum Action { Move { distance: u32 }, Stop } | ||
432 | |||
433 | fn handle(action: Action) { | ||
434 | match action { | ||
435 | Action::Move { distance } <|>if distance > 10 => foo(), | ||
436 | _ => (), | ||
437 | } | ||
438 | } | ||
439 | "#####, | ||
440 | r#####" | ||
441 | enum Action { Move { distance: u32 }, Stop } | ||
442 | |||
443 | fn handle(action: Action) { | ||
444 | match action { | ||
445 | Action::Move { distance } => if distance > 10 { foo() }, | ||
446 | _ => (), | ||
447 | } | ||
448 | } | ||
449 | "#####, | ||
450 | ) | ||
451 | } | ||
452 | |||
453 | #[test] | ||
454 | fn doctest_remove_dbg() { | ||
455 | check( | ||
456 | "remove_dbg", | ||
457 | r#####" | ||
458 | fn main() { | ||
459 | <|>dbg!(92); | ||
460 | } | ||
461 | "#####, | ||
462 | r#####" | ||
463 | fn main() { | ||
464 | 92; | ||
465 | } | ||
466 | "#####, | ||
467 | ) | ||
468 | } | ||
469 | |||
470 | #[test] | ||
471 | fn doctest_remove_hash() { | ||
472 | check( | ||
473 | "remove_hash", | ||
474 | r#####" | ||
475 | fn main() { | ||
476 | r#"Hello,<|> World!"#; | ||
477 | } | ||
478 | "#####, | ||
479 | r#####" | ||
480 | fn main() { | ||
481 | r"Hello, World!"; | ||
482 | } | ||
483 | "#####, | ||
484 | ) | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn doctest_replace_if_let_with_match() { | ||
489 | check( | ||
490 | "replace_if_let_with_match", | ||
491 | r#####" | ||
492 | enum Action { Move { distance: u32 }, Stop } | ||
493 | |||
494 | fn handle(action: Action) { | ||
495 | <|>if let Action::Move { distance } = action { | ||
496 | foo(distance) | ||
497 | } else { | ||
498 | bar() | ||
499 | } | ||
500 | } | ||
501 | "#####, | ||
502 | r#####" | ||
503 | enum Action { Move { distance: u32 }, Stop } | ||
504 | |||
505 | fn handle(action: Action) { | ||
506 | match action { | ||
507 | Action::Move { distance } => foo(distance), | ||
508 | _ => bar(), | ||
509 | } | ||
510 | } | ||
511 | "#####, | ||
512 | ) | ||
513 | } | ||
514 | |||
515 | #[test] | ||
516 | fn doctest_split_import() { | ||
517 | check( | ||
518 | "split_import", | ||
519 | r#####" | ||
520 | use std::<|>collections::HashMap; | ||
521 | "#####, | ||
522 | r#####" | ||
523 | use std::{collections::HashMap}; | ||
524 | "#####, | ||
525 | ) | ||
526 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index ab77b46a9..38599d4f1 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -7,15 +7,16 @@ | |||
7 | 7 | ||
8 | mod assist_ctx; | 8 | mod assist_ctx; |
9 | mod marks; | 9 | mod marks; |
10 | #[cfg(test)] | ||
11 | mod doc_tests; | ||
10 | 12 | ||
11 | use hir::db::HirDatabase; | 13 | use hir::db::HirDatabase; |
12 | use itertools::Itertools; | ||
13 | use ra_db::FileRange; | 14 | use ra_db::FileRange; |
14 | use ra_syntax::{TextRange, TextUnit}; | 15 | use ra_syntax::{TextRange, TextUnit}; |
15 | use ra_text_edit::TextEdit; | 16 | use ra_text_edit::TextEdit; |
16 | 17 | ||
17 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; | 18 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; |
18 | pub use crate::assists::auto_import::auto_import_text_edit; | 19 | pub use crate::assists::add_import::auto_import_text_edit; |
19 | 20 | ||
20 | /// Unique identifier of the assist, should not be shown to the user | 21 | /// Unique identifier of the assist, should not be shown to the user |
21 | /// directly. | 22 | /// directly. |
@@ -36,7 +37,7 @@ pub struct AssistAction { | |||
36 | pub target: Option<TextRange>, | 37 | pub target: Option<TextRange>, |
37 | } | 38 | } |
38 | 39 | ||
39 | /// Return all the assists eapplicable at the given position. | 40 | /// Return all the assists applicable at the given position. |
40 | /// | 41 | /// |
41 | /// Assists are returned in the "unresolved" state, that is only labels are | 42 | /// Assists are returned in the "unresolved" state, that is only labels are |
42 | /// returned, without actual edits. | 43 | /// returned, without actual edits. |
@@ -49,10 +50,10 @@ where | |||
49 | .iter() | 50 | .iter() |
50 | .filter_map(|f| f(ctx.clone())) | 51 | .filter_map(|f| f(ctx.clone())) |
51 | .map(|a| match a { | 52 | .map(|a| match a { |
52 | Assist::Unresolved(labels) => labels, | 53 | Assist::Unresolved { label } => label, |
53 | Assist::Resolved(..) => unreachable!(), | 54 | Assist::Resolved { .. } => unreachable!(), |
54 | }) | 55 | }) |
55 | .concat() | 56 | .collect() |
56 | }) | 57 | }) |
57 | } | 58 | } |
58 | 59 | ||
@@ -71,10 +72,10 @@ where | |||
71 | .iter() | 72 | .iter() |
72 | .filter_map(|f| f(ctx.clone())) | 73 | .filter_map(|f| f(ctx.clone())) |
73 | .map(|a| match a { | 74 | .map(|a| match a { |
74 | Assist::Resolved(labels_actions) => labels_actions, | 75 | Assist::Resolved { label, action } => (label, action), |
75 | Assist::Unresolved(..) => unreachable!(), | 76 | Assist::Unresolved { .. } => unreachable!(), |
76 | }) | 77 | }) |
77 | .concat(); | 78 | .collect::<Vec<_>>(); |
78 | a.sort_by(|a, b| match (a.1.target, b.1.target) { | 79 | a.sort_by(|a, b| match (a.1.target, b.1.target) { |
79 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | 80 | (Some(a), Some(b)) => a.len().cmp(&b.len()), |
80 | (Some(_), None) => Ordering::Less, | 81 | (Some(_), None) => Ordering::Less, |
@@ -95,6 +96,7 @@ mod assists { | |||
95 | mod apply_demorgan; | 96 | mod apply_demorgan; |
96 | mod flip_comma; | 97 | mod flip_comma; |
97 | mod flip_binexpr; | 98 | mod flip_binexpr; |
99 | mod flip_trait_bound; | ||
98 | mod change_visibility; | 100 | mod change_visibility; |
99 | mod fill_match_arms; | 101 | mod fill_match_arms; |
100 | mod merge_match_arms; | 102 | mod merge_match_arms; |
@@ -104,7 +106,7 @@ mod assists { | |||
104 | mod replace_if_let_with_match; | 106 | mod replace_if_let_with_match; |
105 | mod split_import; | 107 | mod split_import; |
106 | mod remove_dbg; | 108 | mod remove_dbg; |
107 | pub(crate) mod auto_import; | 109 | pub(crate) mod add_import; |
108 | mod add_missing_impl_members; | 110 | mod add_missing_impl_members; |
109 | mod move_guard; | 111 | mod move_guard; |
110 | mod move_bounds; | 112 | mod move_bounds; |
@@ -121,11 +123,12 @@ mod assists { | |||
121 | merge_match_arms::merge_match_arms, | 123 | merge_match_arms::merge_match_arms, |
122 | flip_comma::flip_comma, | 124 | flip_comma::flip_comma, |
123 | flip_binexpr::flip_binexpr, | 125 | flip_binexpr::flip_binexpr, |
126 | flip_trait_bound::flip_trait_bound, | ||
124 | introduce_variable::introduce_variable, | 127 | introduce_variable::introduce_variable, |
125 | replace_if_let_with_match::replace_if_let_with_match, | 128 | replace_if_let_with_match::replace_if_let_with_match, |
126 | split_import::split_import, | 129 | split_import::split_import, |
127 | remove_dbg::remove_dbg, | 130 | remove_dbg::remove_dbg, |
128 | auto_import::auto_import, | 131 | add_import::add_import, |
129 | add_missing_impl_members::add_missing_impl_members, | 132 | add_missing_impl_members::add_missing_impl_members, |
130 | add_missing_impl_members::add_missing_default_members, | 133 | add_missing_impl_members::add_missing_default_members, |
131 | inline_local_variable::inline_local_varialbe, | 134 | inline_local_variable::inline_local_varialbe, |
@@ -155,51 +158,17 @@ mod helpers { | |||
155 | before: &str, | 158 | before: &str, |
156 | after: &str, | 159 | after: &str, |
157 | ) { | 160 | ) { |
158 | check_assist_nth_action(assist, before, after, 0) | ||
159 | } | ||
160 | |||
161 | pub(crate) fn check_assist_range( | ||
162 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
163 | before: &str, | ||
164 | after: &str, | ||
165 | ) { | ||
166 | check_assist_range_nth_action(assist, before, after, 0) | ||
167 | } | ||
168 | |||
169 | pub(crate) fn check_assist_target( | ||
170 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
171 | before: &str, | ||
172 | target: &str, | ||
173 | ) { | ||
174 | check_assist_target_nth_action(assist, before, target, 0) | ||
175 | } | ||
176 | |||
177 | pub(crate) fn check_assist_range_target( | ||
178 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
179 | before: &str, | ||
180 | target: &str, | ||
181 | ) { | ||
182 | check_assist_range_target_nth_action(assist, before, target, 0) | ||
183 | } | ||
184 | |||
185 | pub(crate) fn check_assist_nth_action( | ||
186 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
187 | before: &str, | ||
188 | after: &str, | ||
189 | index: usize, | ||
190 | ) { | ||
191 | let (before_cursor_pos, before) = extract_offset(before); | 161 | let (before_cursor_pos, before) = extract_offset(before); |
192 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 162 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
193 | let frange = | 163 | let frange = |
194 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | 164 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; |
195 | let assist = | 165 | let assist = |
196 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 166 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
197 | let labels_actions = match assist { | 167 | let action = match assist { |
198 | Assist::Unresolved(_) => unreachable!(), | 168 | Assist::Unresolved { .. } => unreachable!(), |
199 | Assist::Resolved(labels_actions) => labels_actions, | 169 | Assist::Resolved { action, .. } => action, |
200 | }; | 170 | }; |
201 | 171 | ||
202 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
203 | let actual = action.edit.apply(&before); | 172 | let actual = action.edit.apply(&before); |
204 | let actual_cursor_pos = match action.cursor_position { | 173 | let actual_cursor_pos = match action.cursor_position { |
205 | None => action | 174 | None => action |
@@ -212,23 +181,21 @@ mod helpers { | |||
212 | assert_eq_text!(after, &actual); | 181 | assert_eq_text!(after, &actual); |
213 | } | 182 | } |
214 | 183 | ||
215 | pub(crate) fn check_assist_range_nth_action( | 184 | pub(crate) fn check_assist_range( |
216 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 185 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
217 | before: &str, | 186 | before: &str, |
218 | after: &str, | 187 | after: &str, |
219 | index: usize, | ||
220 | ) { | 188 | ) { |
221 | let (range, before) = extract_range(before); | 189 | let (range, before) = extract_range(before); |
222 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 190 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
223 | let frange = FileRange { file_id, range }; | 191 | let frange = FileRange { file_id, range }; |
224 | let assist = | 192 | let assist = |
225 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 193 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
226 | let labels_actions = match assist { | 194 | let action = match assist { |
227 | Assist::Unresolved(_) => unreachable!(), | 195 | Assist::Unresolved { .. } => unreachable!(), |
228 | Assist::Resolved(labels_actions) => labels_actions, | 196 | Assist::Resolved { action, .. } => action, |
229 | }; | 197 | }; |
230 | 198 | ||
231 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
232 | let mut actual = action.edit.apply(&before); | 199 | let mut actual = action.edit.apply(&before); |
233 | if let Some(pos) = action.cursor_position { | 200 | if let Some(pos) = action.cursor_position { |
234 | actual = add_cursor(&actual, pos); | 201 | actual = add_cursor(&actual, pos); |
@@ -236,11 +203,10 @@ mod helpers { | |||
236 | assert_eq_text!(after, &actual); | 203 | assert_eq_text!(after, &actual); |
237 | } | 204 | } |
238 | 205 | ||
239 | pub(crate) fn check_assist_target_nth_action( | 206 | pub(crate) fn check_assist_target( |
240 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 207 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
241 | before: &str, | 208 | before: &str, |
242 | target: &str, | 209 | target: &str, |
243 | index: usize, | ||
244 | ) { | 210 | ) { |
245 | let (before_cursor_pos, before) = extract_offset(before); | 211 | let (before_cursor_pos, before) = extract_offset(before); |
246 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 212 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
@@ -248,33 +214,30 @@ mod helpers { | |||
248 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | 214 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; |
249 | let assist = | 215 | let assist = |
250 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 216 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
251 | let labels_actions = match assist { | 217 | let action = match assist { |
252 | Assist::Unresolved(_) => unreachable!(), | 218 | Assist::Unresolved { .. } => unreachable!(), |
253 | Assist::Resolved(labels_actions) => labels_actions, | 219 | Assist::Resolved { action, .. } => action, |
254 | }; | 220 | }; |
255 | 221 | ||
256 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
257 | let range = action.target.expect("expected target on action"); | 222 | let range = action.target.expect("expected target on action"); |
258 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | 223 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); |
259 | } | 224 | } |
260 | 225 | ||
261 | pub(crate) fn check_assist_range_target_nth_action( | 226 | pub(crate) fn check_assist_range_target( |
262 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 227 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
263 | before: &str, | 228 | before: &str, |
264 | target: &str, | 229 | target: &str, |
265 | index: usize, | ||
266 | ) { | 230 | ) { |
267 | let (range, before) = extract_range(before); | 231 | let (range, before) = extract_range(before); |
268 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 232 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
269 | let frange = FileRange { file_id, range }; | 233 | let frange = FileRange { file_id, range }; |
270 | let assist = | 234 | let assist = |
271 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 235 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
272 | let labels_actions = match assist { | 236 | let action = match assist { |
273 | Assist::Unresolved(_) => unreachable!(), | 237 | Assist::Unresolved { .. } => unreachable!(), |
274 | Assist::Resolved(labels_actions) => labels_actions, | 238 | Assist::Resolved { action, .. } => action, |
275 | }; | 239 | }; |
276 | 240 | ||
277 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
278 | let range = action.target.expect("expected target on action"); | 241 | let range = action.target.expect("expected target on action"); |
279 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | 242 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); |
280 | } | 243 | } |