aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_mbe/src/lib.rs177
-rw-r--r--crates/ra_mbe/src/mbe_expander.rs36
-rw-r--r--crates/ra_mbe/src/subtree_parser.rs12
-rw-r--r--crates/ra_mbe/src/syntax_bridge.rs71
-rw-r--r--crates/ra_mbe/src/tt_cursor.rs40
-rw-r--r--crates/ra_parser/src/grammar.rs59
-rw-r--r--crates/ra_parser/src/grammar/expressions.rs44
-rw-r--r--crates/ra_parser/src/lib.rs24
-rw-r--r--crates/ra_parser/src/syntax_kind/generated.rs4
-rw-r--r--crates/ra_syntax/src/ast/generated.rs66
-rw-r--r--crates/ra_syntax/src/grammar.ron15
-rw-r--r--docs/user/README.md27
-rw-r--r--docs/user/features.md8
-rw-r--r--editors/code/package.json22
-rw-r--r--editors/code/src/commands/cargo_watch.ts102
-rw-r--r--editors/code/src/commands/runnables.ts17
-rw-r--r--editors/code/src/commands/watch_status.ts18
-rw-r--r--editors/code/src/extension.ts25
18 files changed, 690 insertions, 77 deletions
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs
index 8d5008d20..d2115bd67 100644
--- a/crates/ra_mbe/src/lib.rs
+++ b/crates/ra_mbe/src/lib.rs
@@ -37,9 +37,19 @@ pub enum ExpandError {
37 NoMatchingRule, 37 NoMatchingRule,
38 UnexpectedToken, 38 UnexpectedToken,
39 BindingError(String), 39 BindingError(String),
40 ConversionError,
40} 41}
41 42
42pub use crate::syntax_bridge::{ast_to_token_tree, token_tree_to_ast_item_list, syntax_node_to_token_tree}; 43pub use crate::syntax_bridge::{
44 ast_to_token_tree,
45 token_tree_to_ast_item_list,
46 syntax_node_to_token_tree,
47 token_tree_to_expr,
48 token_tree_to_pat,
49 token_tree_to_ty,
50 token_tree_to_macro_items,
51 token_tree_to_macro_stmts,
52};
43 53
44/// This struct contains AST for a single `macro_rules` definition. What might 54/// This struct contains AST for a single `macro_rules` definition. What might
45/// be very confusing is that AST has almost exactly the same shape as 55/// be very confusing is that AST has almost exactly the same shape as
@@ -192,23 +202,26 @@ impl_froms!(TokenTree: Leaf, Subtree);
192 pub(crate) fn expand_to_syntax( 202 pub(crate) fn expand_to_syntax(
193 rules: &MacroRules, 203 rules: &MacroRules,
194 invocation: &str, 204 invocation: &str,
195 ) -> ra_syntax::TreeArc<ast::SourceFile> { 205 ) -> ra_syntax::TreeArc<ast::MacroItems> {
196 let expanded = expand(rules, invocation); 206 let expanded = expand(rules, invocation);
197 token_tree_to_ast_item_list(&expanded) 207 token_tree_to_macro_items(&expanded).unwrap()
198 } 208 }
199 209
200 pub(crate) fn assert_expansion(rules: &MacroRules, invocation: &str, expansion: &str) { 210 pub(crate) fn assert_expansion(rules: &MacroRules, invocation: &str, expansion: &str) {
201 let expanded = expand(rules, invocation); 211 let expanded = expand(rules, invocation);
202 assert_eq!(expanded.to_string(), expansion); 212 assert_eq!(expanded.to_string(), expansion);
203 213
204 let tree = token_tree_to_ast_item_list(&expanded); 214 let tree = token_tree_to_macro_items(&expanded);
205 215
206 // Eat all white space by parse it back and forth 216 // Eat all white space by parse it back and forth
207 let expansion = ast::SourceFile::parse(expansion); 217 let expansion = ast::SourceFile::parse(expansion);
208 let expansion = syntax_node_to_token_tree(expansion.syntax()).unwrap().0; 218 let expansion = syntax_node_to_token_tree(expansion.syntax()).unwrap().0;
209 let file = token_tree_to_ast_item_list(&expansion); 219 let file = token_tree_to_macro_items(&expansion);
210 220
211 assert_eq!(tree.syntax().debug_dump().trim(), file.syntax().debug_dump().trim()); 221 assert_eq!(
222 tree.unwrap().syntax().debug_dump().trim(),
223 file.unwrap().syntax().debug_dump().trim()
224 );
212 } 225 }
213 226
214 #[test] 227 #[test]
@@ -346,11 +359,11 @@ impl_froms!(TokenTree: Leaf, Subtree);
346 ", 359 ",
347 ); 360 );
348 let expansion = expand(&rules, "structs!(Foo, Bar)"); 361 let expansion = expand(&rules, "structs!(Foo, Bar)");
349 let tree = token_tree_to_ast_item_list(&expansion); 362 let tree = token_tree_to_macro_items(&expansion);
350 assert_eq!( 363 assert_eq!(
351 tree.syntax().debug_dump().trim(), 364 tree.unwrap().syntax().debug_dump().trim(),
352 r#" 365 r#"
353SOURCE_FILE@[0; 40) 366MACRO_ITEMS@[0; 40)
354 STRUCT_DEF@[0; 20) 367 STRUCT_DEF@[0; 20)
355 STRUCT_KW@[0; 6) "struct" 368 STRUCT_KW@[0; 6) "struct"
356 NAME@[6; 9) 369 NAME@[6; 9)
@@ -444,6 +457,59 @@ SOURCE_FILE@[0; 40)
444 assert_expansion(&rules, "foo! { foo, bar }", "fn foo () {let a = foo ; let b = bar ;}"); 457 assert_expansion(&rules, "foo! { foo, bar }", "fn foo () {let a = foo ; let b = bar ;}");
445 } 458 }
446 459
460 #[test]
461 fn test_tt_to_stmts() {
462 let rules = create_rules(
463 r#"
464 macro_rules! foo {
465 () => {
466 let a = 0;
467 a = 10 + 1;
468 a
469 }
470 }
471"#,
472 );
473
474 let expanded = expand(&rules, "foo!{}");
475 let stmts = token_tree_to_macro_stmts(&expanded);
476
477 assert_eq!(
478 stmts.unwrap().syntax().debug_dump().trim(),
479 r#"MACRO_STMTS@[0; 15)
480 LET_STMT@[0; 7)
481 LET_KW@[0; 3) "let"
482 BIND_PAT@[3; 4)
483 NAME@[3; 4)
484 IDENT@[3; 4) "a"
485 EQ@[4; 5) "="
486 LITERAL@[5; 6)
487 INT_NUMBER@[5; 6) "0"
488 SEMI@[6; 7) ";"
489 EXPR_STMT@[7; 14)
490 BIN_EXPR@[7; 13)
491 PATH_EXPR@[7; 8)
492 PATH@[7; 8)
493 PATH_SEGMENT@[7; 8)
494 NAME_REF@[7; 8)
495 IDENT@[7; 8) "a"
496 EQ@[8; 9) "="
497 BIN_EXPR@[9; 13)
498 LITERAL@[9; 11)
499 INT_NUMBER@[9; 11) "10"
500 PLUS@[11; 12) "+"
501 LITERAL@[12; 13)
502 INT_NUMBER@[12; 13) "1"
503 SEMI@[13; 14) ";"
504 EXPR_STMT@[14; 15)
505 PATH_EXPR@[14; 15)
506 PATH@[14; 15)
507 PATH_SEGMENT@[14; 15)
508 NAME_REF@[14; 15)
509 IDENT@[14; 15) "a""#,
510 );
511 }
512
447 // The following tests are port from intellij-rust directly 513 // The following tests are port from intellij-rust directly
448 // https://github.com/intellij-rust/intellij-rust/blob/c4e9feee4ad46e7953b1948c112533360b6087bb/src/test/kotlin/org/rust/lang/core/macros/RsMacroExpansionTest.kt 514 // https://github.com/intellij-rust/intellij-rust/blob/c4e9feee4ad46e7953b1948c112533360b6087bb/src/test/kotlin/org/rust/lang/core/macros/RsMacroExpansionTest.kt
449 515
@@ -527,7 +593,7 @@ SOURCE_FILE@[0; 40)
527 593
528 assert_eq!( 594 assert_eq!(
529 expand_to_syntax(&rules, "foo! { 1 + 1 }").syntax().debug_dump().trim(), 595 expand_to_syntax(&rules, "foo! { 1 + 1 }").syntax().debug_dump().trim(),
530 r#"SOURCE_FILE@[0; 15) 596 r#"MACRO_ITEMS@[0; 15)
531 FN_DEF@[0; 15) 597 FN_DEF@[0; 15)
532 FN_KW@[0; 2) "fn" 598 FN_KW@[0; 2) "fn"
533 NAME@[2; 5) 599 NAME@[2; 5)
@@ -665,4 +731,95 @@ SOURCE_FILE@[0; 40)
665 } 731 }
666"#, r#"extern crate a ; mod b ; mod c {} use d ; const E : i32 = 0 ; static F : i32 = 0 ; impl G {} struct H ; enum I {Foo} trait J {} fn h () {} extern {} type T = u8 ;"#); 732"#, r#"extern crate a ; mod b ; mod c {} use d ; const E : i32 = 0 ; static F : i32 = 0 ; impl G {} struct H ; enum I {Foo} trait J {} fn h () {} extern {} type T = u8 ;"#);
667 } 733 }
734
735 #[test]
736 fn test_block() {
737 let rules = create_rules(
738 r#"
739 macro_rules! foo {
740 ($ i:block) => { fn foo() $ i }
741 }
742"#,
743 );
744 assert_expansion(&rules, "foo! { { 1; } }", "fn foo () {1 ;}");
745 }
746
747 #[test]
748 fn test_meta() {
749 let rules = create_rules(
750 r#"
751 macro_rules! foo {
752 ($ i:meta) => (
753 #[$ i]
754 fn bar() {}
755 )
756 }
757"#,
758 );
759 assert_expansion(
760 &rules,
761 r#"foo! { cfg(target_os = "windows") }"#,
762 r#"# [cfg (target_os = "windows")] fn bar () {}"#,
763 );
764 }
765
766 // #[test]
767 // fn test_tt_block() {
768 // let rules = create_rules(
769 // r#"
770 // macro_rules! foo {
771 // ($ i:tt) => { fn foo() $ i }
772 // }
773 // "#,
774 // );
775 // assert_expansion(&rules, r#"foo! { { 1; } }"#, r#"fn foo () {1 ;}"#);
776 // }
777
778 // #[test]
779 // fn test_tt_group() {
780 // let rules = create_rules(
781 // r#"
782 // macro_rules! foo {
783 // ($($ i:tt)*) => { $($ i)* }
784 // }
785 // "#,
786 // );
787 // assert_expansion(&rules, r#"foo! { fn foo() {} }"#, r#"fn foo () {}"#);
788 // }
789
790 #[test]
791 fn test_lifetime() {
792 let rules = create_rules(
793 r#"
794 macro_rules! foo {
795 ($ lt:lifetime) => { struct Ref<$ lt>{ s: &$ lt str } }
796 }
797"#,
798 );
799 assert_expansion(&rules, r#"foo!{'a}"#, r#"struct Ref < 'a > {s : & 'a str}"#);
800 }
801
802 #[test]
803 fn test_literal() {
804 let rules = create_rules(
805 r#"
806 macro_rules! foo {
807 ($ type:ty $ lit:literal) => { const VALUE: $ type = $ lit;};
808 }
809"#,
810 );
811 assert_expansion(&rules, r#"foo!(u8 0)"#, r#"const VALUE : u8 = 0 ;"#);
812 }
813
814 #[test]
815 fn test_vis() {
816 let rules = create_rules(
817 r#"
818 macro_rules! foo {
819 ($ vis:vis $ name:ident) => { $ vis fn $ name() {}};
820 }
821"#,
822 );
823 assert_expansion(&rules, r#"foo!(pub foo);"#, r#"pub fn foo () {}"#);
824 }
668} 825}
diff --git a/crates/ra_mbe/src/mbe_expander.rs b/crates/ra_mbe/src/mbe_expander.rs
index acba42809..86867111f 100644
--- a/crates/ra_mbe/src/mbe_expander.rs
+++ b/crates/ra_mbe/src/mbe_expander.rs
@@ -161,11 +161,47 @@ fn match_lhs(pattern: &crate::Subtree, input: &mut TtCursor) -> Result<Bindings,
161 let pat = input.eat_stmt().ok_or(ExpandError::UnexpectedToken)?.clone(); 161 let pat = input.eat_stmt().ok_or(ExpandError::UnexpectedToken)?.clone();
162 res.inner.insert(text.clone(), Binding::Simple(pat.into())); 162 res.inner.insert(text.clone(), Binding::Simple(pat.into()));
163 } 163 }
164 "block" => {
165 let block =
166 input.eat_block().ok_or(ExpandError::UnexpectedToken)?.clone();
167 res.inner.insert(text.clone(), Binding::Simple(block.into()));
168 }
169 "meta" => {
170 let meta =
171 input.eat_meta().ok_or(ExpandError::UnexpectedToken)?.clone();
172 res.inner.insert(text.clone(), Binding::Simple(meta.into()));
173 }
174 // FIXME:
175 // Enable followiing code when everything is fixed
176 // At least we can dogfood itself to not stackoverflow
177 //
178 // "tt" => {
179 // let token = input.eat().ok_or(ExpandError::UnexpectedToken)?.clone();
180 // res.inner.insert(text.clone(), Binding::Simple(token.into()));
181 // }
164 "item" => { 182 "item" => {
165 let item = 183 let item =
166 input.eat_item().ok_or(ExpandError::UnexpectedToken)?.clone(); 184 input.eat_item().ok_or(ExpandError::UnexpectedToken)?.clone();
167 res.inner.insert(text.clone(), Binding::Simple(item.into())); 185 res.inner.insert(text.clone(), Binding::Simple(item.into()));
168 } 186 }
187 "lifetime" => {
188 let lifetime =
189 input.eat_lifetime().ok_or(ExpandError::UnexpectedToken)?.clone();
190 res.inner.insert(text.clone(), Binding::Simple(lifetime.into()));
191 }
192 "literal" => {
193 let literal =
194 input.eat_literal().ok_or(ExpandError::UnexpectedToken)?.clone();
195 res.inner.insert(
196 text.clone(),
197 Binding::Simple(tt::Leaf::from(literal).into()),
198 );
199 }
200 "vis" => {
201 let vis = input.eat_vis().ok_or(ExpandError::UnexpectedToken)?.clone();
202 res.inner.insert(text.clone(), Binding::Simple(vis.into()));
203 }
204
169 _ => return Err(ExpandError::UnexpectedToken), 205 _ => return Err(ExpandError::UnexpectedToken),
170 } 206 }
171 } 207 }
diff --git a/crates/ra_mbe/src/subtree_parser.rs b/crates/ra_mbe/src/subtree_parser.rs
index 195e4c3ac..528aa0f8a 100644
--- a/crates/ra_mbe/src/subtree_parser.rs
+++ b/crates/ra_mbe/src/subtree_parser.rs
@@ -46,10 +46,22 @@ impl<'a> Parser<'a> {
46 self.parse(|src, sink| ra_parser::parse_stmt(src, sink, false)) 46 self.parse(|src, sink| ra_parser::parse_stmt(src, sink, false))
47 } 47 }
48 48
49 pub fn parse_block(self) -> Option<tt::TokenTree> {
50 self.parse(ra_parser::parse_block)
51 }
52
53 pub fn parse_meta(self) -> Option<tt::TokenTree> {
54 self.parse(ra_parser::parse_meta)
55 }
56
49 pub fn parse_item(self) -> Option<tt::TokenTree> { 57 pub fn parse_item(self) -> Option<tt::TokenTree> {
50 self.parse(ra_parser::parse_item) 58 self.parse(ra_parser::parse_item)
51 } 59 }
52 60
61 pub fn parse_vis(self) -> Option<tt::TokenTree> {
62 self.parse(ra_parser::parse_vis)
63 }
64
53 fn parse<F>(self, f: F) -> Option<tt::TokenTree> 65 fn parse<F>(self, f: F) -> Option<tt::TokenTree>
54 where 66 where
55 F: FnOnce(&dyn TokenSource, &mut dyn TreeSink), 67 F: FnOnce(&dyn TokenSource, &mut dyn TreeSink),
diff --git a/crates/ra_mbe/src/syntax_bridge.rs b/crates/ra_mbe/src/syntax_bridge.rs
index 28ded7870..38a481029 100644
--- a/crates/ra_mbe/src/syntax_bridge.rs
+++ b/crates/ra_mbe/src/syntax_bridge.rs
@@ -5,6 +5,7 @@ use ra_syntax::{
5}; 5};
6 6
7use crate::subtree_source::{SubtreeTokenSource, Querier}; 7use crate::subtree_source::{SubtreeTokenSource, Querier};
8use crate::ExpandError;
8 9
9/// Maps `tt::TokenId` to the relative range of the original token. 10/// Maps `tt::TokenId` to the relative range of the original token.
10#[derive(Default)] 11#[derive(Default)]
@@ -30,6 +31,71 @@ pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> Option<(tt::Subtree, Toke
30 Some((tt, token_map)) 31 Some((tt, token_map))
31} 32}
32 33
34// The following items are what `rustc` macro can be parsed into :
35// link: https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libsyntax/ext/expand.rs#L141
36// * Expr(P<ast::Expr>) -> token_tree_to_expr
37// * Pat(P<ast::Pat>) -> token_tree_to_pat
38// * Ty(P<ast::Ty>) -> token_tree_to_ty
39// * Stmts(SmallVec<[ast::Stmt; 1]>) -> token_tree_to_stmts
40// * Items(SmallVec<[P<ast::Item>; 1]>) -> token_tree_to_items
41//
42// * TraitItems(SmallVec<[ast::TraitItem; 1]>)
43// * ImplItems(SmallVec<[ast::ImplItem; 1]>)
44// * ForeignItems(SmallVec<[ast::ForeignItem; 1]>
45//
46//
47
48/// Parses the token tree (result of macro expansion) to an expression
49pub fn token_tree_to_expr(tt: &tt::Subtree) -> Result<TreeArc<ast::Expr>, ExpandError> {
50 let token_source = SubtreeTokenSource::new(tt);
51 let mut tree_sink = TtTreeSink::new(token_source.querier());
52 ra_parser::parse_expr(&token_source, &mut tree_sink);
53 let syntax = tree_sink.inner.finish();
54 ast::Expr::cast(&syntax)
55 .map(|m| m.to_owned())
56 .ok_or_else(|| crate::ExpandError::ConversionError)
57}
58
59/// Parses the token tree (result of macro expansion) to a Pattern
60pub fn token_tree_to_pat(tt: &tt::Subtree) -> Result<TreeArc<ast::Pat>, ExpandError> {
61 let token_source = SubtreeTokenSource::new(tt);
62 let mut tree_sink = TtTreeSink::new(token_source.querier());
63 ra_parser::parse_pat(&token_source, &mut tree_sink);
64 let syntax = tree_sink.inner.finish();
65 ast::Pat::cast(&syntax).map(|m| m.to_owned()).ok_or_else(|| ExpandError::ConversionError)
66}
67
68/// Parses the token tree (result of macro expansion) to a Type
69pub fn token_tree_to_ty(tt: &tt::Subtree) -> Result<TreeArc<ast::TypeRef>, ExpandError> {
70 let token_source = SubtreeTokenSource::new(tt);
71 let mut tree_sink = TtTreeSink::new(token_source.querier());
72 ra_parser::parse_ty(&token_source, &mut tree_sink);
73 let syntax = tree_sink.inner.finish();
74 ast::TypeRef::cast(&syntax).map(|m| m.to_owned()).ok_or_else(|| ExpandError::ConversionError)
75}
76
77/// Parses the token tree (result of macro expansion) as a sequence of stmts
78pub fn token_tree_to_macro_stmts(
79 tt: &tt::Subtree,
80) -> Result<TreeArc<ast::MacroStmts>, ExpandError> {
81 let token_source = SubtreeTokenSource::new(tt);
82 let mut tree_sink = TtTreeSink::new(token_source.querier());
83 ra_parser::parse_macro_stmts(&token_source, &mut tree_sink);
84 let syntax = tree_sink.inner.finish();
85 ast::MacroStmts::cast(&syntax).map(|m| m.to_owned()).ok_or_else(|| ExpandError::ConversionError)
86}
87
88/// Parses the token tree (result of macro expansion) as a sequence of items
89pub fn token_tree_to_macro_items(
90 tt: &tt::Subtree,
91) -> Result<TreeArc<ast::MacroItems>, ExpandError> {
92 let token_source = SubtreeTokenSource::new(tt);
93 let mut tree_sink = TtTreeSink::new(token_source.querier());
94 ra_parser::parse_macro_items(&token_source, &mut tree_sink);
95 let syntax = tree_sink.inner.finish();
96 ast::MacroItems::cast(&syntax).map(|m| m.to_owned()).ok_or_else(|| ExpandError::ConversionError)
97}
98
33/// Parses the token tree (result of macro expansion) as a sequence of items 99/// Parses the token tree (result of macro expansion) as a sequence of items
34pub fn token_tree_to_ast_item_list(tt: &tt::Subtree) -> TreeArc<ast::SourceFile> { 100pub fn token_tree_to_ast_item_list(tt: &tt::Subtree) -> TreeArc<ast::SourceFile> {
35 let token_source = SubtreeTokenSource::new(tt); 101 let token_source = SubtreeTokenSource::new(tt);
@@ -91,7 +157,10 @@ fn convert_tt(
91 ); 157 );
92 } 158 }
93 } else { 159 } else {
94 let child = if token.kind().is_keyword() || token.kind() == IDENT { 160 let child: tt::TokenTree = if token.kind().is_keyword()
161 || token.kind() == IDENT
162 || token.kind() == LIFETIME
163 {
95 let relative_range = token.range() - global_offset; 164 let relative_range = token.range() - global_offset;
96 let id = token_map.alloc(relative_range); 165 let id = token_map.alloc(relative_range);
97 let text = token.text().clone(); 166 let text = token.text().clone();
diff --git a/crates/ra_mbe/src/tt_cursor.rs b/crates/ra_mbe/src/tt_cursor.rs
index 484437b0e..741b5ea1c 100644
--- a/crates/ra_mbe/src/tt_cursor.rs
+++ b/crates/ra_mbe/src/tt_cursor.rs
@@ -41,6 +41,13 @@ impl<'a> TtCursor<'a> {
41 } 41 }
42 } 42 }
43 43
44 pub(crate) fn at_literal(&mut self) -> Option<&'a tt::Literal> {
45 match self.current() {
46 Some(tt::TokenTree::Leaf(tt::Leaf::Literal(i))) => Some(i),
47 _ => None,
48 }
49 }
50
44 pub(crate) fn bump(&mut self) { 51 pub(crate) fn bump(&mut self) {
45 self.pos += 1; 52 self.pos += 1;
46 } 53 }
@@ -79,6 +86,13 @@ impl<'a> TtCursor<'a> {
79 }) 86 })
80 } 87 }
81 88
89 pub(crate) fn eat_literal(&mut self) -> Option<&'a tt::Literal> {
90 self.at_literal().map(|i| {
91 self.bump();
92 i
93 })
94 }
95
82 pub(crate) fn eat_path(&mut self) -> Option<tt::TokenTree> { 96 pub(crate) fn eat_path(&mut self) -> Option<tt::TokenTree> {
83 let parser = Parser::new(&mut self.pos, self.subtree); 97 let parser = Parser::new(&mut self.pos, self.subtree);
84 parser.parse_path() 98 parser.parse_path()
@@ -104,11 +118,37 @@ impl<'a> TtCursor<'a> {
104 parser.parse_stmt() 118 parser.parse_stmt()
105 } 119 }
106 120
121 pub(crate) fn eat_block(&mut self) -> Option<tt::TokenTree> {
122 let parser = Parser::new(&mut self.pos, self.subtree);
123 parser.parse_block()
124 }
125
126 pub(crate) fn eat_meta(&mut self) -> Option<tt::TokenTree> {
127 let parser = Parser::new(&mut self.pos, self.subtree);
128 parser.parse_meta()
129 }
130
107 pub(crate) fn eat_item(&mut self) -> Option<tt::TokenTree> { 131 pub(crate) fn eat_item(&mut self) -> Option<tt::TokenTree> {
108 let parser = Parser::new(&mut self.pos, self.subtree); 132 let parser = Parser::new(&mut self.pos, self.subtree);
109 parser.parse_item() 133 parser.parse_item()
110 } 134 }
111 135
136 pub(crate) fn eat_lifetime(&mut self) -> Option<tt::TokenTree> {
137 // check if it start from "`"
138 if let Some(ident) = self.at_ident() {
139 if ident.text.chars().next()? != '\'' {
140 return None;
141 }
142 }
143
144 self.eat_ident().cloned().map(|ident| tt::Leaf::from(ident).into())
145 }
146
147 pub(crate) fn eat_vis(&mut self) -> Option<tt::TokenTree> {
148 let parser = Parser::new(&mut self.pos, self.subtree);
149 parser.parse_vis()
150 }
151
112 pub(crate) fn expect_char(&mut self, char: char) -> Result<(), ParseError> { 152 pub(crate) fn expect_char(&mut self, char: char) -> Result<(), ParseError> {
113 if self.at_char(char) { 153 if self.at_char(char) {
114 self.bump(); 154 self.bump();
diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs
index f8ed1299a..67eae749d 100644
--- a/crates/ra_parser/src/grammar.rs
+++ b/crates/ra_parser/src/grammar.rs
@@ -49,6 +49,27 @@ pub(crate) fn root(p: &mut Parser) {
49 m.complete(p, SOURCE_FILE); 49 m.complete(p, SOURCE_FILE);
50} 50}
51 51
52pub(crate) fn macro_items(p: &mut Parser) {
53 let m = p.start();
54 items::mod_contents(p, false);
55 m.complete(p, MACRO_ITEMS);
56}
57
58pub(crate) fn macro_stmts(p: &mut Parser) {
59 let m = p.start();
60
61 while !p.at(EOF) {
62 if p.current() == SEMI {
63 p.bump();
64 continue;
65 }
66
67 expressions::stmt(p, expressions::StmtWithSemi::Optional);
68 }
69
70 m.complete(p, MACRO_STMTS);
71}
72
52pub(crate) fn path(p: &mut Parser) { 73pub(crate) fn path(p: &mut Parser) {
53 paths::type_path(p); 74 paths::type_path(p);
54} 75}
@@ -66,9 +87,45 @@ pub(crate) fn pattern(p: &mut Parser) {
66} 87}
67 88
68pub(crate) fn stmt(p: &mut Parser, with_semi: bool) { 89pub(crate) fn stmt(p: &mut Parser, with_semi: bool) {
90 let with_semi = match with_semi {
91 true => expressions::StmtWithSemi::Yes,
92 false => expressions::StmtWithSemi::No,
93 };
94
69 expressions::stmt(p, with_semi) 95 expressions::stmt(p, with_semi)
70} 96}
71 97
98pub(crate) fn block(p: &mut Parser) {
99 expressions::block(p);
100}
101
102// Parse a meta item , which excluded [], e.g : #[ MetaItem ]
103pub(crate) fn meta_item(p: &mut Parser) {
104 fn is_delimiter(p: &mut Parser) -> bool {
105 match p.current() {
106 L_CURLY | L_PAREN | L_BRACK => true,
107 _ => false,
108 }
109 }
110
111 if is_delimiter(p) {
112 items::token_tree(p);
113 return;
114 }
115
116 let m = p.start();
117 while !p.at(EOF) {
118 if is_delimiter(p) {
119 items::token_tree(p);
120 break;
121 } else {
122 p.bump();
123 }
124 }
125
126 m.complete(p, TOKEN_TREE);
127}
128
72pub(crate) fn item(p: &mut Parser) { 129pub(crate) fn item(p: &mut Parser) {
73 items::item_or_macro(p, true, items::ItemFlavor::Mod) 130 items::item_or_macro(p, true, items::ItemFlavor::Mod)
74} 131}
@@ -110,7 +167,7 @@ impl BlockLike {
110 } 167 }
111} 168}
112 169
113fn opt_visibility(p: &mut Parser) -> bool { 170pub(crate) fn opt_visibility(p: &mut Parser) -> bool {
114 match p.current() { 171 match p.current() {
115 PUB_KW => { 172 PUB_KW => {
116 let m = p.start(); 173 let m = p.start();
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index 06f2b45b1..8df9035e9 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -4,6 +4,12 @@ pub(crate) use self::atom::match_arm_list;
4pub(super) use self::atom::{literal, LITERAL_FIRST}; 4pub(super) use self::atom::{literal, LITERAL_FIRST};
5use super::*; 5use super::*;
6 6
7pub(super) enum StmtWithSemi {
8 Yes,
9 No,
10 Optional,
11}
12
7const EXPR_FIRST: TokenSet = LHS_FIRST; 13const EXPR_FIRST: TokenSet = LHS_FIRST;
8 14
9pub(super) fn expr(p: &mut Parser) -> BlockLike { 15pub(super) fn expr(p: &mut Parser) -> BlockLike {
@@ -48,7 +54,7 @@ fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool {
48 } 54 }
49} 55}
50 56
51pub(super) fn stmt(p: &mut Parser, with_semi: bool) { 57pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi) {
52 // test block_items 58 // test block_items
53 // fn a() { fn b() {} } 59 // fn a() { fn b() {} }
54 let m = p.start(); 60 let m = p.start();
@@ -111,13 +117,23 @@ pub(super) fn stmt(p: &mut Parser, with_semi: bool) {
111 // } 117 // }
112 // test!{} 118 // test!{}
113 // } 119 // }
114 if with_semi { 120
115 if blocklike.is_block() { 121 match with_semi {
116 p.eat(SEMI); 122 StmtWithSemi::Yes => {
117 } else { 123 if blocklike.is_block() {
118 p.expect(SEMI); 124 p.eat(SEMI);
125 } else {
126 p.expect(SEMI);
127 }
128 }
129 StmtWithSemi::No => {}
130 StmtWithSemi::Optional => {
131 if p.at(SEMI) {
132 p.eat(SEMI);
133 }
119 } 134 }
120 } 135 }
136
121 m.complete(p, EXPR_STMT); 137 m.complete(p, EXPR_STMT);
122 } 138 }
123 139
@@ -128,7 +144,7 @@ pub(super) fn stmt(p: &mut Parser, with_semi: bool) {
128 // let c = 92; 144 // let c = 92;
129 // let d: i32 = 92; 145 // let d: i32 = 92;
130 // } 146 // }
131 fn let_stmt(p: &mut Parser, m: Marker, with_semi: bool) { 147 fn let_stmt(p: &mut Parser, m: Marker, with_semi: StmtWithSemi) {
132 assert!(p.at(LET_KW)); 148 assert!(p.at(LET_KW));
133 p.bump(); 149 p.bump();
134 patterns::pattern(p); 150 patterns::pattern(p);
@@ -139,8 +155,16 @@ pub(super) fn stmt(p: &mut Parser, with_semi: bool) {
139 expressions::expr(p); 155 expressions::expr(p);
140 } 156 }
141 157
142 if with_semi { 158 match with_semi {
143 p.expect(SEMI); 159 StmtWithSemi::Yes => {
160 p.expect(SEMI);
161 }
162 StmtWithSemi::No => {}
163 StmtWithSemi::Optional => {
164 if p.at(SEMI) {
165 p.eat(SEMI);
166 }
167 }
144 } 168 }
145 m.complete(p, LET_STMT); 169 m.complete(p, LET_STMT);
146 } 170 }
@@ -160,7 +184,7 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
160 continue; 184 continue;
161 } 185 }
162 186
163 stmt(p, true) 187 stmt(p, StmtWithSemi::Yes)
164 } 188 }
165} 189}
166 190
diff --git a/crates/ra_parser/src/lib.rs b/crates/ra_parser/src/lib.rs
index 11b5b9a75..970d699c0 100644
--- a/crates/ra_parser/src/lib.rs
+++ b/crates/ra_parser/src/lib.rs
@@ -93,11 +93,35 @@ pub fn parse_stmt(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink,
93 parse_from_tokens(token_source, tree_sink, |p| grammar::stmt(p, with_semi)); 93 parse_from_tokens(token_source, tree_sink, |p| grammar::stmt(p, with_semi));
94} 94}
95 95
96/// Parse given tokens into the given sink as a block
97pub fn parse_block(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) {
98 parse_from_tokens(token_source, tree_sink, grammar::block);
99}
100
101pub fn parse_meta(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) {
102 parse_from_tokens(token_source, tree_sink, grammar::meta_item);
103}
104
96/// Parse given tokens into the given sink as an item 105/// Parse given tokens into the given sink as an item
97pub fn parse_item(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) { 106pub fn parse_item(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) {
98 parse_from_tokens(token_source, tree_sink, grammar::item); 107 parse_from_tokens(token_source, tree_sink, grammar::item);
99} 108}
100 109
110/// Parse given tokens into the given sink as an visibility qualifier
111pub fn parse_vis(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) {
112 parse_from_tokens(token_source, tree_sink, |p| {
113 grammar::opt_visibility(p);
114 });
115}
116
117pub fn parse_macro_items(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) {
118 parse_from_tokens(token_source, tree_sink, grammar::macro_items);
119}
120
121pub fn parse_macro_stmts(token_source: &dyn TokenSource, tree_sink: &mut dyn TreeSink) {
122 parse_from_tokens(token_source, tree_sink, grammar::macro_stmts);
123}
124
101/// A parsing function for a specific braced-block. 125/// A parsing function for a specific braced-block.
102pub struct Reparser(fn(&mut parser::Parser)); 126pub struct Reparser(fn(&mut parser::Parser));
103 127
diff --git a/crates/ra_parser/src/syntax_kind/generated.rs b/crates/ra_parser/src/syntax_kind/generated.rs
index 498b0e164..6f984aea1 100644
--- a/crates/ra_parser/src/syntax_kind/generated.rs
+++ b/crates/ra_parser/src/syntax_kind/generated.rs
@@ -233,6 +233,8 @@ pub enum SyntaxKind {
233 ARG_LIST, 233 ARG_LIST,
234 TYPE_BOUND, 234 TYPE_BOUND,
235 TYPE_BOUND_LIST, 235 TYPE_BOUND_LIST,
236 MACRO_ITEMS,
237 MACRO_STMTS,
236 // Technical kind so that we can cast from u16 safely 238 // Technical kind so that we can cast from u16 safely
237 #[doc(hidden)] 239 #[doc(hidden)]
238 __LAST, 240 __LAST,
@@ -592,6 +594,8 @@ impl SyntaxKind {
592 ARG_LIST => &SyntaxInfo { name: "ARG_LIST" }, 594 ARG_LIST => &SyntaxInfo { name: "ARG_LIST" },
593 TYPE_BOUND => &SyntaxInfo { name: "TYPE_BOUND" }, 595 TYPE_BOUND => &SyntaxInfo { name: "TYPE_BOUND" },
594 TYPE_BOUND_LIST => &SyntaxInfo { name: "TYPE_BOUND_LIST" }, 596 TYPE_BOUND_LIST => &SyntaxInfo { name: "TYPE_BOUND_LIST" },
597 MACRO_ITEMS => &SyntaxInfo { name: "MACRO_ITEMS" },
598 MACRO_STMTS => &SyntaxInfo { name: "MACRO_STMTS" },
595 TOMBSTONE => &SyntaxInfo { name: "TOMBSTONE" }, 599 TOMBSTONE => &SyntaxInfo { name: "TOMBSTONE" },
596 EOF => &SyntaxInfo { name: "EOF" }, 600 EOF => &SyntaxInfo { name: "EOF" },
597 __LAST => &SyntaxInfo { name: "__LAST" }, 601 __LAST => &SyntaxInfo { name: "__LAST" },
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 29a7ce166..fae371509 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -1771,6 +1771,72 @@ impl MacroCall {
1771 } 1771 }
1772} 1772}
1773 1773
1774// MacroItems
1775#[derive(Debug, PartialEq, Eq, Hash)]
1776#[repr(transparent)]
1777pub struct MacroItems {
1778 pub(crate) syntax: SyntaxNode,
1779}
1780unsafe impl TransparentNewType for MacroItems {
1781 type Repr = rowan::SyntaxNode;
1782}
1783
1784impl AstNode for MacroItems {
1785 fn cast(syntax: &SyntaxNode) -> Option<&Self> {
1786 match syntax.kind() {
1787 MACRO_ITEMS => Some(MacroItems::from_repr(syntax.into_repr())),
1788 _ => None,
1789 }
1790 }
1791 fn syntax(&self) -> &SyntaxNode { &self.syntax }
1792}
1793
1794impl ToOwned for MacroItems {
1795 type Owned = TreeArc<MacroItems>;
1796 fn to_owned(&self) -> TreeArc<MacroItems> { TreeArc::cast(self.syntax.to_owned()) }
1797}
1798
1799
1800impl ast::ModuleItemOwner for MacroItems {}
1801impl ast::FnDefOwner for MacroItems {}
1802impl MacroItems {}
1803
1804// MacroStmts
1805#[derive(Debug, PartialEq, Eq, Hash)]
1806#[repr(transparent)]
1807pub struct MacroStmts {
1808 pub(crate) syntax: SyntaxNode,
1809}
1810unsafe impl TransparentNewType for MacroStmts {
1811 type Repr = rowan::SyntaxNode;
1812}
1813
1814impl AstNode for MacroStmts {
1815 fn cast(syntax: &SyntaxNode) -> Option<&Self> {
1816 match syntax.kind() {
1817 MACRO_STMTS => Some(MacroStmts::from_repr(syntax.into_repr())),
1818 _ => None,
1819 }
1820 }
1821 fn syntax(&self) -> &SyntaxNode { &self.syntax }
1822}
1823
1824impl ToOwned for MacroStmts {
1825 type Owned = TreeArc<MacroStmts>;
1826 fn to_owned(&self) -> TreeArc<MacroStmts> { TreeArc::cast(self.syntax.to_owned()) }
1827}
1828
1829
1830impl MacroStmts {
1831 pub fn statements(&self) -> impl Iterator<Item = &Stmt> {
1832 super::children(self)
1833 }
1834
1835 pub fn expr(&self) -> Option<&Expr> {
1836 super::child_opt(self)
1837 }
1838}
1839
1774// MatchArm 1840// MatchArm
1775#[derive(Debug, PartialEq, Eq, Hash)] 1841#[derive(Debug, PartialEq, Eq, Hash)]
1776#[repr(transparent)] 1842#[repr(transparent)]
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 41fe35f08..5bdcf9c84 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -247,6 +247,10 @@ Grammar(
247 "ARG_LIST", 247 "ARG_LIST",
248 "TYPE_BOUND", 248 "TYPE_BOUND",
249 "TYPE_BOUND_LIST", 249 "TYPE_BOUND_LIST",
250
251 // macro related
252 "MACRO_ITEMS",
253 "MACRO_STMTS",
250 ], 254 ],
251 ast: { 255 ast: {
252 "SourceFile": ( 256 "SourceFile": (
@@ -668,5 +672,16 @@ Grammar(
668 "TypeArg": (options: ["TypeRef"]), 672 "TypeArg": (options: ["TypeRef"]),
669 "AssocTypeArg": (options: ["NameRef", "TypeRef"]), 673 "AssocTypeArg": (options: ["NameRef", "TypeRef"]),
670 "LifetimeArg": (), 674 "LifetimeArg": (),
675
676 "MacroItems": (
677 traits: [ "ModuleItemOwner", "FnDefOwner" ],
678 ),
679
680 "MacroStmts" : (
681 options: [ "Expr" ],
682 collections: [
683 ["statements", "Stmt"],
684 ],
685 )
671 }, 686 },
672) 687)
diff --git a/docs/user/README.md b/docs/user/README.md
index 33dd4f995..0196bf45f 100644
--- a/docs/user/README.md
+++ b/docs/user/README.md
@@ -78,3 +78,30 @@ Installation:
78to load path and require it in `init.el` 78to load path and require it in `init.el`
79* run `lsp` in a rust buffer 79* run `lsp` in a rust buffer
80* (Optionally) bind commands like `rust-analyzer-join-lines` or `rust-analyzer-extend-selection` to keys 80* (Optionally) bind commands like `rust-analyzer-join-lines` or `rust-analyzer-extend-selection` to keys
81
82
83## Sublime Text 3
84
85Prequisites:
86
87`LSP` package.
88
89Installation:
90
91* Invoke the command palette with <kbd>Ctrl+Shift+P</kbd>
92* Type `LSP Settings` to open the LSP preferences editor
93* Add the following LSP client definition to your settings:
94
95```json
96"rust-analyzer": {
97 "command": ["rustup", "run", "stable", "ra_lsp_server"],
98 "languageId": "rust",
99 "scopes": ["source.rust"],
100 "syntaxes": [
101 "Packages/Rust/Rust.sublime-syntax",
102 "Packages/Rust Enhanced/RustEnhanced.sublime-syntax"
103 ]
104}
105```
106
107* You can now invoke the command palette and type LSP enable to locally/globally enable the rust-analyzer LSP (type LSP enable, then choose either locally or globally, then select rust-analyzer)
diff --git a/docs/user/features.md b/docs/user/features.md
index 09a7f5a43..cffbb4c7f 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -76,6 +76,14 @@ Shows internal statistic about memory usage of rust-analyzer
76 76
77Manually triggers GC 77Manually triggers GC
78 78
79#### Start Cargo Watch
80
81Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed.
82
83#### Stop Cargo Watch
84
85Stop `cargo watch`
86
79### Code Actions (Assists) 87### Code Actions (Assists)
80 88
81These are triggered in a particular context via light bulb. We use custom code on 89These are triggered in a particular context via light bulb. We use custom code on
diff --git a/editors/code/package.json b/editors/code/package.json
index a0454191a..83ceb19f7 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -119,6 +119,16 @@
119 "command": "rust-analyzer.reload", 119 "command": "rust-analyzer.reload",
120 "title": "Restart server", 120 "title": "Restart server",
121 "category": "Rust Analyzer" 121 "category": "Rust Analyzer"
122 },
123 {
124 "command": "rust-analyzer.startCargoWatch",
125 "title": "Start Cargo Watch",
126 "category": "Rust Analyzer"
127 },
128 {
129 "command": "rust-analyzer.stopCargoWatch",
130 "title": "Stop Cargo Watch",
131 "category": "Rust Analyzer"
122 } 132 }
123 ], 133 ],
124 "keybindings": [ 134 "keybindings": [
@@ -250,6 +260,18 @@
250 "${workspaceRoot}" 260 "${workspaceRoot}"
251 ], 261 ],
252 "pattern": "$rustc" 262 "pattern": "$rustc"
263 },
264 {
265 "name": "rustc-watch",
266 "fileLocation": [
267 "relative",
268 "${workspaceRoot}"
269 ],
270 "background": {
271 "beginsPattern": "^\\[Running\\b",
272 "endsPattern": "^\\[Finished running\\b"
273 },
274 "pattern": "$rustc"
253 } 275 }
254 ] 276 ]
255 } 277 }
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
index 32bd38a1c..1d939e28c 100644
--- a/editors/code/src/commands/cargo_watch.ts
+++ b/editors/code/src/commands/cargo_watch.ts
@@ -7,44 +7,55 @@ import { terminate } from '../utils/processes';
7import { LineBuffer } from './line_buffer'; 7import { LineBuffer } from './line_buffer';
8import { StatusDisplay } from './watch_status'; 8import { StatusDisplay } from './watch_status';
9 9
10export class CargoWatchProvider { 10export function registerCargoWatchProvider(
11 private diagnosticCollection?: vscode.DiagnosticCollection; 11 subscriptions: vscode.Disposable[]
12 private cargoProcess?: child_process.ChildProcess; 12): CargoWatchProvider | undefined {
13 private outBuffer: string = ''; 13 let cargoExists = false;
14 private statusDisplay?: StatusDisplay; 14 const cargoTomlFile = path.join(vscode.workspace.rootPath!, 'Cargo.toml');
15 private outputChannel?: vscode.OutputChannel; 15 // Check if the working directory is valid cargo root path
16 16 try {
17 public activate(subscriptions: vscode.Disposable[]) { 17 if (fs.existsSync(cargoTomlFile)) {
18 let cargoExists = false; 18 cargoExists = true;
19 const cargoTomlFile = path.join( 19 }
20 vscode.workspace.rootPath!, 20 } catch (err) {
21 'Cargo.toml' 21 cargoExists = false;
22 }
23
24 if (!cargoExists) {
25 vscode.window.showErrorMessage(
26 `Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}`
22 ); 27 );
23 // Check if the working directory is valid cargo root path 28 return;
24 try { 29 }
25 if (fs.existsSync(cargoTomlFile)) {
26 cargoExists = true;
27 }
28 } catch (err) {
29 cargoExists = false;
30 }
31 30
32 if (!cargoExists) { 31 const provider = new CargoWatchProvider();
33 vscode.window.showErrorMessage( 32 subscriptions.push(provider);
34 `Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}` 33 return provider;
35 ); 34}
36 return;
37 }
38 35
39 subscriptions.push(this); 36export class CargoWatchProvider implements vscode.Disposable {
37 private readonly diagnosticCollection: vscode.DiagnosticCollection;
38 private readonly statusDisplay: StatusDisplay;
39 private readonly outputChannel: vscode.OutputChannel;
40 private cargoProcess?: child_process.ChildProcess;
41
42 constructor() {
40 this.diagnosticCollection = vscode.languages.createDiagnosticCollection( 43 this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
41 'rustc' 44 'rustc'
42 ); 45 );
43 46 this.statusDisplay = new StatusDisplay();
44 this.statusDisplay = new StatusDisplay(subscriptions);
45 this.outputChannel = vscode.window.createOutputChannel( 47 this.outputChannel = vscode.window.createOutputChannel(
46 'Cargo Watch Trace' 48 'Cargo Watch Trace'
47 ); 49 );
50 }
51
52 public start() {
53 if (this.cargoProcess) {
54 vscode.window.showInformationMessage(
55 'Cargo Watch is already running'
56 );
57 return;
58 }
48 59
49 let args = 'check --message-format json'; 60 let args = 'check --message-format json';
50 if (Server.config.cargoWatchOptions.checkArguments.length > 0) { 61 if (Server.config.cargoWatchOptions.checkArguments.length > 0) {
@@ -95,25 +106,28 @@ export class CargoWatchProvider {
95 this.logInfo('cargo-watch started.'); 106 this.logInfo('cargo-watch started.');
96 } 107 }
97 108
98 public dispose(): void { 109 public stop() {
99 if (this.diagnosticCollection) {
100 this.diagnosticCollection.clear();
101 this.diagnosticCollection.dispose();
102 }
103
104 if (this.cargoProcess) { 110 if (this.cargoProcess) {
105 this.cargoProcess.kill(); 111 this.cargoProcess.kill();
106 terminate(this.cargoProcess); 112 terminate(this.cargoProcess);
113 this.cargoProcess = undefined;
114 } else {
115 vscode.window.showInformationMessage('Cargo Watch is not running');
107 } 116 }
117 }
108 118
109 if (this.outputChannel) { 119 public dispose(): void {
110 this.outputChannel.dispose(); 120 this.stop();
111 } 121
122 this.diagnosticCollection.clear();
123 this.diagnosticCollection.dispose();
124 this.outputChannel.dispose();
125 this.statusDisplay.dispose();
112 } 126 }
113 127
114 private logInfo(line: string) { 128 private logInfo(line: string) {
115 if (Server.config.cargoWatchOptions.trace === 'verbose') { 129 if (Server.config.cargoWatchOptions.trace === 'verbose') {
116 this.outputChannel!.append(line); 130 this.outputChannel.append(line);
117 } 131 }
118 } 132 }
119 133
@@ -122,18 +136,18 @@ export class CargoWatchProvider {
122 Server.config.cargoWatchOptions.trace === 'error' || 136 Server.config.cargoWatchOptions.trace === 'error' ||
123 Server.config.cargoWatchOptions.trace === 'verbose' 137 Server.config.cargoWatchOptions.trace === 'verbose'
124 ) { 138 ) {
125 this.outputChannel!.append(line); 139 this.outputChannel.append(line);
126 } 140 }
127 } 141 }
128 142
129 private parseLine(line: string) { 143 private parseLine(line: string) {
130 if (line.startsWith('[Running')) { 144 if (line.startsWith('[Running')) {
131 this.diagnosticCollection!.clear(); 145 this.diagnosticCollection.clear();
132 this.statusDisplay!.show(); 146 this.statusDisplay.show();
133 } 147 }
134 148
135 if (line.startsWith('[Finished running')) { 149 if (line.startsWith('[Finished running')) {
136 this.statusDisplay!.hide(); 150 this.statusDisplay.hide();
137 } 151 }
138 152
139 function getLevel(s: string): vscode.DiagnosticSeverity { 153 function getLevel(s: string): vscode.DiagnosticSeverity {
@@ -193,7 +207,7 @@ export class CargoWatchProvider {
193 207
194 // The format of the package_id is "{name} {version} ({source_id})", 208 // The format of the package_id is "{name} {version} ({source_id})",
195 // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53 209 // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
196 this.statusDisplay!.packageName = msg.package_id.split(' ')[0]; 210 this.statusDisplay.packageName = msg.package_id.split(' ')[0];
197 } else if (data.reason === 'compiler-message') { 211 } else if (data.reason === 'compiler-message') {
198 const msg = data.message as RustDiagnostic; 212 const msg = data.message as RustDiagnostic;
199 213
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index c4df24c79..26372c1e8 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -5,7 +5,7 @@ import * as vscode from 'vscode';
5import * as lc from 'vscode-languageclient'; 5import * as lc from 'vscode-languageclient';
6 6
7import { Server } from '../server'; 7import { Server } from '../server';
8import { CargoWatchProvider } from './cargo_watch'; 8import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch';
9 9
10interface RunnablesParams { 10interface RunnablesParams {
11 textDocument: lc.TextDocumentIdentifier; 11 textDocument: lc.TextDocumentIdentifier;
@@ -137,7 +137,7 @@ export async function handleSingle(runnable: Runnable) {
137 */ 137 */
138export async function interactivelyStartCargoWatch( 138export async function interactivelyStartCargoWatch(
139 context: vscode.ExtensionContext 139 context: vscode.ExtensionContext
140) { 140): Promise<CargoWatchProvider | undefined> {
141 if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') { 141 if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') {
142 return; 142 return;
143 } 143 }
@@ -153,6 +153,12 @@ export async function interactivelyStartCargoWatch(
153 } 153 }
154 } 154 }
155 155
156 return startCargoWatch(context);
157}
158
159export async function startCargoWatch(
160 context: vscode.ExtensionContext
161): Promise<CargoWatchProvider | undefined> {
156 const execPromise = util.promisify(child_process.exec); 162 const execPromise = util.promisify(child_process.exec);
157 163
158 const { stderr } = await execPromise('cargo watch --version').catch(e => e); 164 const { stderr } = await execPromise('cargo watch --version').catch(e => e);
@@ -197,6 +203,9 @@ export async function interactivelyStartCargoWatch(
197 } 203 }
198 } 204 }
199 205
200 const validater = new CargoWatchProvider(); 206 const provider = await registerCargoWatchProvider(context.subscriptions);
201 validater.activate(context.subscriptions); 207 if (provider) {
208 provider.start();
209 }
210 return provider;
202} 211}
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts
index 86ae821de..a3b0178f2 100644
--- a/editors/code/src/commands/watch_status.ts
+++ b/editors/code/src/commands/watch_status.ts
@@ -2,19 +2,18 @@ import * as vscode from 'vscode';
2 2
3const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; 3const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
4 4
5export class StatusDisplay { 5export class StatusDisplay implements vscode.Disposable {
6 public packageName?: string; 6 public packageName?: string;
7 7
8 private i = 0; 8 private i = 0;
9 private statusBarItem: vscode.StatusBarItem; 9 private statusBarItem: vscode.StatusBarItem;
10 private timer?: NodeJS.Timeout; 10 private timer?: NodeJS.Timeout;
11 11
12 constructor(subscriptions: vscode.Disposable[]) { 12 constructor() {
13 this.statusBarItem = vscode.window.createStatusBarItem( 13 this.statusBarItem = vscode.window.createStatusBarItem(
14 vscode.StatusBarAlignment.Left, 14 vscode.StatusBarAlignment.Left,
15 10 15 10
16 ); 16 );
17 subscriptions.push(this.statusBarItem);
18 this.statusBarItem.hide(); 17 this.statusBarItem.hide();
19 } 18 }
20 19
@@ -33,7 +32,7 @@ export class StatusDisplay {
33 } 32 }
34 }, 300); 33 }, 300);
35 34
36 this.statusBarItem!.show(); 35 this.statusBarItem.show();
37 } 36 }
38 37
39 public hide() { 38 public hide() {
@@ -42,7 +41,16 @@ export class StatusDisplay {
42 this.timer = undefined; 41 this.timer = undefined;
43 } 42 }
44 43
45 this.statusBarItem!.hide(); 44 this.statusBarItem.hide();
45 }
46
47 public dispose() {
48 if (this.timer) {
49 clearInterval(this.timer);
50 this.timer = undefined;
51 }
52
53 this.statusBarItem.dispose();
46 } 54 }
47 55
48 private frame() { 56 private frame() {
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index 1073a36a0..48dd2a614 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -2,7 +2,11 @@ import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3 3
4import * as commands from './commands'; 4import * as commands from './commands';
5import { interactivelyStartCargoWatch } from './commands/runnables'; 5import { CargoWatchProvider } from './commands/cargo_watch';
6import {
7 interactivelyStartCargoWatch,
8 startCargoWatch
9} from './commands/runnables';
6import { SyntaxTreeContentProvider } from './commands/syntaxTree'; 10import { SyntaxTreeContentProvider } from './commands/syntaxTree';
7import * as events from './events'; 11import * as events from './events';
8import * as notifications from './notifications'; 12import * as notifications from './notifications';
@@ -126,7 +130,24 @@ export function activate(context: vscode.ExtensionContext) {
126 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); 130 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
127 131
128 // Executing `cargo watch` provides us with inline diagnostics on save 132 // Executing `cargo watch` provides us with inline diagnostics on save
129 interactivelyStartCargoWatch(context); 133 let provider: CargoWatchProvider | undefined;
134 interactivelyStartCargoWatch(context).then(p => {
135 provider = p;
136 });
137 registerCommand('rust-analyzer.startCargoWatch', () => {
138 if (provider) {
139 provider.start();
140 } else {
141 startCargoWatch(context).then(p => {
142 provider = p;
143 });
144 }
145 });
146 registerCommand('rust-analyzer.stopCargoWatch', () => {
147 if (provider) {
148 provider.stop();
149 }
150 });
130 151
131 // Start the language server, finally! 152 // Start the language server, finally!
132 startServer(); 153 startServer();