aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-02-24 12:52:44 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-02-24 12:52:44 +0000
commit043991662c648d86876be44bc3405ee3a60c37a1 (patch)
tree45fcbfb58cce8650f67ce4f41e1b5564ef49bfb3 /crates
parentf6f160391db945a0dcc2f73b38926d6919f7c566 (diff)
parentc110e72a115bbec36413bd440812dfe9194c58e2 (diff)
Merge #889
889: Refactor assits r=matklad a=matklad * assign unique IDs to assists so that clients could do custom stuff * specify kinds for assists, * make introduce_variable a `refactoring.extract` and make it available only when expression is selected * introduce marks to assists Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/Cargo.toml2
-rw-r--r--crates/ra_assists/src/add_derive.rs4
-rw-r--r--crates/ra_assists/src/add_impl.rs4
-rw-r--r--crates/ra_assists/src/assist_ctx.rs5
-rw-r--r--crates/ra_assists/src/auto_import.rs16
-rw-r--r--crates/ra_assists/src/change_visibility.rs8
-rw-r--r--crates/ra_assists/src/fill_match_arms.rs4
-rw-r--r--crates/ra_assists/src/flip_comma.rs4
-rw-r--r--crates/ra_assists/src/introduce_variable.rs146
-rw-r--r--crates/ra_assists/src/lib.rs27
-rw-r--r--crates/ra_assists/src/marks.rs5
-rw-r--r--crates/ra_assists/src/remove_dbg.rs4
-rw-r--r--crates/ra_assists/src/replace_if_let_with_match.rs4
-rw-r--r--crates/ra_assists/src/split_import.rs4
-rw-r--r--crates/ra_ide_api/src/assists.rs16
-rw-r--r--crates/ra_ide_api/src/lib.rs3
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs48
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/main.rs11
18 files changed, 182 insertions, 133 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index 880bc4b18..d4056a349 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -13,6 +13,4 @@ ra_text_edit = { path = "../ra_text_edit" }
13ra_fmt = { path = "../ra_fmt" } 13ra_fmt = { path = "../ra_fmt" }
14ra_db = { path = "../ra_db" } 14ra_db = { path = "../ra_db" }
15hir = { path = "../ra_hir", package = "ra_hir" } 15hir = { path = "../ra_hir", package = "ra_hir" }
16
17[dev-dependencies]
18test_utils = { path = "../test_utils" } 16test_utils = { path = "../test_utils" }
diff --git a/crates/ra_assists/src/add_derive.rs b/crates/ra_assists/src/add_derive.rs
index 0556dd69c..e91b5eb8d 100644
--- a/crates/ra_assists/src/add_derive.rs
+++ b/crates/ra_assists/src/add_derive.rs
@@ -5,12 +5,12 @@ use ra_syntax::{
5 TextUnit, 5 TextUnit,
6}; 6};
7 7
8use crate::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist, AssistId};
9 9
10pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?; 11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
12 let node_start = derive_insertion_offset(nominal)?; 12 let node_start = derive_insertion_offset(nominal)?;
13 ctx.add_action("add `#[derive]`", |edit| { 13 ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| {
14 let derive_attr = nominal 14 let derive_attr = nominal
15 .attrs() 15 .attrs()
16 .filter_map(|x| x.as_call()) 16 .filter_map(|x| x.as_call())
diff --git a/crates/ra_assists/src/add_impl.rs b/crates/ra_assists/src/add_impl.rs
index b40b9cc0c..b292f188d 100644
--- a/crates/ra_assists/src/add_impl.rs
+++ b/crates/ra_assists/src/add_impl.rs
@@ -5,12 +5,12 @@ use ra_syntax::{
5 TextUnit, 5 TextUnit,
6}; 6};
7 7
8use crate::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist, AssistId};
9 9
10pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?; 11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
12 let name = nominal.name()?; 12 let name = nominal.name()?;
13 ctx.add_action("add impl", |edit| { 13 ctx.add_action(AssistId("add_impl"), "add impl", |edit| {
14 edit.target(nominal.syntax().range()); 14 edit.target(nominal.syntax().range());
15 let type_params = nominal.type_param_list(); 15 let type_params = nominal.type_param_list();
16 let start_offset = nominal.syntax().range().end(); 16 let start_offset = nominal.syntax().range().end();
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index e9c4f0a23..4ad21c74b 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -7,7 +7,7 @@ use ra_syntax::{
7}; 7};
8use ra_fmt::{leading_indent, reindent}; 8use ra_fmt::{leading_indent, reindent};
9 9
10use crate::{AssistLabel, AssistAction}; 10use crate::{AssistLabel, AssistAction, AssistId};
11 11
12#[derive(Clone, Debug)] 12#[derive(Clone, Debug)]
13pub(crate) enum Assist { 13pub(crate) enum Assist {
@@ -81,10 +81,11 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
81 81
82 pub(crate) fn add_action( 82 pub(crate) fn add_action(
83 &mut self, 83 &mut self,
84 id: AssistId,
84 label: impl Into<String>, 85 label: impl Into<String>,
85 f: impl FnOnce(&mut AssistBuilder), 86 f: impl FnOnce(&mut AssistBuilder),
86 ) -> &mut Self { 87 ) -> &mut Self {
87 let label = AssistLabel { label: label.into() }; 88 let label = AssistLabel { label: label.into(), id };
88 match &mut self.assist { 89 match &mut self.assist {
89 Assist::Unresolved(labels) => labels.push(label), 90 Assist::Unresolved(labels) => labels.push(label),
90 Assist::Resolved(labels_actions) => { 91 Assist::Resolved(labels_actions) => {
diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs
index 105c888d5..685dbed06 100644
--- a/crates/ra_assists/src/auto_import.rs
+++ b/crates/ra_assists/src/auto_import.rs
@@ -4,7 +4,10 @@ use ra_syntax::{
4 ast::{ self, NameOwner }, AstNode, SyntaxNode, Direction, TextRange, 4 ast::{ self, NameOwner }, AstNode, SyntaxNode, Direction, TextRange,
5 SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA } 5 SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA }
6}; 6};
7use crate::assist_ctx::{AssistCtx, Assist, AssistBuilder}; 7use crate::{
8 AssistId,
9 assist_ctx::{AssistCtx, Assist, AssistBuilder},
10};
8 11
9fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> { 12fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> {
10 let mut v = Vec::new(); 13 let mut v = Vec::new();
@@ -526,6 +529,7 @@ pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
526 if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { 529 if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) {
527 if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { 530 if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) {
528 ctx.add_action( 531 ctx.add_action(
532 AssistId("auto_import"),
529 format!("import {} in mod {}", fmt_segments(&segments), name.text()), 533 format!("import {} in mod {}", fmt_segments(&segments), name.text()),
530 |edit| { 534 |edit| {
531 apply_auto_import(item_list.syntax(), path, &segments, edit); 535 apply_auto_import(item_list.syntax(), path, &segments, edit);
@@ -534,9 +538,13 @@ pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
534 } 538 }
535 } else { 539 } else {
536 let current_file = node.ancestors().find_map(ast::SourceFile::cast)?; 540 let current_file = node.ancestors().find_map(ast::SourceFile::cast)?;
537 ctx.add_action(format!("import {} in the current file", fmt_segments(&segments)), |edit| { 541 ctx.add_action(
538 apply_auto_import(current_file.syntax(), path, &segments, edit); 542 AssistId("auto_import"),
539 }); 543 format!("import {} in the current file", fmt_segments(&segments)),
544 |edit| {
545 apply_auto_import(current_file.syntax(), path, &segments, edit);
546 },
547 );
540 } 548 }
541 549
542 ctx.build() 550 ctx.build()
diff --git a/crates/ra_assists/src/change_visibility.rs b/crates/ra_assists/src/change_visibility.rs
index c2ba897a4..50c1be5ae 100644
--- a/crates/ra_assists/src/change_visibility.rs
+++ b/crates/ra_assists/src/change_visibility.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT, WHITESPACE, COMMENT, ATTR}, 5 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT, WHITESPACE, COMMENT, ATTR},
6}; 6};
7 7
8use crate::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist, AssistId};
9 9
10pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { 11 if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() {
@@ -41,7 +41,7 @@ fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
41 (vis_offset(field.syntax()), ident.range()) 41 (vis_offset(field.syntax()), ident.range())
42 }; 42 };
43 43
44 ctx.add_action("make pub(crate)", |edit| { 44 ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| {
45 edit.target(target); 45 edit.target(target);
46 edit.insert(offset, "pub(crate) "); 46 edit.insert(offset, "pub(crate) ");
47 edit.set_cursor(offset); 47 edit.set_cursor(offset);
@@ -63,7 +63,7 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
63 63
64fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> { 64fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
65 if vis.syntax().text() == "pub" { 65 if vis.syntax().text() == "pub" {
66 ctx.add_action("change to pub(crate)", |edit| { 66 ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| {
67 edit.target(vis.syntax().range()); 67 edit.target(vis.syntax().range());
68 edit.replace(vis.syntax().range(), "pub(crate)"); 68 edit.replace(vis.syntax().range(), "pub(crate)");
69 edit.set_cursor(vis.syntax().range().start()) 69 edit.set_cursor(vis.syntax().range().start())
@@ -72,7 +72,7 @@ fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Op
72 return ctx.build(); 72 return ctx.build();
73 } 73 }
74 if vis.syntax().text() == "pub(crate)" { 74 if vis.syntax().text() == "pub(crate)" {
75 ctx.add_action("change to pub", |edit| { 75 ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| {
76 edit.target(vis.syntax().range()); 76 edit.target(vis.syntax().range());
77 edit.replace(vis.syntax().range(), "pub"); 77 edit.replace(vis.syntax().range(), "pub");
78 edit.set_cursor(vis.syntax().range().start()); 78 edit.set_cursor(vis.syntax().range().start());
diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs
index 6bf6e7332..30020b56e 100644
--- a/crates/ra_assists/src/fill_match_arms.rs
+++ b/crates/ra_assists/src/fill_match_arms.rs
@@ -6,7 +6,7 @@ use hir::{
6}; 6};
7use ra_syntax::ast::{self, AstNode}; 7use ra_syntax::ast::{self, AstNode};
8 8
9use crate::{AssistCtx, Assist}; 9use crate::{AssistCtx, Assist, AssistId};
10 10
11pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 11pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; 12 let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
@@ -37,7 +37,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As
37 let enum_name = enum_def.name(ctx.db)?; 37 let enum_name = enum_def.name(ctx.db)?;
38 let db = ctx.db; 38 let db = ctx.db;
39 39
40 ctx.add_action("fill match arms", |edit| { 40 ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| {
41 let mut buf = format!("match {} {{\n", expr.syntax().text().to_string()); 41 let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
42 let variants = enum_def.variants(db); 42 let variants = enum_def.variants(db);
43 for variant in variants { 43 for variant in variants {
diff --git a/crates/ra_assists/src/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs
index 0d4a789fc..6b98cac68 100644
--- a/crates/ra_assists/src/flip_comma.rs
+++ b/crates/ra_assists/src/flip_comma.rs
@@ -5,13 +5,13 @@ use ra_syntax::{
5 algo::non_trivia_sibling, 5 algo::non_trivia_sibling,
6}; 6};
7 7
8use crate::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist, AssistId};
9 9
10pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; 11 let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?;
12 let prev = non_trivia_sibling(comma, Direction::Prev)?; 12 let prev = non_trivia_sibling(comma, Direction::Prev)?;
13 let next = non_trivia_sibling(comma, Direction::Next)?; 13 let next = non_trivia_sibling(comma, Direction::Next)?;
14 ctx.add_action("flip comma", |edit| { 14 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| {
15 edit.target(comma.range()); 15 edit.target(comma.range());
16 edit.replace(prev.range(), next.text()); 16 edit.replace(prev.range(), next.text());
17 edit.replace(next.range(), prev.text()); 17 edit.replace(next.range(), prev.text());
diff --git a/crates/ra_assists/src/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs
index f0e012105..353bc4105 100644
--- a/crates/ra_assists/src/introduce_variable.rs
+++ b/crates/ra_assists/src/introduce_variable.rs
@@ -1,3 +1,4 @@
1use test_utils::tested_by;
1use hir::db::HirDatabase; 2use hir::db::HirDatabase;
2use ra_syntax::{ 3use ra_syntax::{
3 ast::{self, AstNode}, 4 ast::{self, AstNode},
@@ -6,20 +7,24 @@ use ra_syntax::{
6 }, SyntaxNode, TextUnit, 7 }, SyntaxNode, TextUnit,
7}; 8};
8 9
9use crate::{AssistCtx, Assist}; 10use crate::{AssistCtx, Assist, AssistId};
10 11
11pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 12pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
13 if ctx.frange.range.is_empty() {
14 return None;
15 }
12 let node = ctx.covering_node(); 16 let node = ctx.covering_node();
13 if !valid_covering_node(node) { 17 if node.kind() == COMMENT {
18 tested_by!(introduce_var_in_comment_is_not_applicable);
14 return None; 19 return None;
15 } 20 }
16 let expr = node.ancestors().filter_map(valid_target_expr).next()?; 21 let expr = node.ancestors().find_map(valid_target_expr)?;
17 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr)?; 22 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr)?;
18 let indent = anchor_stmt.prev_sibling()?; 23 let indent = anchor_stmt.prev_sibling()?;
19 if indent.kind() != WHITESPACE { 24 if indent.kind() != WHITESPACE {
20 return None; 25 return None;
21 } 26 }
22 ctx.add_action("introduce variable", move |edit| { 27 ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| {
23 let mut buf = String::new(); 28 let mut buf = String::new();
24 29
25 let cursor_offset = if wrap_in_block { 30 let cursor_offset = if wrap_in_block {
@@ -38,6 +43,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option
38 false 43 false
39 }; 44 };
40 if is_full_stmt { 45 if is_full_stmt {
46 tested_by!(test_introduce_var_expr_stmt);
41 if !full_stmt.unwrap().has_semi() { 47 if !full_stmt.unwrap().has_semi() {
42 buf.push_str(";"); 48 buf.push_str(";");
43 } 49 }
@@ -73,9 +79,6 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option
73 ctx.build() 79 ctx.build()
74} 80}
75 81
76fn valid_covering_node(node: &SyntaxNode) -> bool {
77 node.kind() != COMMENT
78}
79/// Check whether the node is a valid expression which can be extracted to a variable. 82/// Check whether the node is a valid expression which can be extracted to a variable.
80/// In general that's true for any expression, but in some cases that would produce invalid code. 83/// In general that's true for any expression, but in some cases that would produce invalid code.
81fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { 84fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
@@ -101,6 +104,7 @@ fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
101 104
102 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { 105 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
103 if expr.syntax() == node { 106 if expr.syntax() == node {
107 tested_by!(test_introduce_var_last_expr);
104 return Some((node, false)); 108 return Some((node, false));
105 } 109 }
106 } 110 }
@@ -117,8 +121,11 @@ fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
117 121
118#[cfg(test)] 122#[cfg(test)]
119mod tests { 123mod tests {
124 use test_utils::covers;
125
126 use crate::helpers::{check_assist_range_not_applicable, check_assist_range, check_assist_range_target};
127
120 use super::*; 128 use super::*;
121 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range, check_assist_target, check_assist_range_target};
122 129
123 #[test] 130 #[test]
124 fn test_introduce_var_simple() { 131 fn test_introduce_var_simple() {
@@ -137,7 +144,17 @@ fn foo() {
137 } 144 }
138 145
139 #[test] 146 #[test]
147 fn introduce_var_in_comment_is_not_applicable() {
148 covers!(introduce_var_in_comment_is_not_applicable);
149 check_assist_range_not_applicable(
150 introduce_variable,
151 "fn main() { 1 + /* <|>comment<|> */ 1; }",
152 );
153 }
154
155 #[test]
140 fn test_introduce_var_expr_stmt() { 156 fn test_introduce_var_expr_stmt() {
157 covers!(test_introduce_var_expr_stmt);
141 check_assist_range( 158 check_assist_range(
142 introduce_variable, 159 introduce_variable,
143 " 160 "
@@ -149,6 +166,19 @@ fn foo() {
149 let <|>var_name = 1 + 1; 166 let <|>var_name = 1 + 1;
150}", 167}",
151 ); 168 );
169 check_assist_range(
170 introduce_variable,
171 "
172fn foo() {
173 <|>{ let x = 0; x }<|>
174 something_else();
175}",
176 "
177fn foo() {
178 let <|>var_name = { let x = 0; x };
179 something_else();
180}",
181 );
152 } 182 }
153 183
154 #[test] 184 #[test]
@@ -169,6 +199,7 @@ fn foo() {
169 199
170 #[test] 200 #[test]
171 fn test_introduce_var_last_expr() { 201 fn test_introduce_var_last_expr() {
202 covers!(test_introduce_var_last_expr);
172 check_assist_range( 203 check_assist_range(
173 introduce_variable, 204 introduce_variable,
174 " 205 "
@@ -181,10 +212,6 @@ fn foo() {
181 bar(var_name) 212 bar(var_name)
182}", 213}",
183 ); 214 );
184 }
185
186 #[test]
187 fn test_introduce_var_last_full_expr() {
188 check_assist_range( 215 check_assist_range(
189 introduce_variable, 216 introduce_variable,
190 " 217 "
@@ -196,24 +223,7 @@ fn foo() {
196 let <|>var_name = bar(1 + 1); 223 let <|>var_name = bar(1 + 1);
197 var_name 224 var_name
198}", 225}",
199 ); 226 )
200 }
201
202 #[test]
203 fn test_introduce_var_block_expr_second_to_last() {
204 check_assist_range(
205 introduce_variable,
206 "
207fn foo() {
208 <|>{ let x = 0; x }<|>
209 something_else();
210}",
211 "
212fn foo() {
213 let <|>var_name = { let x = 0; x };
214 something_else();
215}",
216 );
217 } 227 }
218 228
219 #[test] 229 #[test]
@@ -309,11 +319,11 @@ fn main() {
309 319
310 #[test] 320 #[test]
311 fn test_introduce_var_path_simple() { 321 fn test_introduce_var_path_simple() {
312 check_assist( 322 check_assist_range(
313 introduce_variable, 323 introduce_variable,
314 " 324 "
315fn main() { 325fn main() {
316 let o = S<|>ome(true); 326 let o = <|>Some(true)<|>;
317} 327}
318", 328",
319 " 329 "
@@ -327,11 +337,11 @@ fn main() {
327 337
328 #[test] 338 #[test]
329 fn test_introduce_var_path_method() { 339 fn test_introduce_var_path_method() {
330 check_assist( 340 check_assist_range(
331 introduce_variable, 341 introduce_variable,
332 " 342 "
333fn main() { 343fn main() {
334 let v = b<|>ar.foo(); 344 let v = <|>bar.foo()<|>;
335} 345}
336", 346",
337 " 347 "
@@ -345,11 +355,11 @@ fn main() {
345 355
346 #[test] 356 #[test]
347 fn test_introduce_var_return() { 357 fn test_introduce_var_return() {
348 check_assist( 358 check_assist_range(
349 introduce_variable, 359 introduce_variable,
350 " 360 "
351fn foo() -> u32 { 361fn foo() -> u32 {
352 r<|>eturn 2 + 2; 362 <|>return 2 + 2<|>;
353} 363}
354", 364",
355 " 365 "
@@ -363,13 +373,13 @@ fn foo() -> u32 {
363 373
364 #[test] 374 #[test]
365 fn test_introduce_var_does_not_add_extra_whitespace() { 375 fn test_introduce_var_does_not_add_extra_whitespace() {
366 check_assist( 376 check_assist_range(
367 introduce_variable, 377 introduce_variable,
368 " 378 "
369fn foo() -> u32 { 379fn foo() -> u32 {
370 380
371 381
372 r<|>eturn 2 + 2; 382 <|>return 2 + 2<|>;
373} 383}
374", 384",
375 " 385 "
@@ -382,12 +392,12 @@ fn foo() -> u32 {
382", 392",
383 ); 393 );
384 394
385 check_assist( 395 check_assist_range(
386 introduce_variable, 396 introduce_variable,
387 " 397 "
388fn foo() -> u32 { 398fn foo() -> u32 {
389 399
390 r<|>eturn 2 + 2; 400 <|>return 2 + 2<|>;
391} 401}
392", 402",
393 " 403 "
@@ -399,7 +409,7 @@ fn foo() -> u32 {
399", 409",
400 ); 410 );
401 411
402 check_assist( 412 check_assist_range(
403 introduce_variable, 413 introduce_variable,
404 " 414 "
405fn foo() -> u32 { 415fn foo() -> u32 {
@@ -408,7 +418,7 @@ fn foo() -> u32 {
408 // bar 418 // bar
409 419
410 420
411 r<|>eturn 2 + 2; 421 <|>return 2 + 2<|>;
412} 422}
413", 423",
414 " 424 "
@@ -427,12 +437,12 @@ fn foo() -> u32 {
427 437
428 #[test] 438 #[test]
429 fn test_introduce_var_break() { 439 fn test_introduce_var_break() {
430 check_assist( 440 check_assist_range(
431 introduce_variable, 441 introduce_variable,
432 " 442 "
433fn main() { 443fn main() {
434 let result = loop { 444 let result = loop {
435 b<|>reak 2 + 2; 445 <|>break 2 + 2<|>;
436 }; 446 };
437} 447}
438", 448",
@@ -449,11 +459,11 @@ fn main() {
449 459
450 #[test] 460 #[test]
451 fn test_introduce_var_for_cast() { 461 fn test_introduce_var_for_cast() {
452 check_assist( 462 check_assist_range(
453 introduce_variable, 463 introduce_variable,
454 " 464 "
455fn main() { 465fn main() {
456 let v = 0f32 a<|>s u32; 466 let v = <|>0f32 as u32<|>;
457} 467}
458", 468",
459 " 469 "
@@ -467,57 +477,23 @@ fn main() {
467 477
468 #[test] 478 #[test]
469 fn test_introduce_var_for_return_not_applicable() { 479 fn test_introduce_var_for_return_not_applicable() {
470 check_assist_not_applicable( 480 check_assist_range_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } ");
471 introduce_variable,
472 "
473fn foo() {
474 r<|>eturn;
475}
476",
477 );
478 } 481 }
479 482
480 #[test] 483 #[test]
481 fn test_introduce_var_for_break_not_applicable() { 484 fn test_introduce_var_for_break_not_applicable() {
482 check_assist_not_applicable( 485 check_assist_range_not_applicable(
483 introduce_variable,
484 "
485fn main() {
486 loop {
487 b<|>reak;
488 };
489}
490",
491 );
492 }
493
494 #[test]
495 fn test_introduce_var_in_comment_not_applicable() {
496 check_assist_not_applicable(
497 introduce_variable, 486 introduce_variable,
498 " 487 "fn main() { loop { <|>break<|>; }; }",
499fn main() {
500 let x = true;
501 let tuple = match x {
502 // c<|>omment
503 true => (2 + 2, true)
504 _ => (0, false)
505 };
506}
507",
508 ); 488 );
509 } 489 }
510 490
511 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic 491 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
512 #[test] 492 #[test]
513 fn introduce_var_target() { 493 fn introduce_var_target() {
514 check_assist_target( 494 check_assist_range_target(
515 introduce_variable, 495 introduce_variable,
516 " 496 "fn foo() -> u32 { <|>return 2 + 2<|>; }",
517fn foo() -> u32 {
518 r<|>eturn 2 + 2;
519}
520",
521 "2 + 2", 497 "2 + 2",
522 ); 498 );
523 499
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index e1e899edc..6c3d75d79 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -6,6 +6,7 @@
6//! becomes available. 6//! becomes available.
7 7
8mod assist_ctx; 8mod assist_ctx;
9mod marks;
9 10
10use itertools::Itertools; 11use itertools::Itertools;
11 12
@@ -16,10 +17,16 @@ use hir::db::HirDatabase;
16 17
17pub(crate) use crate::assist_ctx::{AssistCtx, Assist}; 18pub(crate) use crate::assist_ctx::{AssistCtx, Assist};
18 19
20/// Unique identifier of the assist, should not be shown to the user
21/// directly.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct AssistId(pub &'static str);
24
19#[derive(Debug, Clone)] 25#[derive(Debug, Clone)]
20pub struct AssistLabel { 26pub struct AssistLabel {
21 /// Short description of the assist, as shown in the UI. 27 /// Short description of the assist, as shown in the UI.
22 pub label: String, 28 pub label: String,
29 pub id: AssistId,
23} 30}
24 31
25#[derive(Debug, Clone)] 32#[derive(Debug, Clone)]
@@ -253,6 +260,17 @@ mod helpers {
253 let assist = AssistCtx::with_ctx(&db, frange, true, assist); 260 let assist = AssistCtx::with_ctx(&db, frange, true, assist);
254 assert!(assist.is_none()); 261 assert!(assist.is_none());
255 } 262 }
263
264 pub(crate) fn check_assist_range_not_applicable(
265 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
266 before: &str,
267 ) {
268 let (range, before) = extract_range(before);
269 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
270 let frange = FileRange { file_id, range };
271 let assist = AssistCtx::with_ctx(&db, frange, true, assist);
272 assert!(assist.is_none());
273 }
256} 274}
257 275
258#[cfg(test)] 276#[cfg(test)]
@@ -260,7 +278,7 @@ mod tests {
260 use hir::mock::MockDatabase; 278 use hir::mock::MockDatabase;
261 use ra_syntax::TextRange; 279 use ra_syntax::TextRange;
262 use ra_db::FileRange; 280 use ra_db::FileRange;
263 use test_utils::{extract_offset}; 281 use test_utils::{extract_offset, extract_range};
264 282
265 #[test] 283 #[test]
266 fn assist_order_field_struct() { 284 fn assist_order_field_struct() {
@@ -280,16 +298,15 @@ mod tests {
280 fn assist_order_if_expr() { 298 fn assist_order_if_expr() {
281 let before = " 299 let before = "
282 pub fn test_some_range(a: int) -> bool { 300 pub fn test_some_range(a: int) -> bool {
283 if let 2..6 = 5<|> { 301 if let 2..6 = <|>5<|> {
284 true 302 true
285 } else { 303 } else {
286 false 304 false
287 } 305 }
288 }"; 306 }";
289 let (before_cursor_pos, before) = extract_offset(before); 307 let (range, before) = extract_range(before);
290 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); 308 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
291 let frange = 309 let frange = FileRange { file_id, range };
292 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
293 let assists = super::assists(&db, frange); 310 let assists = super::assists(&db, frange);
294 let mut assists = assists.iter(); 311 let mut assists = assists.iter();
295 312
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
new file mode 100644
index 000000000..a29f9f658
--- /dev/null
+++ b/crates/ra_assists/src/marks.rs
@@ -0,0 +1,5 @@
1test_utils::marks!(
2 introduce_var_in_comment_is_not_applicable
3 test_introduce_var_expr_stmt
4 test_introduce_var_last_expr
5);
diff --git a/crates/ra_assists/src/remove_dbg.rs b/crates/ra_assists/src/remove_dbg.rs
index 2bed270a1..6ea48d909 100644
--- a/crates/ra_assists/src/remove_dbg.rs
+++ b/crates/ra_assists/src/remove_dbg.rs
@@ -6,7 +6,7 @@ use ra_syntax::{
6 L_PAREN, R_PAREN, L_CURLY, R_CURLY, L_BRACK, R_BRACK, EXCL 6 L_PAREN, R_PAREN, L_CURLY, R_CURLY, L_BRACK, R_BRACK, EXCL
7 }, 7 },
8}; 8};
9use crate::{AssistCtx, Assist}; 9use crate::{AssistCtx, Assist, AssistId};
10 10
11pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 11pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let macro_call = ctx.node_at_offset::<ast::MacroCall>()?; 12 let macro_call = ctx.node_at_offset::<ast::MacroCall>()?;
@@ -46,7 +46,7 @@ pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
46 macro_args.text().slice(start..end).to_string() 46 macro_args.text().slice(start..end).to_string()
47 }; 47 };
48 48
49 ctx.add_action("remove dbg!()", |edit| { 49 ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| {
50 edit.target(macro_call.syntax().range()); 50 edit.target(macro_call.syntax().range());
51 edit.replace(macro_range, macro_content); 51 edit.replace(macro_range, macro_content);
52 edit.set_cursor(cursor_pos); 52 edit.set_cursor(cursor_pos);
diff --git a/crates/ra_assists/src/replace_if_let_with_match.rs b/crates/ra_assists/src/replace_if_let_with_match.rs
index 87a2c1185..230573499 100644
--- a/crates/ra_assists/src/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/replace_if_let_with_match.rs
@@ -2,7 +2,7 @@ use ra_syntax::{AstNode, ast};
2use ra_fmt::extract_trivial_expression; 2use ra_fmt::extract_trivial_expression;
3use hir::db::HirDatabase; 3use hir::db::HirDatabase;
4 4
5use crate::{AssistCtx, Assist}; 5use crate::{AssistCtx, Assist, AssistId};
6 6
7pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 7pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
8 let if_expr: &ast::IfExpr = ctx.node_at_offset()?; 8 let if_expr: &ast::IfExpr = ctx.node_at_offset()?;
@@ -15,7 +15,7 @@ pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) ->
15 ast::ElseBranchFlavor::IfExpr(_) => return None, 15 ast::ElseBranchFlavor::IfExpr(_) => return None,
16 }; 16 };
17 17
18 ctx.add_action("replace with match", |edit| { 18 ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| {
19 let match_expr = build_match_expr(expr, pat, then_block, else_block); 19 let match_expr = build_match_expr(expr, pat, then_block, else_block);
20 edit.target(if_expr.syntax().range()); 20 edit.target(if_expr.syntax().range());
21 edit.replace_node_and_indent(if_expr.syntax(), match_expr); 21 edit.replace_node_and_indent(if_expr.syntax(), match_expr);
diff --git a/crates/ra_assists/src/split_import.rs b/crates/ra_assists/src/split_import.rs
index f043be636..dd5be4e91 100644
--- a/crates/ra_assists/src/split_import.rs
+++ b/crates/ra_assists/src/split_import.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 algo::generate, 5 algo::generate,
6}; 6};
7 7
8use crate::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist, AssistId};
9 9
10pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let colon_colon = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COLONCOLON)?; 11 let colon_colon = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COLONCOLON)?;
@@ -23,7 +23,7 @@ pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis
23 None => top_path.syntax().range().end(), 23 None => top_path.syntax().range().end(),
24 }; 24 };
25 25
26 ctx.add_action("split import", |edit| { 26 ctx.add_action(AssistId("split_import"), "split import", |edit| {
27 edit.target(colon_colon.range()); 27 edit.target(colon_colon.range());
28 edit.insert(l_curly, "{"); 28 edit.insert(l_curly, "{");
29 edit.insert(r_curly, "}"); 29 edit.insert(r_curly, "}");
diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs
index 7a9c66681..3c0475a51 100644
--- a/crates/ra_ide_api/src/assists.rs
+++ b/crates/ra_ide_api/src/assists.rs
@@ -2,20 +2,30 @@ use ra_db::{FileRange, FilePosition};
2 2
3use crate::{SourceFileEdit, SourceChange, db::RootDatabase}; 3use crate::{SourceFileEdit, SourceChange, db::RootDatabase};
4 4
5pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<SourceChange> { 5pub use ra_assists::AssistId;
6
7#[derive(Debug)]
8pub struct Assist {
9 pub id: AssistId,
10 pub change: SourceChange,
11}
12
13pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
6 ra_assists::assists(db, frange) 14 ra_assists::assists(db, frange)
7 .into_iter() 15 .into_iter()
8 .map(|(label, action)| { 16 .map(|(label, action)| {
9 let file_id = frange.file_id; 17 let file_id = frange.file_id;
10 let file_edit = SourceFileEdit { file_id, edit: action.edit }; 18 let file_edit = SourceFileEdit { file_id, edit: action.edit };
11 SourceChange { 19 let id = label.id;
20 let change = SourceChange {
12 label: label.label, 21 label: label.label,
13 source_file_edits: vec![file_edit], 22 source_file_edits: vec![file_edit],
14 file_system_edits: vec![], 23 file_system_edits: vec![],
15 cursor_position: action 24 cursor_position: action
16 .cursor_position 25 .cursor_position
17 .map(|offset| FilePosition { offset, file_id }), 26 .map(|offset| FilePosition { offset, file_id }),
18 } 27 };
28 Assist { id, change }
19 }) 29 })
20 .collect() 30 .collect()
21} 31}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 4b9fc9372..076a8396c 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -57,6 +57,7 @@ pub use crate::{
57 runnables::{Runnable, RunnableKind}, 57 runnables::{Runnable, RunnableKind},
58 navigation_target::NavigationTarget, 58 navigation_target::NavigationTarget,
59 references::ReferenceSearchResult, 59 references::ReferenceSearchResult,
60 assists::{Assist, AssistId},
60}; 61};
61pub use ra_ide_api_light::{ 62pub use ra_ide_api_light::{
62 Fold, FoldKind, HighlightedRange, Severity, StructureNode, LocalEdit, 63 Fold, FoldKind, HighlightedRange, Severity, StructureNode, LocalEdit,
@@ -368,7 +369,7 @@ impl Analysis {
368 369
369 /// Computes assists (aks code actons aka intentions) for the given 370 /// Computes assists (aks code actons aka intentions) for the given
370 /// position. 371 /// position.
371 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> { 372 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
372 self.with_db(|db| assists::assists(db, frange)) 373 self.with_db(|db| assists::assists(db, frange))
373 } 374 }
374 375
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 9abd4054e..5da731801 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -1,6 +1,6 @@
1use gen_lsp_server::ErrorCode; 1use gen_lsp_server::ErrorCode;
2use lsp_types::{ 2use lsp_types::{
3 CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity, 3 CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity, CodeAction,
4 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, 4 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange,
5 FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, 5 FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent,
6 MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, 6 MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range,
@@ -9,6 +9,7 @@ use lsp_types::{
9}; 9};
10use ra_ide_api::{ 10use ra_ide_api::{
11 FileId, FilePosition, FileRange, FoldKind, Query, RangeInfo, RunnableKind, Severity, Cancelable, 11 FileId, FilePosition, FileRange, FoldKind, Query, RangeInfo, RunnableKind, Severity, Cancelable,
12 AssistId,
12}; 13};
13use ra_syntax::{AstNode, SyntaxKind, TextUnit}; 14use ra_syntax::{AstNode, SyntaxKind, TextUnit};
14use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
@@ -576,28 +577,57 @@ pub fn handle_code_action(
576 let range = params.range.conv_with(&line_index); 577 let range = params.range.conv_with(&line_index);
577 578
578 let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter(); 579 let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
579 let fixes = world 580 let diagnostics = world.analysis().diagnostics(file_id)?;
580 .analysis() 581 let mut res: Vec<CodeAction> = Vec::new();
581 .diagnostics(file_id)? 582
583 let fixes_from_diagnostics = diagnostics
582 .into_iter() 584 .into_iter()
583 .filter_map(|d| Some((d.range, d.fix?))) 585 .filter_map(|d| Some((d.range, d.fix?)))
584 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some()) 586 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
585 .map(|(_range, fix)| fix); 587 .map(|(_range, fix)| fix);
586 588
587 let mut res = Vec::new(); 589 for source_edit in fixes_from_diagnostics {
588 for source_edit in assists.chain(fixes) {
589 let title = source_edit.label.clone(); 590 let title = source_edit.label.clone();
590 let edit = source_edit.try_conv_with(&world)?; 591 let edit = source_edit.try_conv_with(&world)?;
591 592
592 let cmd = Command { 593 let command = Command {
594 title,
595 command: "rust-analyzer.applySourceChange".to_string(),
596 arguments: Some(vec![to_value(edit).unwrap()]),
597 };
598 let action = CodeAction {
599 title: command.title.clone(),
600 kind: None,
601 diagnostics: None,
602 edit: None,
603 command: Some(command),
604 };
605 res.push(action);
606 }
607
608 for assist in assists {
609 let title = assist.change.label.clone();
610 let edit = assist.change.try_conv_with(&world)?;
611
612 let command = Command {
593 title, 613 title,
594 command: "rust-analyzer.applySourceChange".to_string(), 614 command: "rust-analyzer.applySourceChange".to_string(),
595 arguments: Some(vec![to_value(edit).unwrap()]), 615 arguments: Some(vec![to_value(edit).unwrap()]),
596 }; 616 };
597 res.push(cmd); 617 let action = CodeAction {
618 title: command.title.clone(),
619 kind: match assist.id {
620 AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
621 _ => None,
622 },
623 diagnostics: None,
624 edit: None,
625 command: Some(command),
626 };
627 res.push(action);
598 } 628 }
599 629
600 Ok(Some(CodeActionResponse::Commands(res))) 630 Ok(Some(CodeActionResponse::Actions(res)))
601} 631}
602 632
603pub fn handle_code_lens( 633pub fn handle_code_lens(
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index e49c87169..996bf8e01 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -225,10 +225,12 @@ fn main() {}
225 context: empty_context(), 225 context: empty_context(),
226 }, 226 },
227 json!([ 227 json!([
228 { 228 {
229 "command": {
229 "arguments": [ 230 "arguments": [
230 { 231 {
231 "cursorPosition": null, 232 "cursorPosition": null,
233 "label": "create module",
232 "workspaceEdit": { 234 "workspaceEdit": {
233 "documentChanges": [ 235 "documentChanges": [
234 { 236 {
@@ -236,13 +238,14 @@ fn main() {}
236 "uri": "file:///[..]/src/bar.rs" 238 "uri": "file:///[..]/src/bar.rs"
237 } 239 }
238 ] 240 ]
239 }, 241 }
240 "label": "create module"
241 } 242 }
242 ], 243 ],
243 "command": "rust-analyzer.applySourceChange", 244 "command": "rust-analyzer.applySourceChange",
244 "title": "create module" 245 "title": "create module"
245 } 246 },
247 "title": "create module"
248 }
246 ]), 249 ]),
247 ); 250 );
248 251