diff options
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | crates/ra_assists/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 50 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/inline_local_variable.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_transform.rs | 13 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 8 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 39 | ||||
-rw-r--r-- | crates/ra_hir_def/src/find_path.rs | 12 | ||||
-rw-r--r-- | crates/ra_hir_def/src/path.rs | 11 | ||||
-rw-r--r-- | crates/ra_hir_def/src/path/lower/lower_use.rs | 8 | ||||
-rw-r--r-- | crates/ra_ide/src/assists.rs | 44 | ||||
-rw-r--r-- | crates/ra_lsp_server/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 28 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 81 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 42 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 13 | ||||
-rw-r--r-- | editors/code/src/main.ts | 1 |
17 files changed, 251 insertions, 109 deletions
diff --git a/Cargo.lock b/Cargo.lock index 090454658..10bd94f7b 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -869,8 +869,8 @@ version = "0.1.0" | |||
869 | name = "ra_assists" | 869 | name = "ra_assists" |
870 | version = "0.1.0" | 870 | version = "0.1.0" |
871 | dependencies = [ | 871 | dependencies = [ |
872 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||
872 | "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | 873 | "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
873 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||
874 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | 874 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
875 | "ra_db 0.1.0", | 875 | "ra_db 0.1.0", |
876 | "ra_fmt 0.1.0", | 876 | "ra_fmt 0.1.0", |
@@ -1066,6 +1066,7 @@ name = "ra_lsp_server" | |||
1066 | version = "0.1.0" | 1066 | version = "0.1.0" |
1067 | dependencies = [ | 1067 | dependencies = [ |
1068 | "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1068 | "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1069 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1069 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1070 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1070 | "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1071 | "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1071 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", | 1072 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", |
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 50be8d9bc..0d2109e4e 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -11,7 +11,7 @@ doctest = false | |||
11 | format-buf = "1.0.0" | 11 | format-buf = "1.0.0" |
12 | join_to_string = "0.1.3" | 12 | join_to_string = "0.1.3" |
13 | rustc-hash = "1.0" | 13 | rustc-hash = "1.0" |
14 | itertools = "0.8.0" | 14 | either = "1.5" |
15 | 15 | ||
16 | ra_syntax = { path = "../ra_syntax" } | 16 | ra_syntax = { path = "../ra_syntax" } |
17 | ra_text_edit = { path = "../ra_text_edit" } | 17 | ra_text_edit = { path = "../ra_text_edit" } |
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 1a65b5dc0..9d533fa0c 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. | 1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. |
2 | use either::Either; | ||
2 | use hir::{db::HirDatabase, InFile, SourceAnalyzer}; | 3 | use hir::{db::HirDatabase, InFile, SourceAnalyzer}; |
3 | use ra_db::FileRange; | 4 | use ra_db::FileRange; |
4 | use ra_fmt::{leading_indent, reindent}; | 5 | use ra_fmt::{leading_indent, reindent}; |
@@ -9,12 +10,12 @@ use ra_syntax::{ | |||
9 | }; | 10 | }; |
10 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
11 | 12 | ||
12 | use crate::{AssistAction, AssistId, AssistLabel}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; |
13 | 14 | ||
14 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
15 | pub(crate) enum Assist { | 16 | pub(crate) enum Assist { |
16 | Unresolved { label: AssistLabel }, | 17 | Unresolved { label: AssistLabel }, |
17 | Resolved { label: AssistLabel, action: AssistAction }, | 18 | Resolved { assist: ResolvedAssist }, |
18 | } | 19 | } |
19 | 20 | ||
20 | /// `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. |
@@ -81,18 +82,45 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
81 | self, | 82 | self, |
82 | id: AssistId, | 83 | id: AssistId, |
83 | label: impl Into<String>, | 84 | label: impl Into<String>, |
84 | f: impl FnOnce(&mut AssistBuilder), | 85 | f: impl FnOnce(&mut ActionBuilder), |
85 | ) -> Option<Assist> { | 86 | ) -> Option<Assist> { |
86 | let label = AssistLabel { label: label.into(), id }; | 87 | let label = AssistLabel { label: label.into(), id }; |
87 | assert!(label.label.chars().nth(0).unwrap().is_uppercase()); | 88 | assert!(label.label.chars().nth(0).unwrap().is_uppercase()); |
88 | 89 | ||
89 | let assist = if self.should_compute_edit { | 90 | let assist = if self.should_compute_edit { |
90 | let action = { | 91 | let action = { |
91 | let mut edit = AssistBuilder::default(); | 92 | let mut edit = ActionBuilder::default(); |
92 | f(&mut edit); | 93 | f(&mut edit); |
93 | edit.build() | 94 | edit.build() |
94 | }; | 95 | }; |
95 | Assist::Resolved { label, action } | 96 | Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } } |
97 | } else { | ||
98 | Assist::Unresolved { label } | ||
99 | }; | ||
100 | |||
101 | Some(assist) | ||
102 | } | ||
103 | |||
104 | #[allow(dead_code)] // will be used for auto import assist with multiple actions | ||
105 | pub(crate) fn add_assist_group( | ||
106 | self, | ||
107 | id: AssistId, | ||
108 | label: impl Into<String>, | ||
109 | f: impl FnOnce() -> Vec<ActionBuilder>, | ||
110 | ) -> Option<Assist> { | ||
111 | let label = AssistLabel { label: label.into(), id }; | ||
112 | let assist = if self.should_compute_edit { | ||
113 | let actions = f(); | ||
114 | assert!(!actions.is_empty(), "Assist cannot have no"); | ||
115 | |||
116 | Assist::Resolved { | ||
117 | assist: ResolvedAssist { | ||
118 | label, | ||
119 | action_data: Either::Right( | ||
120 | actions.into_iter().map(ActionBuilder::build).collect(), | ||
121 | ), | ||
122 | }, | ||
123 | } | ||
96 | } else { | 124 | } else { |
97 | Assist::Unresolved { label } | 125 | Assist::Unresolved { label } |
98 | }; | 126 | }; |
@@ -128,13 +156,20 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
128 | } | 156 | } |
129 | 157 | ||
130 | #[derive(Default)] | 158 | #[derive(Default)] |
131 | pub(crate) struct AssistBuilder { | 159 | pub(crate) struct ActionBuilder { |
132 | edit: TextEditBuilder, | 160 | edit: TextEditBuilder, |
133 | cursor_position: Option<TextUnit>, | 161 | cursor_position: Option<TextUnit>, |
134 | target: Option<TextRange>, | 162 | target: Option<TextRange>, |
163 | label: Option<String>, | ||
135 | } | 164 | } |
136 | 165 | ||
137 | impl AssistBuilder { | 166 | impl ActionBuilder { |
167 | #[allow(dead_code)] | ||
168 | /// Adds a custom label to the action, if it needs to be different from the assist label | ||
169 | pub(crate) fn label(&mut self, label: impl Into<String>) { | ||
170 | self.label = Some(label.into()) | ||
171 | } | ||
172 | |||
138 | /// Replaces specified `range` of text with a given string. | 173 | /// Replaces specified `range` of text with a given string. |
139 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 174 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
140 | self.edit.replace(range, replace_with.into()) | 175 | self.edit.replace(range, replace_with.into()) |
@@ -193,6 +228,7 @@ impl AssistBuilder { | |||
193 | edit: self.edit.finish(), | 228 | edit: self.edit.finish(), |
194 | cursor_position: self.cursor_position, | 229 | cursor_position: self.cursor_position, |
195 | target: self.target, | 230 | target: self.target, |
231 | label: self.label, | ||
196 | } | 232 | } |
197 | } | 233 | } |
198 | } | 234 | } |
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs index 164aee90c..45e0f983f 100644 --- a/crates/ra_assists/src/assists/inline_local_variable.rs +++ b/crates/ra_assists/src/assists/inline_local_variable.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextRange, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::assist_ctx::AssistBuilder; | 7 | use crate::assist_ctx::ActionBuilder; |
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 9 | ||
10 | // Assist: inline_local_variable | 10 | // Assist: inline_local_variable |
@@ -94,7 +94,7 @@ pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option< | |||
94 | ctx.add_assist( | 94 | ctx.add_assist( |
95 | AssistId("inline_local_variable"), | 95 | AssistId("inline_local_variable"), |
96 | "Inline variable", | 96 | "Inline variable", |
97 | move |edit: &mut AssistBuilder| { | 97 | move |edit: &mut ActionBuilder| { |
98 | edit.delete(delete_range); | 98 | edit.delete(delete_range); |
99 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | 99 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { |
100 | if should_wrap { | 100 | if should_wrap { |
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index eac2903d1..56b7588ef 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | use rustc_hash::FxHashMap; | 2 | use rustc_hash::FxHashMap; |
3 | 3 | ||
4 | use hir::{db::HirDatabase, InFile, PathResolution}; | 4 | use hir::{db::HirDatabase, InFile, PathResolution}; |
5 | use ra_syntax::ast::{self, make, AstNode}; | 5 | use ra_syntax::ast::{self, AstNode}; |
6 | 6 | ||
7 | pub trait AstTransform<'a> { | 7 | pub trait AstTransform<'a> { |
8 | fn get_substitution( | 8 | fn get_substitution( |
@@ -134,11 +134,18 @@ impl<'a, DB: HirDatabase> QualifyPaths<'a, DB> { | |||
134 | match resolution { | 134 | match resolution { |
135 | PathResolution::Def(def) => { | 135 | PathResolution::Def(def) => { |
136 | let found_path = from.find_use_path(self.db, def)?; | 136 | let found_path = from.find_use_path(self.db, def)?; |
137 | let args = p | 137 | let mut path = path_to_ast(found_path); |
138 | |||
139 | let type_args = p | ||
138 | .segment() | 140 | .segment() |
139 | .and_then(|s| s.type_arg_list()) | 141 | .and_then(|s| s.type_arg_list()) |
140 | .map(|arg_list| apply(self, node.with_value(arg_list))); | 142 | .map(|arg_list| apply(self, node.with_value(arg_list))); |
141 | Some(make::path_with_type_arg_list(path_to_ast(found_path), args).syntax().clone()) | 143 | if let Some(type_args) = type_args { |
144 | let last_segment = path.segment().unwrap(); | ||
145 | path = path.with_segment(last_segment.with_type_args(type_args)) | ||
146 | } | ||
147 | |||
148 | Some(path.syntax().clone()) | ||
142 | } | 149 | } |
143 | PathResolution::Local(_) | 150 | PathResolution::Local(_) |
144 | | PathResolution::TypeParam(_) | 151 | | PathResolution::TypeParam(_) |
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs index a8f8446cb..5dc1ee233 100644 --- a/crates/ra_assists/src/doc_tests.rs +++ b/crates/ra_assists/src/doc_tests.rs | |||
@@ -15,21 +15,21 @@ fn check(assist_id: &str, before: &str, after: &str) { | |||
15 | let (db, file_id) = TestDB::with_single_file(&before); | 15 | let (db, file_id) = TestDB::with_single_file(&before); |
16 | let frange = FileRange { file_id, range: selection.into() }; | 16 | let frange = FileRange { file_id, range: selection.into() }; |
17 | 17 | ||
18 | let (_assist_id, action) = crate::assists(&db, frange) | 18 | let assist = crate::assists(&db, frange) |
19 | .into_iter() | 19 | .into_iter() |
20 | .find(|(id, _)| id.id.0 == assist_id) | 20 | .find(|assist| assist.label.id.0 == assist_id) |
21 | .unwrap_or_else(|| { | 21 | .unwrap_or_else(|| { |
22 | panic!( | 22 | panic!( |
23 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 23 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
24 | assist_id, | 24 | assist_id, |
25 | crate::assists(&db, frange) | 25 | crate::assists(&db, frange) |
26 | .into_iter() | 26 | .into_iter() |
27 | .map(|(id, _)| id.id.0) | 27 | .map(|assist| assist.label.id.0) |
28 | .collect::<Vec<_>>() | 28 | .collect::<Vec<_>>() |
29 | .join(", ") | 29 | .join(", ") |
30 | ) | 30 | ) |
31 | }); | 31 | }); |
32 | 32 | ||
33 | let actual = action.edit.apply(&before); | 33 | let actual = assist.get_first_action().edit.apply(&before); |
34 | assert_eq_text!(after, &actual); | 34 | assert_eq_text!(after, &actual); |
35 | } | 35 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 150b34ac7..d45b58966 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -13,6 +13,7 @@ mod doc_tests; | |||
13 | mod test_db; | 13 | mod test_db; |
14 | pub mod ast_transform; | 14 | pub mod ast_transform; |
15 | 15 | ||
16 | use either::Either; | ||
16 | use hir::db::HirDatabase; | 17 | use hir::db::HirDatabase; |
17 | use ra_db::FileRange; | 18 | use ra_db::FileRange; |
18 | use ra_syntax::{TextRange, TextUnit}; | 19 | use ra_syntax::{TextRange, TextUnit}; |
@@ -35,11 +36,27 @@ pub struct AssistLabel { | |||
35 | 36 | ||
36 | #[derive(Debug, Clone)] | 37 | #[derive(Debug, Clone)] |
37 | pub struct AssistAction { | 38 | pub struct AssistAction { |
39 | pub label: Option<String>, | ||
38 | pub edit: TextEdit, | 40 | pub edit: TextEdit, |
39 | pub cursor_position: Option<TextUnit>, | 41 | pub cursor_position: Option<TextUnit>, |
40 | pub target: Option<TextRange>, | 42 | pub target: Option<TextRange>, |
41 | } | 43 | } |
42 | 44 | ||
45 | #[derive(Debug, Clone)] | ||
46 | pub struct ResolvedAssist { | ||
47 | pub label: AssistLabel, | ||
48 | pub action_data: Either<AssistAction, Vec<AssistAction>>, | ||
49 | } | ||
50 | |||
51 | impl ResolvedAssist { | ||
52 | pub fn get_first_action(&self) -> AssistAction { | ||
53 | match &self.action_data { | ||
54 | Either::Left(action) => action.clone(), | ||
55 | Either::Right(actions) => actions[0].clone(), | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
43 | /// Return all the assists applicable at the given position. | 60 | /// Return all the assists applicable at the given position. |
44 | /// | 61 | /// |
45 | /// Assists are returned in the "unresolved" state, that is only labels are | 62 | /// Assists are returned in the "unresolved" state, that is only labels are |
@@ -64,7 +81,7 @@ where | |||
64 | /// | 81 | /// |
65 | /// Assists are returned in the "resolved" state, that is with edit fully | 82 | /// Assists are returned in the "resolved" state, that is with edit fully |
66 | /// computed. | 83 | /// computed. |
67 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | 84 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> |
68 | where | 85 | where |
69 | H: HirDatabase + 'static, | 86 | H: HirDatabase + 'static, |
70 | { | 87 | { |
@@ -75,11 +92,11 @@ where | |||
75 | .iter() | 92 | .iter() |
76 | .filter_map(|f| f(ctx.clone())) | 93 | .filter_map(|f| f(ctx.clone())) |
77 | .map(|a| match a { | 94 | .map(|a| match a { |
78 | Assist::Resolved { label, action } => (label, action), | 95 | Assist::Resolved { assist } => assist, |
79 | Assist::Unresolved { .. } => unreachable!(), | 96 | Assist::Unresolved { .. } => unreachable!(), |
80 | }) | 97 | }) |
81 | .collect::<Vec<_>>(); | 98 | .collect::<Vec<_>>(); |
82 | a.sort_by(|a, b| match (a.1.target, b.1.target) { | 99 | a.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) { |
83 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | 100 | (Some(a), Some(b)) => a.len().cmp(&b.len()), |
84 | (Some(_), None) => Ordering::Less, | 101 | (Some(_), None) => Ordering::Less, |
85 | (None, Some(_)) => Ordering::Greater, | 102 | (None, Some(_)) => Ordering::Greater, |
@@ -174,7 +191,7 @@ mod helpers { | |||
174 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 191 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
175 | let action = match assist { | 192 | let action = match assist { |
176 | Assist::Unresolved { .. } => unreachable!(), | 193 | Assist::Unresolved { .. } => unreachable!(), |
177 | Assist::Resolved { action, .. } => action, | 194 | Assist::Resolved { assist } => assist.get_first_action(), |
178 | }; | 195 | }; |
179 | 196 | ||
180 | let actual = action.edit.apply(&before); | 197 | let actual = action.edit.apply(&before); |
@@ -201,7 +218,7 @@ mod helpers { | |||
201 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 218 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
202 | let action = match assist { | 219 | let action = match assist { |
203 | Assist::Unresolved { .. } => unreachable!(), | 220 | Assist::Unresolved { .. } => unreachable!(), |
204 | Assist::Resolved { action, .. } => action, | 221 | Assist::Resolved { assist } => assist.get_first_action(), |
205 | }; | 222 | }; |
206 | 223 | ||
207 | let mut actual = action.edit.apply(&before); | 224 | let mut actual = action.edit.apply(&before); |
@@ -224,7 +241,7 @@ mod helpers { | |||
224 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 241 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
225 | let action = match assist { | 242 | let action = match assist { |
226 | Assist::Unresolved { .. } => unreachable!(), | 243 | Assist::Unresolved { .. } => unreachable!(), |
227 | Assist::Resolved { action, .. } => action, | 244 | Assist::Resolved { assist } => assist.get_first_action(), |
228 | }; | 245 | }; |
229 | 246 | ||
230 | let range = action.target.expect("expected target on action"); | 247 | let range = action.target.expect("expected target on action"); |
@@ -243,7 +260,7 @@ mod helpers { | |||
243 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 260 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
244 | let action = match assist { | 261 | let action = match assist { |
245 | Assist::Unresolved { .. } => unreachable!(), | 262 | Assist::Unresolved { .. } => unreachable!(), |
246 | Assist::Resolved { action, .. } => action, | 263 | Assist::Resolved { assist } => assist.get_first_action(), |
247 | }; | 264 | }; |
248 | 265 | ||
249 | let range = action.target.expect("expected target on action"); | 266 | let range = action.target.expect("expected target on action"); |
@@ -293,10 +310,10 @@ mod tests { | |||
293 | let mut assists = assists.iter(); | 310 | let mut assists = assists.iter(); |
294 | 311 | ||
295 | assert_eq!( | 312 | assert_eq!( |
296 | assists.next().expect("expected assist").0.label, | 313 | assists.next().expect("expected assist").label.label, |
297 | "Change visibility to pub(crate)" | 314 | "Change visibility to pub(crate)" |
298 | ); | 315 | ); |
299 | assert_eq!(assists.next().expect("expected assist").0.label, "Add `#[derive]`"); | 316 | assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); |
300 | } | 317 | } |
301 | 318 | ||
302 | #[test] | 319 | #[test] |
@@ -315,7 +332,7 @@ mod tests { | |||
315 | let assists = super::assists(&db, frange); | 332 | let assists = super::assists(&db, frange); |
316 | let mut assists = assists.iter(); | 333 | let mut assists = assists.iter(); |
317 | 334 | ||
318 | assert_eq!(assists.next().expect("expected assist").0.label, "Extract into variable"); | 335 | assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); |
319 | assert_eq!(assists.next().expect("expected assist").0.label, "Replace with match"); | 336 | assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); |
320 | } | 337 | } |
321 | } | 338 | } |
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs index f7dc8acb7..8cc2fb160 100644 --- a/crates/ra_hir_def/src/find_path.rs +++ b/crates/ra_hir_def/src/find_path.rs | |||
@@ -35,7 +35,7 @@ fn find_path_inner( | |||
35 | let def_map = db.crate_def_map(from.krate); | 35 | let def_map = db.crate_def_map(from.krate); |
36 | let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope; | 36 | let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope; |
37 | if let Some((name, _)) = from_scope.name_of(item) { | 37 | if let Some((name, _)) = from_scope.name_of(item) { |
38 | return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()])); | 38 | return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()])); |
39 | } | 39 | } |
40 | 40 | ||
41 | // - if the item is the crate root, return `crate` | 41 | // - if the item is the crate root, return `crate` |
@@ -45,12 +45,12 @@ fn find_path_inner( | |||
45 | local_id: def_map.root, | 45 | local_id: def_map.root, |
46 | })) | 46 | })) |
47 | { | 47 | { |
48 | return Some(ModPath::from_simple_segments(PathKind::Crate, Vec::new())); | 48 | return Some(ModPath::from_segments(PathKind::Crate, Vec::new())); |
49 | } | 49 | } |
50 | 50 | ||
51 | // - if the item is the module we're in, use `self` | 51 | // - if the item is the module we're in, use `self` |
52 | if item == ItemInNs::Types(from.into()) { | 52 | if item == ItemInNs::Types(from.into()) { |
53 | return Some(ModPath::from_simple_segments(PathKind::Super(0), Vec::new())); | 53 | return Some(ModPath::from_segments(PathKind::Super(0), Vec::new())); |
54 | } | 54 | } |
55 | 55 | ||
56 | // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) | 56 | // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) |
@@ -61,14 +61,14 @@ fn find_path_inner( | |||
61 | local_id: parent_id, | 61 | local_id: parent_id, |
62 | })) | 62 | })) |
63 | { | 63 | { |
64 | return Some(ModPath::from_simple_segments(PathKind::Super(1), Vec::new())); | 64 | return Some(ModPath::from_segments(PathKind::Super(1), Vec::new())); |
65 | } | 65 | } |
66 | } | 66 | } |
67 | 67 | ||
68 | // - if the item is the crate root of a dependency crate, return the name from the extern prelude | 68 | // - if the item is the crate root of a dependency crate, return the name from the extern prelude |
69 | for (name, def_id) in &def_map.extern_prelude { | 69 | for (name, def_id) in &def_map.extern_prelude { |
70 | if item == ItemInNs::Types(*def_id) { | 70 | if item == ItemInNs::Types(*def_id) { |
71 | return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()])); | 71 | return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()])); |
72 | } | 72 | } |
73 | } | 73 | } |
74 | 74 | ||
@@ -79,7 +79,7 @@ fn find_path_inner( | |||
79 | &prelude_def_map.modules[prelude_module.local_id].scope; | 79 | &prelude_def_map.modules[prelude_module.local_id].scope; |
80 | if let Some((name, vis)) = prelude_scope.name_of(item) { | 80 | if let Some((name, vis)) = prelude_scope.name_of(item) { |
81 | if vis.is_visible_from(db, from) { | 81 | if vis.is_visible_from(db, from) { |
82 | return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()])); | 82 | return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()])); |
83 | } | 83 | } |
84 | } | 84 | } |
85 | } | 85 | } |
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs index 9f93a5424..ab290e2c9 100644 --- a/crates/ra_hir_def/src/path.rs +++ b/crates/ra_hir_def/src/path.rs | |||
@@ -39,10 +39,7 @@ impl ModPath { | |||
39 | lower::lower_path(path, hygiene).map(|it| it.mod_path) | 39 | lower::lower_path(path, hygiene).map(|it| it.mod_path) |
40 | } | 40 | } |
41 | 41 | ||
42 | pub fn from_simple_segments( | 42 | pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath { |
43 | kind: PathKind, | ||
44 | segments: impl IntoIterator<Item = Name>, | ||
45 | ) -> ModPath { | ||
46 | let segments = segments.into_iter().collect::<Vec<_>>(); | 43 | let segments = segments.into_iter().collect::<Vec<_>>(); |
47 | ModPath { kind, segments } | 44 | ModPath { kind, segments } |
48 | } | 45 | } |
@@ -240,7 +237,7 @@ impl From<Name> for Path { | |||
240 | fn from(name: Name) -> Path { | 237 | fn from(name: Name) -> Path { |
241 | Path { | 238 | Path { |
242 | type_anchor: None, | 239 | type_anchor: None, |
243 | mod_path: ModPath::from_simple_segments(PathKind::Plain, iter::once(name)), | 240 | mod_path: ModPath::from_segments(PathKind::Plain, iter::once(name)), |
244 | generic_args: vec![None], | 241 | generic_args: vec![None], |
245 | } | 242 | } |
246 | } | 243 | } |
@@ -248,7 +245,7 @@ impl From<Name> for Path { | |||
248 | 245 | ||
249 | impl From<Name> for ModPath { | 246 | impl From<Name> for ModPath { |
250 | fn from(name: Name) -> ModPath { | 247 | fn from(name: Name) -> ModPath { |
251 | ModPath::from_simple_segments(PathKind::Plain, iter::once(name)) | 248 | ModPath::from_segments(PathKind::Plain, iter::once(name)) |
252 | } | 249 | } |
253 | } | 250 | } |
254 | 251 | ||
@@ -311,7 +308,7 @@ macro_rules! __known_path { | |||
311 | macro_rules! __path { | 308 | macro_rules! __path { |
312 | ($start:ident $(:: $seg:ident)*) => ({ | 309 | ($start:ident $(:: $seg:ident)*) => ({ |
313 | $crate::__known_path!($start $(:: $seg)*); | 310 | $crate::__known_path!($start $(:: $seg)*); |
314 | $crate::path::ModPath::from_simple_segments($crate::path::PathKind::Abs, vec![ | 311 | $crate::path::ModPath::from_segments($crate::path::PathKind::Abs, vec![ |
315 | $crate::path::__name![$start], $($crate::path::__name![$seg],)* | 312 | $crate::path::__name![$start], $($crate::path::__name![$seg],)* |
316 | ]) | 313 | ]) |
317 | }); | 314 | }); |
diff --git a/crates/ra_hir_def/src/path/lower/lower_use.rs b/crates/ra_hir_def/src/path/lower/lower_use.rs index 3218eaf0a..531878174 100644 --- a/crates/ra_hir_def/src/path/lower/lower_use.rs +++ b/crates/ra_hir_def/src/path/lower/lower_use.rs | |||
@@ -84,7 +84,7 @@ fn convert_path(prefix: Option<ModPath>, path: ast::Path, hygiene: &Hygiene) -> | |||
84 | res | 84 | res |
85 | } | 85 | } |
86 | Either::Right(crate_id) => { | 86 | Either::Right(crate_id) => { |
87 | return Some(ModPath::from_simple_segments( | 87 | return Some(ModPath::from_segments( |
88 | PathKind::DollarCrate(crate_id), | 88 | PathKind::DollarCrate(crate_id), |
89 | iter::empty(), | 89 | iter::empty(), |
90 | )) | 90 | )) |
@@ -95,19 +95,19 @@ fn convert_path(prefix: Option<ModPath>, path: ast::Path, hygiene: &Hygiene) -> | |||
95 | if prefix.is_some() { | 95 | if prefix.is_some() { |
96 | return None; | 96 | return None; |
97 | } | 97 | } |
98 | ModPath::from_simple_segments(PathKind::Crate, iter::empty()) | 98 | ModPath::from_segments(PathKind::Crate, iter::empty()) |
99 | } | 99 | } |
100 | ast::PathSegmentKind::SelfKw => { | 100 | ast::PathSegmentKind::SelfKw => { |
101 | if prefix.is_some() { | 101 | if prefix.is_some() { |
102 | return None; | 102 | return None; |
103 | } | 103 | } |
104 | ModPath::from_simple_segments(PathKind::Super(0), iter::empty()) | 104 | ModPath::from_segments(PathKind::Super(0), iter::empty()) |
105 | } | 105 | } |
106 | ast::PathSegmentKind::SuperKw => { | 106 | ast::PathSegmentKind::SuperKw => { |
107 | if prefix.is_some() { | 107 | if prefix.is_some() { |
108 | return None; | 108 | return None; |
109 | } | 109 | } |
110 | ModPath::from_simple_segments(PathKind::Super(1), iter::empty()) | 110 | ModPath::from_segments(PathKind::Super(1), iter::empty()) |
111 | } | 111 | } |
112 | ast::PathSegmentKind::Type { .. } => { | 112 | ast::PathSegmentKind::Type { .. } => { |
113 | // not allowed in imports | 113 | // not allowed in imports |
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs index e00589733..a936900da 100644 --- a/crates/ra_ide/src/assists.rs +++ b/crates/ra_ide/src/assists.rs | |||
@@ -2,27 +2,53 @@ | |||
2 | 2 | ||
3 | use ra_db::{FilePosition, FileRange}; | 3 | use ra_db::{FilePosition, FileRange}; |
4 | 4 | ||
5 | use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; | 5 | use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit}; |
6 | 6 | ||
7 | use either::Either; | ||
7 | pub use ra_assists::AssistId; | 8 | pub use ra_assists::AssistId; |
9 | use ra_assists::{AssistAction, AssistLabel}; | ||
8 | 10 | ||
9 | #[derive(Debug)] | 11 | #[derive(Debug)] |
10 | pub struct Assist { | 12 | pub struct Assist { |
11 | pub id: AssistId, | 13 | pub id: AssistId, |
12 | pub change: SourceChange, | 14 | pub label: String, |
15 | pub change_data: Either<SourceChange, Vec<SourceChange>>, | ||
13 | } | 16 | } |
14 | 17 | ||
15 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | 18 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { |
16 | ra_assists::assists(db, frange) | 19 | ra_assists::assists(db, frange) |
17 | .into_iter() | 20 | .into_iter() |
18 | .map(|(label, action)| { | 21 | .map(|assist| { |
19 | let file_id = frange.file_id; | 22 | let file_id = frange.file_id; |
20 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | 23 | let assist_label = &assist.label; |
21 | let id = label.id; | 24 | Assist { |
22 | let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( | 25 | id: assist_label.id, |
23 | action.cursor_position.map(|offset| FilePosition { offset, file_id }), | 26 | label: assist_label.label.clone(), |
24 | ); | 27 | change_data: match assist.action_data { |
25 | Assist { id, change } | 28 | Either::Left(action) => { |
29 | Either::Left(action_to_edit(action, file_id, assist_label)) | ||
30 | } | ||
31 | Either::Right(actions) => Either::Right( | ||
32 | actions | ||
33 | .into_iter() | ||
34 | .map(|action| action_to_edit(action, file_id, assist_label)) | ||
35 | .collect(), | ||
36 | ), | ||
37 | }, | ||
38 | } | ||
26 | }) | 39 | }) |
27 | .collect() | 40 | .collect() |
28 | } | 41 | } |
42 | |||
43 | fn action_to_edit( | ||
44 | action: AssistAction, | ||
45 | file_id: FileId, | ||
46 | assist_label: &AssistLabel, | ||
47 | ) -> SourceChange { | ||
48 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | ||
49 | SourceChange::source_file_edit( | ||
50 | action.label.unwrap_or_else(|| assist_label.label.clone()), | ||
51 | file_edit, | ||
52 | ) | ||
53 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) | ||
54 | } | ||
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index c08e67b8e..579158780 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml | |||
@@ -28,6 +28,7 @@ ra_prof = { path = "../ra_prof" } | |||
28 | ra_vfs_glob = { path = "../ra_vfs_glob" } | 28 | ra_vfs_glob = { path = "../ra_vfs_glob" } |
29 | env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } | 29 | env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } |
30 | ra_cargo_watch = { path = "../ra_cargo_watch" } | 30 | ra_cargo_watch = { path = "../ra_cargo_watch" } |
31 | either = "1.5" | ||
31 | 32 | ||
32 | [dev-dependencies] | 33 | [dev-dependencies] |
33 | tempfile = "3" | 34 | tempfile = "3" |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index f2db575ea..9e9964880 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | use std::{fmt::Write as _, io::Write as _}; | 4 | use std::{fmt::Write as _, io::Write as _}; |
5 | 5 | ||
6 | use either::Either; | ||
6 | use lsp_server::ErrorCode; | 7 | use lsp_server::ErrorCode; |
7 | use lsp_types::{ | 8 | use lsp_types::{ |
8 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 9 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
@@ -644,7 +645,6 @@ pub fn handle_code_action( | |||
644 | let line_index = world.analysis().file_line_index(file_id)?; | 645 | let line_index = world.analysis().file_line_index(file_id)?; |
645 | let range = params.range.conv_with(&line_index); | 646 | let range = params.range.conv_with(&line_index); |
646 | 647 | ||
647 | let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter(); | ||
648 | let diagnostics = world.analysis().diagnostics(file_id)?; | 648 | let diagnostics = world.analysis().diagnostics(file_id)?; |
649 | let mut res = CodeActionResponse::default(); | 649 | let mut res = CodeActionResponse::default(); |
650 | 650 | ||
@@ -697,15 +697,27 @@ pub fn handle_code_action( | |||
697 | res.push(action.into()); | 697 | res.push(action.into()); |
698 | } | 698 | } |
699 | 699 | ||
700 | for assist in assists { | 700 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { |
701 | let title = assist.change.label.clone(); | 701 | let title = assist.label.clone(); |
702 | let edit = assist.change.try_conv_with(&world)?; | ||
703 | 702 | ||
704 | let command = Command { | 703 | let command = match assist.change_data { |
705 | title, | 704 | Either::Left(change) => Command { |
706 | command: "rust-analyzer.applySourceChange".to_string(), | 705 | title, |
707 | arguments: Some(vec![to_value(edit).unwrap()]), | 706 | command: "rust-analyzer.applySourceChange".to_string(), |
707 | arguments: Some(vec![to_value(change.try_conv_with(&world)?)?]), | ||
708 | }, | ||
709 | Either::Right(changes) => Command { | ||
710 | title, | ||
711 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), | ||
712 | arguments: Some(vec![to_value( | ||
713 | changes | ||
714 | .into_iter() | ||
715 | .map(|change| change.try_conv_with(&world)) | ||
716 | .collect::<Result<Vec<_>>>()?, | ||
717 | )?]), | ||
718 | }, | ||
708 | }; | 719 | }; |
720 | |||
709 | let action = CodeAction { | 721 | let action = CodeAction { |
710 | title: command.title.clone(), | 722 | title: command.title.clone(), |
711 | kind: match assist.id { | 723 | kind: match assist.id { |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index b736098ac..0e78d8b63 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -22,9 +22,8 @@ impl ast::BinExpr { | |||
22 | #[must_use] | 22 | #[must_use] |
23 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { | 23 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { |
24 | let op_node: SyntaxElement = self.op_details()?.0.into(); | 24 | let op_node: SyntaxElement = self.op_details()?.0.into(); |
25 | let to_insert: Option<SyntaxElement> = Some(tokens::op(op).into()); | 25 | let to_insert: Option<SyntaxElement> = Some(make::token(op).into()); |
26 | let replace_range = RangeInclusive::new(op_node.clone(), op_node); | 26 | Some(replace_children(self, single_node(op_node), to_insert)) |
27 | Some(replace_children(self, replace_range, to_insert.into_iter())) | ||
28 | } | 27 | } |
29 | } | 28 | } |
30 | 29 | ||
@@ -40,11 +39,10 @@ impl ast::FnDef { | |||
40 | } else { | 39 | } else { |
41 | to_insert.push(make::tokens::single_space().into()); | 40 | to_insert.push(make::tokens::single_space().into()); |
42 | to_insert.push(body.syntax().clone().into()); | 41 | to_insert.push(body.syntax().clone().into()); |
43 | return insert_children(self, InsertPosition::Last, to_insert.into_iter()); | 42 | return insert_children(self, InsertPosition::Last, to_insert); |
44 | }; | 43 | }; |
45 | to_insert.push(body.syntax().clone().into()); | 44 | to_insert.push(body.syntax().clone().into()); |
46 | let replace_range = RangeInclusive::new(old_body_or_semi.clone(), old_body_or_semi); | 45 | replace_children(self, single_node(old_body_or_semi), to_insert) |
47 | replace_children(self, replace_range, to_insert.into_iter()) | ||
48 | } | 46 | } |
49 | } | 47 | } |
50 | 48 | ||
@@ -77,7 +75,7 @@ impl ast::ItemList { | |||
77 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | 75 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); |
78 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | 76 | let to_insert: ArrayVec<[SyntaxElement; 2]> = |
79 | [ws.ws().into(), item.syntax().clone().into()].into(); | 77 | [ws.ws().into(), item.syntax().clone().into()].into(); |
80 | insert_children(self, position, to_insert.into_iter()) | 78 | insert_children(self, position, to_insert) |
81 | } | 79 | } |
82 | 80 | ||
83 | fn l_curly(&self) -> Option<SyntaxElement> { | 81 | fn l_curly(&self) -> Option<SyntaxElement> { |
@@ -109,9 +107,7 @@ impl ast::ItemList { | |||
109 | let to_insert = iter::once(ws.ws().into()); | 107 | let to_insert = iter::once(ws.ws().into()); |
110 | match existing_ws { | 108 | match existing_ws { |
111 | None => insert_children(self, InsertPosition::After(l_curly), to_insert), | 109 | None => insert_children(self, InsertPosition::After(l_curly), to_insert), |
112 | Some(ws) => { | 110 | Some(ws) => replace_children(self, single_node(ws), to_insert), |
113 | replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert) | ||
114 | } | ||
115 | } | 111 | } |
116 | } | 112 | } |
117 | } | 113 | } |
@@ -188,7 +184,7 @@ impl ast::RecordFieldList { | |||
188 | InsertPosition::After(anchor) => after_field!(anchor), | 184 | InsertPosition::After(anchor) => after_field!(anchor), |
189 | }; | 185 | }; |
190 | 186 | ||
191 | insert_children(self, position, to_insert.iter().cloned()) | 187 | insert_children(self, position, to_insert) |
192 | } | 188 | } |
193 | 189 | ||
194 | fn l_curly(&self) -> Option<SyntaxElement> { | 190 | fn l_curly(&self) -> Option<SyntaxElement> { |
@@ -207,7 +203,49 @@ impl ast::TypeParam { | |||
207 | Some(it) => it.syntax().clone().into(), | 203 | Some(it) => it.syntax().clone().into(), |
208 | None => colon.clone().into(), | 204 | None => colon.clone().into(), |
209 | }; | 205 | }; |
210 | replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty()) | 206 | replace_children(self, colon.into()..=end, iter::empty()) |
207 | } | ||
208 | } | ||
209 | |||
210 | impl ast::Path { | ||
211 | #[must_use] | ||
212 | pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { | ||
213 | if let Some(old) = self.segment() { | ||
214 | return replace_children( | ||
215 | self, | ||
216 | single_node(old.syntax().clone()), | ||
217 | iter::once(segment.syntax().clone().into()), | ||
218 | ); | ||
219 | } | ||
220 | self.clone() | ||
221 | } | ||
222 | } | ||
223 | |||
224 | impl ast::PathSegment { | ||
225 | #[must_use] | ||
226 | pub fn with_type_args(&self, type_args: ast::TypeArgList) -> ast::PathSegment { | ||
227 | self._with_type_args(type_args, false) | ||
228 | } | ||
229 | |||
230 | #[must_use] | ||
231 | pub fn with_turbo_fish(&self, type_args: ast::TypeArgList) -> ast::PathSegment { | ||
232 | self._with_type_args(type_args, true) | ||
233 | } | ||
234 | |||
235 | fn _with_type_args(&self, type_args: ast::TypeArgList, turbo: bool) -> ast::PathSegment { | ||
236 | if let Some(old) = self.type_arg_list() { | ||
237 | return replace_children( | ||
238 | self, | ||
239 | single_node(old.syntax().clone()), | ||
240 | iter::once(type_args.syntax().clone().into()), | ||
241 | ); | ||
242 | } | ||
243 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); | ||
244 | if turbo { | ||
245 | to_insert.push(make::token(T![::]).into()); | ||
246 | } | ||
247 | to_insert.push(type_args.syntax().clone().into()); | ||
248 | insert_children(self, InsertPosition::Last, to_insert) | ||
211 | } | 249 | } |
212 | } | 250 | } |
213 | 251 | ||
@@ -224,7 +262,7 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { | |||
224 | Some(el) if el.kind() == WHITESPACE => el.clone(), | 262 | Some(el) if el.kind() == WHITESPACE => el.clone(), |
225 | Some(_) | None => start.clone(), | 263 | Some(_) | None => start.clone(), |
226 | }; | 264 | }; |
227 | node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty()); | 265 | node = algo::replace_children(&node, start..=end, &mut iter::empty()); |
228 | } | 266 | } |
229 | node | 267 | node |
230 | } | 268 | } |
@@ -232,9 +270,10 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { | |||
232 | #[must_use] | 270 | #[must_use] |
233 | pub fn replace_descendants<N: AstNode, D: AstNode>( | 271 | pub fn replace_descendants<N: AstNode, D: AstNode>( |
234 | parent: &N, | 272 | parent: &N, |
235 | replacement_map: impl Iterator<Item = (D, D)>, | 273 | replacement_map: impl IntoIterator<Item = (D, D)>, |
236 | ) -> N { | 274 | ) -> N { |
237 | let map = replacement_map | 275 | let map = replacement_map |
276 | .into_iter() | ||
238 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) | 277 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) |
239 | .collect::<FxHashMap<SyntaxElement, _>>(); | 278 | .collect::<FxHashMap<SyntaxElement, _>>(); |
240 | let new_syntax = algo::replace_descendants(parent.syntax(), &|n| map.get(n).cloned()); | 279 | let new_syntax = algo::replace_descendants(parent.syntax(), &|n| map.get(n).cloned()); |
@@ -348,19 +387,25 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { | |||
348 | fn insert_children<N: AstNode>( | 387 | fn insert_children<N: AstNode>( |
349 | parent: &N, | 388 | parent: &N, |
350 | position: InsertPosition<SyntaxElement>, | 389 | position: InsertPosition<SyntaxElement>, |
351 | mut to_insert: impl Iterator<Item = SyntaxElement>, | 390 | to_insert: impl IntoIterator<Item = SyntaxElement>, |
352 | ) -> N { | 391 | ) -> N { |
353 | let new_syntax = algo::insert_children(parent.syntax(), position, &mut to_insert); | 392 | let new_syntax = algo::insert_children(parent.syntax(), position, &mut to_insert.into_iter()); |
354 | N::cast(new_syntax).unwrap() | 393 | N::cast(new_syntax).unwrap() |
355 | } | 394 | } |
356 | 395 | ||
396 | fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { | ||
397 | let element = element.into(); | ||
398 | element.clone()..=element | ||
399 | } | ||
400 | |||
357 | #[must_use] | 401 | #[must_use] |
358 | fn replace_children<N: AstNode>( | 402 | fn replace_children<N: AstNode>( |
359 | parent: &N, | 403 | parent: &N, |
360 | to_replace: RangeInclusive<SyntaxElement>, | 404 | to_replace: RangeInclusive<SyntaxElement>, |
361 | mut to_insert: impl Iterator<Item = SyntaxElement>, | 405 | to_insert: impl IntoIterator<Item = SyntaxElement>, |
362 | ) -> N { | 406 | ) -> N { |
363 | let new_syntax = algo::replace_children(parent.syntax(), to_replace, &mut to_insert); | 407 | let new_syntax = |
408 | algo::replace_children(parent.syntax(), to_replace, &mut to_insert.into_iter()); | ||
364 | N::cast(new_syntax).unwrap() | 409 | N::cast(new_syntax).unwrap() |
365 | } | 410 | } |
366 | 411 | ||
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index eef45090d..36e648180 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | //! of smaller pieces. | 2 | //! of smaller pieces. |
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | 4 | ||
5 | use crate::{algo, ast, AstNode, SourceFile}; | 5 | use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken}; |
6 | 6 | ||
7 | pub fn name(text: &str) -> ast::Name { | 7 | pub fn name(text: &str) -> ast::Name { |
8 | ast_from_text(&format!("mod {};", text)) | 8 | ast_from_text(&format!("mod {};", text)) |
@@ -21,20 +21,6 @@ pub fn path_qualified(qual: ast::Path, name_ref: ast::NameRef) -> ast::Path { | |||
21 | fn path_from_text(text: &str) -> ast::Path { | 21 | fn path_from_text(text: &str) -> ast::Path { |
22 | ast_from_text(text) | 22 | ast_from_text(text) |
23 | } | 23 | } |
24 | pub fn path_with_type_arg_list(path: ast::Path, args: Option<ast::TypeArgList>) -> ast::Path { | ||
25 | if let Some(args) = args { | ||
26 | let syntax = path.syntax(); | ||
27 | // FIXME: remove existing type args | ||
28 | let new_syntax = algo::insert_children( | ||
29 | syntax, | ||
30 | crate::algo::InsertPosition::Last, | ||
31 | &mut Some(args).into_iter().map(|n| n.syntax().clone().into()), | ||
32 | ); | ||
33 | ast::Path::cast(new_syntax).unwrap() | ||
34 | } else { | ||
35 | path | ||
36 | } | ||
37 | } | ||
38 | 24 | ||
39 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { | 25 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { |
40 | return match expr { | 26 | return match expr { |
@@ -181,27 +167,27 @@ pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetSt | |||
181 | ast_from_text(&format!("fn f() {{ {} }}", text)) | 167 | ast_from_text(&format!("fn f() {{ {} }}", text)) |
182 | } | 168 | } |
183 | 169 | ||
170 | pub fn token(kind: SyntaxKind) -> SyntaxToken { | ||
171 | tokens::SOURCE_FILE | ||
172 | .tree() | ||
173 | .syntax() | ||
174 | .descendants_with_tokens() | ||
175 | .filter_map(|it| it.into_token()) | ||
176 | .find(|it| it.kind() == kind) | ||
177 | .unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) | ||
178 | } | ||
179 | |||
184 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 180 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
185 | let parse = SourceFile::parse(text); | 181 | let parse = SourceFile::parse(text); |
186 | parse.tree().syntax().descendants().find_map(N::cast).unwrap() | 182 | parse.tree().syntax().descendants().find_map(N::cast).unwrap() |
187 | } | 183 | } |
188 | 184 | ||
189 | pub mod tokens { | 185 | pub mod tokens { |
190 | use crate::{AstNode, Parse, SourceFile, SyntaxKind, SyntaxKind::*, SyntaxToken, T}; | 186 | use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; |
191 | use once_cell::sync::Lazy; | 187 | use once_cell::sync::Lazy; |
192 | 188 | ||
193 | static SOURCE_FILE: Lazy<Parse<SourceFile>> = | 189 | pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = |
194 | Lazy::new(|| SourceFile::parse("const C: () = (1 != 1, 2 == 2)\n;")); | 190 | Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2)\n;")); |
195 | |||
196 | pub fn op(op: SyntaxKind) -> SyntaxToken { | ||
197 | SOURCE_FILE | ||
198 | .tree() | ||
199 | .syntax() | ||
200 | .descendants_with_tokens() | ||
201 | .filter_map(|it| it.into_token()) | ||
202 | .find(|it| it.kind() == op) | ||
203 | .unwrap() | ||
204 | } | ||
205 | 191 | ||
206 | pub fn comma() -> SyntaxToken { | 192 | pub fn comma() -> SyntaxToken { |
207 | SOURCE_FILE | 193 | SOURCE_FILE |
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 9a1697dcb..dc075aa82 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -39,6 +39,18 @@ function applySourceChange(ctx: Ctx): Cmd { | |||
39 | }; | 39 | }; |
40 | } | 40 | } |
41 | 41 | ||
42 | function selectAndApplySourceChange(ctx: Ctx): Cmd { | ||
43 | return async (changes: sourceChange.SourceChange[]) => { | ||
44 | if (changes.length === 1) { | ||
45 | await sourceChange.applySourceChange(ctx, changes[0]); | ||
46 | } else if (changes.length > 0) { | ||
47 | const selectedChange = await vscode.window.showQuickPick(changes); | ||
48 | if (!selectedChange) return; | ||
49 | await sourceChange.applySourceChange(ctx, selectedChange); | ||
50 | } | ||
51 | }; | ||
52 | } | ||
53 | |||
42 | function reload(ctx: Ctx): Cmd { | 54 | function reload(ctx: Ctx): Cmd { |
43 | return async () => { | 55 | return async () => { |
44 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); | 56 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); |
@@ -59,5 +71,6 @@ export { | |||
59 | runSingle, | 71 | runSingle, |
60 | showReferences, | 72 | showReferences, |
61 | applySourceChange, | 73 | applySourceChange, |
74 | selectAndApplySourceChange, | ||
62 | reload | 75 | reload |
63 | }; | 76 | }; |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 430ad31b4..0494ccf63 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -26,6 +26,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
26 | ctx.registerCommand('runSingle', commands.runSingle); | 26 | ctx.registerCommand('runSingle', commands.runSingle); |
27 | ctx.registerCommand('showReferences', commands.showReferences); | 27 | ctx.registerCommand('showReferences', commands.showReferences); |
28 | ctx.registerCommand('applySourceChange', commands.applySourceChange); | 28 | ctx.registerCommand('applySourceChange', commands.applySourceChange); |
29 | ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); | ||
29 | 30 | ||
30 | if (ctx.config.enableEnhancedTyping) { | 31 | if (ctx.config.enableEnhancedTyping) { |
31 | ctx.overrideCommand('type', commands.onEnter); | 32 | ctx.overrideCommand('type', commands.onEnter); |