aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock3
-rw-r--r--crates/ra_assists/Cargo.toml2
-rw-r--r--crates/ra_assists/src/assist_ctx.rs50
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs4
-rw-r--r--crates/ra_assists/src/ast_transform.rs13
-rw-r--r--crates/ra_assists/src/doc_tests.rs8
-rw-r--r--crates/ra_assists/src/lib.rs39
-rw-r--r--crates/ra_hir_def/src/find_path.rs12
-rw-r--r--crates/ra_hir_def/src/path.rs11
-rw-r--r--crates/ra_hir_def/src/path/lower/lower_use.rs8
-rw-r--r--crates/ra_ide/src/assists.rs44
-rw-r--r--crates/ra_lsp_server/Cargo.toml1
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs28
-rw-r--r--crates/ra_syntax/src/ast/edit.rs81
-rw-r--r--crates/ra_syntax/src/ast/make.rs42
-rw-r--r--editors/code/src/commands/index.ts13
-rw-r--r--editors/code/src/main.ts1
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"
869name = "ra_assists" 869name = "ra_assists"
870version = "0.1.0" 870version = "0.1.0"
871dependencies = [ 871dependencies = [
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"
1066version = "0.1.0" 1066version = "0.1.0"
1067dependencies = [ 1067dependencies = [
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
11format-buf = "1.0.0" 11format-buf = "1.0.0"
12join_to_string = "0.1.3" 12join_to_string = "0.1.3"
13rustc-hash = "1.0" 13rustc-hash = "1.0"
14itertools = "0.8.0" 14either = "1.5"
15 15
16ra_syntax = { path = "../ra_syntax" } 16ra_syntax = { path = "../ra_syntax" }
17ra_text_edit = { path = "../ra_text_edit" } 17ra_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.
2use either::Either;
2use hir::{db::HirDatabase, InFile, SourceAnalyzer}; 3use hir::{db::HirDatabase, InFile, SourceAnalyzer};
3use ra_db::FileRange; 4use ra_db::FileRange;
4use ra_fmt::{leading_indent, reindent}; 5use ra_fmt::{leading_indent, reindent};
@@ -9,12 +10,12 @@ use ra_syntax::{
9}; 10};
10use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
11 12
12use crate::{AssistAction, AssistId, AssistLabel}; 13use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist};
13 14
14#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
15pub(crate) enum Assist { 16pub(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)]
131pub(crate) struct AssistBuilder { 159pub(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
137impl AssistBuilder { 166impl 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
7use crate::assist_ctx::AssistBuilder; 7use crate::assist_ctx::ActionBuilder;
8use crate::{Assist, AssistCtx, AssistId}; 8use 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 @@
2use rustc_hash::FxHashMap; 2use rustc_hash::FxHashMap;
3 3
4use hir::{db::HirDatabase, InFile, PathResolution}; 4use hir::{db::HirDatabase, InFile, PathResolution};
5use ra_syntax::ast::{self, make, AstNode}; 5use ra_syntax::ast::{self, AstNode};
6 6
7pub trait AstTransform<'a> { 7pub 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;
13mod test_db; 13mod test_db;
14pub mod ast_transform; 14pub mod ast_transform;
15 15
16use either::Either;
16use hir::db::HirDatabase; 17use hir::db::HirDatabase;
17use ra_db::FileRange; 18use ra_db::FileRange;
18use ra_syntax::{TextRange, TextUnit}; 19use ra_syntax::{TextRange, TextUnit};
@@ -35,11 +36,27 @@ pub struct AssistLabel {
35 36
36#[derive(Debug, Clone)] 37#[derive(Debug, Clone)]
37pub struct AssistAction { 38pub 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)]
46pub struct ResolvedAssist {
47 pub label: AssistLabel,
48 pub action_data: Either<AssistAction, Vec<AssistAction>>,
49}
50
51impl 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.
67pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> 84pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist>
68where 85where
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
249impl From<Name> for ModPath { 246impl 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 {
311macro_rules! __path { 308macro_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
3use ra_db::{FilePosition, FileRange}; 3use ra_db::{FilePosition, FileRange};
4 4
5use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; 5use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit};
6 6
7use either::Either;
7pub use ra_assists::AssistId; 8pub use ra_assists::AssistId;
9use ra_assists::{AssistAction, AssistLabel};
8 10
9#[derive(Debug)] 11#[derive(Debug)]
10pub struct Assist { 12pub 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
15pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { 18pub(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
43fn 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" }
28ra_vfs_glob = { path = "../ra_vfs_glob" } 28ra_vfs_glob = { path = "../ra_vfs_glob" }
29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } 29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] }
30ra_cargo_watch = { path = "../ra_cargo_watch" } 30ra_cargo_watch = { path = "../ra_cargo_watch" }
31either = "1.5"
31 32
32[dev-dependencies] 33[dev-dependencies]
33tempfile = "3" 34tempfile = "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
4use std::{fmt::Write as _, io::Write as _}; 4use std::{fmt::Write as _, io::Write as _};
5 5
6use either::Either;
6use lsp_server::ErrorCode; 7use lsp_server::ErrorCode;
7use lsp_types::{ 8use 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
210impl 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
224impl 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]
233pub fn replace_descendants<N: AstNode, D: AstNode>( 271pub 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> {
348fn insert_children<N: AstNode>( 387fn 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
396fn 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]
358fn replace_children<N: AstNode>( 402fn 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.
3use itertools::Itertools; 3use itertools::Itertools;
4 4
5use crate::{algo, ast, AstNode, SourceFile}; 5use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken};
6 6
7pub fn name(text: &str) -> ast::Name { 7pub 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 {
21fn path_from_text(text: &str) -> ast::Path { 21fn path_from_text(text: &str) -> ast::Path {
22 ast_from_text(text) 22 ast_from_text(text)
23} 23}
24pub 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
39pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { 25pub 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
170pub 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
184fn ast_from_text<N: AstNode>(text: &str) -> N { 180fn 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
189pub mod tokens { 185pub 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
42function 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
42function reload(ctx: Ctx): Cmd { 54function 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);