aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ide_assists/src/handlers/convert_comment_block.rs419
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/proc_macro_srv/src/rustc_server.rs40
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/bin/args.rs274
-rw-r--r--crates/rust-analyzer/src/bin/flags.rs251
-rw-r--r--crates/rust-analyzer/src/bin/main.rs85
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs3
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs2
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs3
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs9
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs4
-rw-r--r--crates/syntax/src/ast/edit.rs11
-rw-r--r--crates/syntax/src/ast/token_ext.rs3
14 files changed, 791 insertions, 317 deletions
diff --git a/crates/ide_assists/src/handlers/convert_comment_block.rs b/crates/ide_assists/src/handlers/convert_comment_block.rs
new file mode 100644
index 000000000..cdc45fc42
--- /dev/null
+++ b/crates/ide_assists/src/handlers/convert_comment_block.rs
@@ -0,0 +1,419 @@
1use itertools::Itertools;
2use std::convert::identity;
3use syntax::{
4 ast::{
5 self,
6 edit::IndentLevel,
7 Comment, CommentKind,
8 CommentPlacement::{Inner, Outer},
9 CommentShape::{self, Block, Line},
10 Whitespace,
11 },
12 AstToken, Direction, SyntaxElement, TextRange,
13};
14
15use crate::{AssistContext, AssistId, AssistKind, Assists};
16
17/// Assist: line_to_block
18///
19/// Converts comments between block and single-line form
20///
21/// ```
22/// // Multi-line
23/// // comment
24/// ```
25/// ->
26/// ```
27/// /**
28/// Multi-line
29/// comment
30/// */
31/// ```
32pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 if let Some(comment) = ctx.find_token_at_offset::<ast::Comment>() {
34 // Only allow comments which are alone on their line
35 if let Some(prev) = comment.syntax().prev_token() {
36 if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
37 return None;
38 }
39 }
40
41 return match comment.kind().shape {
42 ast::CommentShape::Block => block_to_line(acc, comment),
43 ast::CommentShape::Line => line_to_block(acc, comment),
44 };
45 }
46
47 return None;
48}
49
50fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
51 let target = comment.syntax().text_range();
52
53 acc.add(
54 AssistId("block_to_line", AssistKind::RefactorRewrite),
55 "Replace block comment with line comments",
56 target,
57 |edit| {
58 let indentation = IndentLevel::from_token(comment.syntax());
59 let line_prefix =
60 comment_kind_prefix(CommentKind { shape: CommentShape::Line, ..comment.kind() });
61
62 let text = comment.text();
63 let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim();
64
65 let lines = text.lines().peekable();
66
67 let indent_spaces = indentation.to_string();
68 let output = lines
69 .map(|l| l.trim_start_matches(&indent_spaces))
70 .map(|l| {
71 // Don't introduce trailing whitespace
72 if l.is_empty() {
73 line_prefix.to_string()
74 } else {
75 format!("{} {}", line_prefix, l.trim_start_matches(&indent_spaces))
76 }
77 })
78 .join(&format!("\n{}", indent_spaces));
79
80 edit.replace(target, output)
81 },
82 )
83}
84
85fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
86 // Find all the comments we'll be collapsing into a block
87 let comments = relevant_line_comments(&comment);
88
89 // Establish the target of our edit based on the comments we found
90 let target = TextRange::new(
91 comments[0].syntax().text_range().start(),
92 comments.last().unwrap().syntax().text_range().end(),
93 );
94
95 acc.add(
96 AssistId("line_to_block", AssistKind::RefactorRewrite),
97 "Replace line comments with a single block comment",
98 target,
99 |edit| {
100 // We pick a single indentation level for the whole block comment based on the
101 // comment where the assist was invoked. This will be prepended to the
102 // contents of each line comment when they're put into the block comment.
103 let indentation = IndentLevel::from_token(&comment.syntax());
104
105 let block_comment_body =
106 comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n");
107
108 let block_prefix =
109 comment_kind_prefix(CommentKind { shape: CommentShape::Block, ..comment.kind() });
110
111 let output =
112 format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation.to_string());
113
114 edit.replace(target, output)
115 },
116 )
117}
118
119/// The line -> block assist can be invoked from anywhere within a sequence of line comments.
120/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
121/// be joined.
122fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
123 // The prefix identifies the kind of comment we're dealing with
124 let prefix = comment.prefix();
125 let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
126
127 // These tokens are allowed to exist between comments
128 let skippable = |not: &SyntaxElement| {
129 not.clone()
130 .into_token()
131 .and_then(Whitespace::cast)
132 .map(|w| !w.spans_multiple_lines())
133 .unwrap_or(false)
134 };
135
136 // Find all preceding comments (in reverse order) that have the same prefix
137 let prev_comments = comment
138 .syntax()
139 .siblings_with_tokens(Direction::Prev)
140 .filter(|s| !skippable(s))
141 .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
142 .take_while(|opt_com| opt_com.is_some())
143 .filter_map(identity)
144 .skip(1); // skip the first element so we don't duplicate it in next_comments
145
146 let next_comments = comment
147 .syntax()
148 .siblings_with_tokens(Direction::Next)
149 .filter(|s| !skippable(s))
150 .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
151 .take_while(|opt_com| opt_com.is_some())
152 .filter_map(identity);
153
154 let mut comments: Vec<_> = prev_comments.collect();
155 comments.reverse();
156 comments.extend(next_comments);
157 comments
158}
159
160// Line comments usually begin with a single space character following the prefix as seen here:
161//^
162// But comments can also include indented text:
163// > Hello there
164//
165// We handle this by stripping *AT MOST* one space character from the start of the line
166// This has its own problems because it can cause alignment issues:
167//
168// /*
169// a ----> a
170//b ----> b
171// */
172//
173// But since such comments aren't idiomatic we're okay with this.
174fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
175 let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap();
176 let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);
177
178 // Don't add the indentation if the line is empty
179 if contents.is_empty() {
180 contents.to_owned()
181 } else {
182 indentation.to_string() + &contents
183 }
184}
185
186fn comment_kind_prefix(ck: ast::CommentKind) -> &'static str {
187 match (ck.shape, ck.doc) {
188 (Line, Some(Inner)) => "//!",
189 (Line, Some(Outer)) => "///",
190 (Line, None) => "//",
191 (Block, Some(Inner)) => "/*!",
192 (Block, Some(Outer)) => "/**",
193 (Block, None) => "/*",
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use crate::tests::{check_assist, check_assist_not_applicable};
200
201 use super::*;
202
203 #[test]
204 fn single_line_to_block() {
205 check_assist(
206 convert_comment_block,
207 r#"
208// line$0 comment
209fn main() {
210 foo();
211}
212"#,
213 r#"
214/*
215line comment
216*/
217fn main() {
218 foo();
219}
220"#,
221 );
222 }
223
224 #[test]
225 fn single_line_to_block_indented() {
226 check_assist(
227 convert_comment_block,
228 r#"
229fn main() {
230 // line$0 comment
231 foo();
232}
233"#,
234 r#"
235fn main() {
236 /*
237 line comment
238 */
239 foo();
240}
241"#,
242 );
243 }
244
245 #[test]
246 fn multiline_to_block() {
247 check_assist(
248 convert_comment_block,
249 r#"
250fn main() {
251 // above
252 // line$0 comment
253 //
254 // below
255 foo();
256}
257"#,
258 r#"
259fn main() {
260 /*
261 above
262 line comment
263
264 below
265 */
266 foo();
267}
268"#,
269 );
270 }
271
272 #[test]
273 fn end_of_line_to_block() {
274 check_assist_not_applicable(
275 convert_comment_block,
276 r#"
277fn main() {
278 foo(); // end-of-line$0 comment
279}
280"#,
281 );
282 }
283
284 #[test]
285 fn single_line_different_kinds() {
286 check_assist(
287 convert_comment_block,
288 r#"
289fn main() {
290 /// different prefix
291 // line$0 comment
292 // below
293 foo();
294}
295"#,
296 r#"
297fn main() {
298 /// different prefix
299 /*
300 line comment
301 below
302 */
303 foo();
304}
305"#,
306 );
307 }
308
309 #[test]
310 fn single_line_separate_chunks() {
311 check_assist(
312 convert_comment_block,
313 r#"
314fn main() {
315 // different chunk
316
317 // line$0 comment
318 // below
319 foo();
320}
321"#,
322 r#"
323fn main() {
324 // different chunk
325
326 /*
327 line comment
328 below
329 */
330 foo();
331}
332"#,
333 );
334 }
335
336 #[test]
337 fn doc_block_comment_to_lines() {
338 check_assist(
339 convert_comment_block,
340 r#"
341/**
342 hi$0 there
343*/
344"#,
345 r#"
346/// hi there
347"#,
348 );
349 }
350
351 #[test]
352 fn block_comment_to_lines() {
353 check_assist(
354 convert_comment_block,
355 r#"
356/*
357 hi$0 there
358*/
359"#,
360 r#"
361// hi there
362"#,
363 );
364 }
365
366 #[test]
367 fn inner_doc_block_to_lines() {
368 check_assist(
369 convert_comment_block,
370 r#"
371/*!
372 hi$0 there
373*/
374"#,
375 r#"
376//! hi there
377"#,
378 );
379 }
380
381 #[test]
382 fn block_to_lines_indent() {
383 check_assist(
384 convert_comment_block,
385 r#"
386fn main() {
387 /*!
388 hi$0 there
389
390 ```
391 code_sample
392 ```
393 */
394}
395"#,
396 r#"
397fn main() {
398 //! hi there
399 //!
400 //! ```
401 //! code_sample
402 //! ```
403}
404"#,
405 );
406 }
407
408 #[test]
409 fn end_of_line_block_to_line() {
410 check_assist_not_applicable(
411 convert_comment_block,
412 r#"
413fn main() {
414 foo(); /* end-of-line$0 comment */
415}
416"#,
417 );
418 }
419}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 53542d433..9c8148462 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -115,6 +115,7 @@ mod handlers {
115 mod auto_import; 115 mod auto_import;
116 mod change_visibility; 116 mod change_visibility;
117 mod convert_integer_literal; 117 mod convert_integer_literal;
118 mod convert_comment_block;
118 mod early_return; 119 mod early_return;
119 mod expand_glob_import; 120 mod expand_glob_import;
120 mod extract_function; 121 mod extract_function;
@@ -178,6 +179,7 @@ mod handlers {
178 auto_import::auto_import, 179 auto_import::auto_import,
179 change_visibility::change_visibility, 180 change_visibility::change_visibility,
180 convert_integer_literal::convert_integer_literal, 181 convert_integer_literal::convert_integer_literal,
182 convert_comment_block::convert_comment_block,
181 early_return::convert_to_guarded_return, 183 early_return::convert_to_guarded_return,
182 expand_glob_import::expand_glob_import, 184 expand_glob_import::expand_glob_import,
183 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 185 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/proc_macro_srv/src/rustc_server.rs b/crates/proc_macro_srv/src/rustc_server.rs
index a8504f762..952b4a97f 100644
--- a/crates/proc_macro_srv/src/rustc_server.rs
+++ b/crates/proc_macro_srv/src/rustc_server.rs
@@ -14,7 +14,6 @@ use std::collections::HashMap;
14use std::hash::Hash; 14use std::hash::Hash;
15use std::iter::FromIterator; 15use std::iter::FromIterator;
16use std::ops::Bound; 16use std::ops::Bound;
17use std::str::FromStr;
18use std::{ascii, vec::IntoIter}; 17use std::{ascii, vec::IntoIter};
19 18
20type Group = tt::Subtree; 19type Group = tt::Subtree;
@@ -278,6 +277,42 @@ impl server::FreeFunctions for Rustc {
278 } 277 }
279} 278}
280 279
280fn subtree_replace_token_ids_with_unspecified(subtree: tt::Subtree) -> tt::Subtree {
281 tt::Subtree {
282 delimiter: subtree.delimiter.map(|d| tt::Delimiter { id: tt::TokenId::unspecified(), ..d }),
283 token_trees: subtree
284 .token_trees
285 .into_iter()
286 .map(|t| token_tree_replace_token_ids_with_unspecified(t))
287 .collect(),
288 }
289}
290
291fn token_tree_replace_token_ids_with_unspecified(tt: tt::TokenTree) -> tt::TokenTree {
292 match tt {
293 tt::TokenTree::Leaf(leaf) => {
294 tt::TokenTree::Leaf(leaf_replace_token_ids_with_unspecified(leaf))
295 }
296 tt::TokenTree::Subtree(subtree) => {
297 tt::TokenTree::Subtree(subtree_replace_token_ids_with_unspecified(subtree))
298 }
299 }
300}
301
302fn leaf_replace_token_ids_with_unspecified(leaf: tt::Leaf) -> tt::Leaf {
303 match leaf {
304 tt::Leaf::Literal(lit) => {
305 tt::Leaf::Literal(tt::Literal { id: tt::TokenId::unspecified(), ..lit })
306 }
307 tt::Leaf::Punct(punct) => {
308 tt::Leaf::Punct(tt::Punct { id: tt::TokenId::unspecified(), ..punct })
309 }
310 tt::Leaf::Ident(ident) => {
311 tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), ..ident })
312 }
313 }
314}
315
281impl server::TokenStream for Rustc { 316impl server::TokenStream for Rustc {
282 fn new(&mut self) -> Self::TokenStream { 317 fn new(&mut self) -> Self::TokenStream {
283 Self::TokenStream::new() 318 Self::TokenStream::new()
@@ -287,7 +322,8 @@ impl server::TokenStream for Rustc {
287 stream.is_empty() 322 stream.is_empty()
288 } 323 }
289 fn from_str(&mut self, src: &str) -> Self::TokenStream { 324 fn from_str(&mut self, src: &str) -> Self::TokenStream {
290 Self::TokenStream::from_str(src).expect("cannot parse string") 325 let (subtree, _) = mbe::parse_to_token_tree(src).expect("cannot parse string");
326 TokenStream::with_subtree(subtree_replace_token_ids_with_unspecified(subtree))
291 } 327 }
292 fn to_string(&mut self, stream: &Self::TokenStream) -> String { 328 fn to_string(&mut self, stream: &Self::TokenStream) -> String {
293 stream.to_string() 329 stream.to_string()
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index b881cc229..8789f0852 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -24,7 +24,7 @@ jod-thread = "0.1.0"
24log = "0.4.8" 24log = "0.4.8"
25lsp-types = { version = "0.88.0", features = ["proposed"] } 25lsp-types = { version = "0.88.0", features = ["proposed"] }
26parking_lot = "0.11.0" 26parking_lot = "0.11.0"
27pico-args = "0.4.0" 27xflags = "0.1.2"
28oorandom = "11.1.2" 28oorandom = "11.1.2"
29rustc-hash = "1.1.0" 29rustc-hash = "1.1.0"
30serde = { version = "1.0.106", features = ["derive"] } 30serde = { version = "1.0.106", features = ["derive"] }
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs
deleted file mode 100644
index 164d94a30..000000000
--- a/crates/rust-analyzer/src/bin/args.rs
+++ /dev/null
@@ -1,274 +0,0 @@
1//! Command like parsing for rust-analyzer.
2//!
3//! If run started args, we run the LSP server loop. With a subcommand, we do a
4//! one-time batch processing.
5
6use std::{env, path::PathBuf};
7
8use anyhow::{bail, format_err, Result};
9use ide_ssr::{SsrPattern, SsrRule};
10use pico_args::Arguments;
11use rust_analyzer::cli::{AnalysisStatsCmd, BenchCmd, BenchWhat, Position, Verbosity};
12use vfs::AbsPathBuf;
13
14pub(crate) struct Args {
15 pub(crate) verbosity: Verbosity,
16 pub(crate) log_file: Option<PathBuf>,
17 pub(crate) no_buffering: bool,
18 pub(crate) command: Command,
19 #[allow(unused)]
20 pub(crate) wait_dbg: bool,
21}
22
23pub(crate) enum Command {
24 Parse { no_dump: bool },
25 Symbols,
26 Highlight { rainbow: bool },
27 AnalysisStats(AnalysisStatsCmd),
28 Bench(BenchCmd),
29 Diagnostics { path: PathBuf, load_output_dirs: bool, with_proc_macro: bool },
30 Ssr { rules: Vec<SsrRule> },
31 StructuredSearch { debug_snippet: Option<String>, patterns: Vec<SsrPattern> },
32 ProcMacro,
33 RunServer,
34 PrintConfigSchema,
35 Version,
36 Help,
37}
38
39const HELP: &str = "\
40rust-analyzer
41
42USAGE:
43 rust-analyzer [FLAGS] [COMMAND] [COMMAND_OPTIONS]
44
45FLAGS:
46 --version Print version
47 -h, --help Print this help
48
49 -v, --verbose
50 -vv, --spammy
51 -q, --quiet Set verbosity
52
53 --print-config-schema
54 Dump a LSP config JSON schema
55 --log-file <PATH> Log to the specified file instead of stderr
56 --no-log-buffering
57 Flush log records to the file immediately
58
59 --wait-dbg Wait until a debugger is attached to.
60 The flag is valid for debug builds only
61
62ENVIRONMENTAL VARIABLES:
63 RA_LOG Set log filter in env_logger format
64 RA_PROFILE Enable hierarchical profiler
65 RA_WAIT_DBG If set acts like a --wait-dbg flag
66
67COMMANDS:
68
69not specified Launch LSP server
70
71parse < main.rs Parse tree
72 --no-dump Suppress printing
73
74symbols < main.rs Parse input an print the list of symbols
75
76highlight < main.rs Highlight input as html
77 --rainbow Enable rainbow highlighting of identifiers
78
79analysis-stats <PATH> Batch typecheck project and print summary statistics
80 <PATH> Directory with Cargo.toml
81 --randomize Randomize order in which crates, modules, and items are processed
82 --parallel Run type inference in parallel
83 --memory-usage Collect memory usage statistics
84 -o, --only <PATH> Only analyze items matching this path
85 --with-deps Also analyze all dependencies
86 --load-output-dirs
87 Load OUT_DIR values by running `cargo check` before analysis
88 --with-proc-macro Use proc-macro-srv for proc-macro expanding
89
90analysis-bench <PATH> Benchmark specific analysis operation
91 <PATH> Directory with Cargo.toml
92 --highlight <PATH>
93 Compute syntax highlighting for this file
94 --complete <PATH:LINE:COLUMN>
95 Compute completions at this location
96 --goto-def <PATH:LINE:COLUMN>
97 Compute goto definition at this location
98 --memory-usage Collect memory usage statistics
99 --load-output-dirs
100 Load OUT_DIR values by running `cargo check` before analysis
101 --with-proc-macro Use proc-macro-srv for proc-macro expanding
102
103diagnostics <PATH>
104 <PATH> Directory with Cargo.toml
105 --load-output-dirs
106 Load OUT_DIR values by running `cargo check` before analysis
107 --with-proc-macro Use proc-macro-srv for proc-macro expanding
108
109ssr [RULE...]
110 <RULE> A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
111
112search [PATTERN..]
113 <PATTERN> A structured search replace pattern (`$a.foo($b)`)
114 --debug <snippet> Prints debug information for any nodes with source exactly
115 equal to <snippet>
116";
117
118impl Args {
119 pub(crate) fn parse() -> Result<Args> {
120 let mut matches = Arguments::from_env();
121
122 if matches.contains("--version") {
123 finish_args(matches)?;
124 return Ok(Args {
125 verbosity: Verbosity::Normal,
126 log_file: None,
127 command: Command::Version,
128 no_buffering: false,
129 wait_dbg: false,
130 });
131 }
132
133 let verbosity = match (
134 matches.contains(["-vv", "--spammy"]),
135 matches.contains(["-v", "--verbose"]),
136 matches.contains(["-q", "--quiet"]),
137 ) {
138 (true, _, true) => bail!("Invalid flags: -q conflicts with -vv"),
139 (true, _, false) => Verbosity::Spammy,
140 (false, false, false) => Verbosity::Normal,
141 (false, false, true) => Verbosity::Quiet,
142 (false, true, false) => Verbosity::Verbose,
143 (false, true, true) => bail!("Invalid flags: -q conflicts with -v"),
144 };
145 let log_file = matches.opt_value_from_str("--log-file")?;
146 let no_buffering = matches.contains("--no-log-buffering");
147 let wait_dbg = matches.contains("--wait-dbg");
148
149 if matches.contains(["-h", "--help"]) {
150 eprintln!("{}", HELP);
151 return Ok(Args {
152 verbosity,
153 log_file: None,
154 command: Command::Help,
155 no_buffering,
156 wait_dbg,
157 });
158 }
159
160 if matches.contains("--print-config-schema") {
161 return Ok(Args {
162 verbosity,
163 log_file,
164 command: Command::PrintConfigSchema,
165 no_buffering,
166 wait_dbg,
167 });
168 }
169
170 let subcommand = match matches.subcommand()? {
171 Some(it) => it,
172 None => {
173 finish_args(matches)?;
174 return Ok(Args {
175 verbosity,
176 log_file,
177 command: Command::RunServer,
178 no_buffering,
179 wait_dbg,
180 });
181 }
182 };
183 let command = match subcommand.as_str() {
184 "parse" => Command::Parse { no_dump: matches.contains("--no-dump") },
185 "symbols" => Command::Symbols,
186 "highlight" => Command::Highlight { rainbow: matches.contains("--rainbow") },
187 "analysis-stats" => Command::AnalysisStats(AnalysisStatsCmd {
188 randomize: matches.contains("--randomize"),
189 parallel: matches.contains("--parallel"),
190 memory_usage: matches.contains("--memory-usage"),
191 only: matches.opt_value_from_str(["-o", "--only"])?,
192 with_deps: matches.contains("--with-deps"),
193 load_output_dirs: matches.contains("--load-output-dirs"),
194 with_proc_macro: matches.contains("--with-proc-macro"),
195 path: matches
196 .opt_free_from_str()?
197 .ok_or_else(|| format_err!("expected positional argument"))?,
198 }),
199 "analysis-bench" => Command::Bench(BenchCmd {
200 what: {
201 let highlight_path: Option<String> =
202 matches.opt_value_from_str("--highlight")?;
203 let complete_path: Option<Position> =
204 matches.opt_value_from_str("--complete")?;
205 let goto_def_path: Option<Position> =
206 matches.opt_value_from_str("--goto-def")?;
207 match (highlight_path, complete_path, goto_def_path) {
208 (Some(path), None, None) => {
209 let path = env::current_dir().unwrap().join(path);
210 BenchWhat::Highlight { path: AbsPathBuf::assert(path) }
211 }
212 (None, Some(position), None) => BenchWhat::Complete(position),
213 (None, None, Some(position)) => BenchWhat::GotoDef(position),
214 _ => panic!(
215 "exactly one of `--highlight`, `--complete` or `--goto-def` must be set"
216 ),
217 }
218 },
219 memory_usage: matches.contains("--memory-usage"),
220 load_output_dirs: matches.contains("--load-output-dirs"),
221 with_proc_macro: matches.contains("--with-proc-macro"),
222 path: matches
223 .opt_free_from_str()?
224 .ok_or_else(|| format_err!("expected positional argument"))?,
225 }),
226 "diagnostics" => Command::Diagnostics {
227 load_output_dirs: matches.contains("--load-output-dirs"),
228 with_proc_macro: matches.contains("--with-proc-macro"),
229 path: matches
230 .opt_free_from_str()?
231 .ok_or_else(|| format_err!("expected positional argument"))?,
232 },
233 "proc-macro" => Command::ProcMacro,
234 "ssr" => Command::Ssr {
235 rules: {
236 let mut acc = Vec::new();
237 while let Some(rule) = matches.opt_free_from_str()? {
238 acc.push(rule);
239 }
240 acc
241 },
242 },
243 "search" => Command::StructuredSearch {
244 debug_snippet: matches.opt_value_from_str("--debug")?,
245 patterns: {
246 let mut acc = Vec::new();
247 while let Some(rule) = matches.opt_free_from_str()? {
248 acc.push(rule);
249 }
250 acc
251 },
252 },
253 _ => {
254 eprintln!("{}", HELP);
255 return Ok(Args {
256 verbosity,
257 log_file: None,
258 command: Command::Help,
259 no_buffering,
260 wait_dbg,
261 });
262 }
263 };
264 finish_args(matches)?;
265 Ok(Args { verbosity, log_file, command, no_buffering, wait_dbg })
266 }
267}
268
269fn finish_args(args: Arguments) -> Result<()> {
270 if !args.finish().is_empty() {
271 bail!("Unused arguments.");
272 }
273 Ok(())
274}
diff --git a/crates/rust-analyzer/src/bin/flags.rs b/crates/rust-analyzer/src/bin/flags.rs
new file mode 100644
index 000000000..244912d26
--- /dev/null
+++ b/crates/rust-analyzer/src/bin/flags.rs
@@ -0,0 +1,251 @@
1//! Grammar for the command-line arguments.
2#![allow(unreachable_pub)]
3use std::{env, path::PathBuf};
4
5use ide_ssr::{SsrPattern, SsrRule};
6use rust_analyzer::cli::{BenchWhat, Position, Verbosity};
7use vfs::AbsPathBuf;
8
9xflags::args_parser! {
10 /// LSP server for the Rust programming language.
11 cmd rust-analyzer {
12 /// Verbosity level, can be repeated multiple times.
13 repeated -v, --verbose
14 /// Verbosity level.
15 optional -q, --quiet
16
17 /// Log to the specified file instead of stderr.
18 optional --log-file path: PathBuf
19 /// Flush log records to the file immediately.
20 optional --no-log-buffering
21
22 /// Wait until a debugger is attached to (requires debug build).
23 optional --wait-dbg
24
25 default cmd lsp-server {
26 /// Print version.
27 optional --version
28 /// Print help.
29 optional -h, --help
30
31 /// Dump a LSP config JSON schema.
32 optional --print-config-schema
33 }
34
35 /// Parse stdin.
36 cmd parse {
37 /// Suppress printing.
38 optional --no-dump
39 }
40
41 /// Parse stdin and print the list of symbols.
42 cmd symbols {}
43
44 /// Highlight stdin as html.
45 cmd highlight {
46 /// Enable rainbow highlighting of identifiers.
47 optional --rainbow
48 }
49
50 /// Batch typecheck project and print summary statistics
51 cmd analysis-stats
52 /// Directory with Cargo.toml.
53 required path: PathBuf
54 {
55 /// Randomize order in which crates, modules, and items are processed.
56 optional --randomize
57 /// Run type inference in parallel.
58 optional --parallel
59 /// Collect memory usage statistics.
60 optional --memory-usage
61
62 /// Only analyze items matching this path.
63 optional -o, --only path: String
64 /// Also analyze all dependencies.
65 optional --with-deps
66
67 /// Load OUT_DIR values by running `cargo check` before analysis.
68 optional --load-output-dirs
69 /// Use proc-macro-srv for proc-macro expanding.
70 optional --with-proc-macro
71 }
72
73 /// Benchmark specific analysis operation
74 cmd analysis-bench
75 /// Directory with Cargo.toml.
76 required path: PathBuf
77 {
78 /// Collect memory usage statistics.
79 optional --memory-usage
80
81 /// Compute syntax highlighting for this file
82 optional --highlight path: PathBuf
83 /// Compute completions at file:line:column location.
84 optional --complete location: Position
85 /// Compute goto definition at file:line:column location.
86 optional --goto-def location: Position
87
88 /// Load OUT_DIR values by running `cargo check` before analysis.
89 optional --load-output-dirs
90 /// Use proc-macro-srv for proc-macro expanding.
91 optional --with-proc-macro
92 }
93
94 cmd diagnostics
95 /// Directory with Cargo.toml.
96 required path: PathBuf
97 {
98 /// Load OUT_DIR values by running `cargo check` before analysis.
99 optional --load-output-dirs
100 /// Use proc-macro-srv for proc-macro expanding.
101 optional --with-proc-macro
102 }
103
104 cmd ssr
105 /// A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
106 repeated rule: SsrRule
107 {}
108
109 cmd search
110 /// A structured search replace pattern (`$a.foo($b)`)
111 repeated pattern: SsrPattern
112 {
113 /// Prints debug information for any nodes with source exactly equal to snippet.
114 optional --debug snippet: String
115 }
116
117 cmd proc-macro {}
118 }
119}
120
121// generated start
122// The following code is generated by `xflags` macro.
123// Run `env XFLAGS_DUMP= cargo build` to regenerate.
124#[derive(Debug)]
125pub struct RustAnalyzer {
126 pub verbose: u32,
127 pub quiet: bool,
128 pub log_file: Option<PathBuf>,
129 pub no_log_buffering: bool,
130 pub wait_dbg: bool,
131 pub subcommand: RustAnalyzerCmd,
132}
133
134#[derive(Debug)]
135pub enum RustAnalyzerCmd {
136 LspServer(LspServer),
137 Parse(Parse),
138 Symbols(Symbols),
139 Highlight(Highlight),
140 AnalysisStats(AnalysisStats),
141 AnalysisBench(AnalysisBench),
142 Diagnostics(Diagnostics),
143 Ssr(Ssr),
144 Search(Search),
145 ProcMacro(ProcMacro),
146}
147
148#[derive(Debug)]
149pub struct LspServer {
150 pub version: bool,
151 pub help: bool,
152 pub print_config_schema: bool,
153}
154
155#[derive(Debug)]
156pub struct Parse {
157 pub no_dump: bool,
158}
159
160#[derive(Debug)]
161pub struct Symbols {}
162
163#[derive(Debug)]
164pub struct Highlight {
165 pub rainbow: bool,
166}
167
168#[derive(Debug)]
169pub struct AnalysisStats {
170 pub path: PathBuf,
171
172 pub randomize: bool,
173 pub parallel: bool,
174 pub memory_usage: bool,
175 pub only: Option<String>,
176 pub with_deps: bool,
177 pub load_output_dirs: bool,
178 pub with_proc_macro: bool,
179}
180
181#[derive(Debug)]
182pub struct AnalysisBench {
183 pub path: PathBuf,
184
185 pub memory_usage: bool,
186 pub highlight: Option<PathBuf>,
187 pub complete: Option<Position>,
188 pub goto_def: Option<Position>,
189 pub load_output_dirs: bool,
190 pub with_proc_macro: bool,
191}
192
193#[derive(Debug)]
194pub struct Diagnostics {
195 pub path: PathBuf,
196
197 pub load_output_dirs: bool,
198 pub with_proc_macro: bool,
199}
200
201#[derive(Debug)]
202pub struct Ssr {
203 pub rule: Vec<SsrRule>,
204}
205
206#[derive(Debug)]
207pub struct Search {
208 pub pattern: Vec<SsrPattern>,
209
210 pub debug: Option<String>,
211}
212
213#[derive(Debug)]
214pub struct ProcMacro {}
215
216impl RustAnalyzer {
217 pub const HELP: &'static str = Self::_HELP;
218
219 pub fn from_env() -> xflags::Result<Self> {
220 let mut p = xflags::rt::Parser::new_from_env();
221 Self::_parse(&mut p)
222 }
223}
224// generated end
225
226impl RustAnalyzer {
227 pub(crate) fn verbosity(&self) -> Verbosity {
228 if self.quiet {
229 return Verbosity::Quiet;
230 }
231 match self.verbose {
232 0 => Verbosity::Normal,
233 1 => Verbosity::Verbose,
234 _ => Verbosity::Spammy,
235 }
236 }
237}
238
239impl AnalysisBench {
240 pub(crate) fn what(&self) -> BenchWhat {
241 match (&self.highlight, &self.complete, &self.goto_def) {
242 (Some(path), None, None) => {
243 let path = env::current_dir().unwrap().join(path);
244 BenchWhat::Highlight { path: AbsPathBuf::assert(path) }
245 }
246 (None, Some(position), None) => BenchWhat::Complete(position.clone()),
247 (None, None, Some(position)) => BenchWhat::GotoDef(position.clone()),
248 _ => panic!("exactly one of `--highlight`, `--complete` or `--goto-def` must be set"),
249 }
250 }
251}
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 89482b952..288847980 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -1,14 +1,20 @@
1//! Driver for rust-analyzer. 1//! Driver for rust-analyzer.
2//! 2//!
3//! Based on cli flags, either spawns an LSP server, or runs a batch analysis 3//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
4mod args; 4mod flags;
5mod logger; 5mod logger;
6 6
7use std::{convert::TryFrom, env, fs, path::PathBuf, process}; 7use std::{convert::TryFrom, env, fs, path::Path, process};
8 8
9use lsp_server::Connection; 9use lsp_server::Connection;
10use project_model::ProjectManifest; 10use project_model::ProjectManifest;
11use rust_analyzer::{cli, config::Config, from_json, lsp_ext::supports_utf8, Result}; 11use rust_analyzer::{
12 cli::{self, AnalysisStatsCmd, BenchCmd},
13 config::Config,
14 from_json,
15 lsp_ext::supports_utf8,
16 Result,
17};
12use vfs::AbsPathBuf; 18use vfs::AbsPathBuf;
13 19
14#[cfg(all(feature = "mimalloc"))] 20#[cfg(all(feature = "mimalloc"))]
@@ -28,10 +34,10 @@ fn main() {
28} 34}
29 35
30fn try_main() -> Result<()> { 36fn try_main() -> Result<()> {
31 let args = args::Args::parse()?; 37 let flags = flags::RustAnalyzer::from_env()?;
32 38
33 #[cfg(debug_assertions)] 39 #[cfg(debug_assertions)]
34 if args.wait_dbg || env::var("RA_WAIT_DBG").is_ok() { 40 if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
35 #[allow(unused_mut)] 41 #[allow(unused_mut)]
36 let mut d = 4; 42 let mut d = 4;
37 while d == 4 { 43 while d == 4 {
@@ -39,35 +45,62 @@ fn try_main() -> Result<()> {
39 } 45 }
40 } 46 }
41 47
42 setup_logging(args.log_file, args.no_buffering)?; 48 setup_logging(flags.log_file.as_deref(), flags.no_log_buffering)?;
43 match args.command { 49 let verbosity = flags.verbosity();
44 args::Command::RunServer => run_server()?, 50
45 args::Command::PrintConfigSchema => { 51 match flags.subcommand {
46 println!("{:#}", Config::json_schema()); 52 flags::RustAnalyzerCmd::LspServer(cmd) => {
53 if cmd.print_config_schema {
54 println!("{:#}", Config::json_schema());
55 return Ok(());
56 }
57 if cmd.version {
58 println!("rust-analyzer {}", env!("REV"));
59 return Ok(());
60 }
61 if cmd.help {
62 println!("{}", flags::RustAnalyzer::HELP);
63 return Ok(());
64 }
65 run_server()?
47 } 66 }
48 args::Command::ProcMacro => proc_macro_srv::cli::run()?, 67 flags::RustAnalyzerCmd::ProcMacro(_) => proc_macro_srv::cli::run()?,
49 68 flags::RustAnalyzerCmd::Parse(cmd) => cli::parse(cmd.no_dump)?,
50 args::Command::Parse { no_dump } => cli::parse(no_dump)?, 69 flags::RustAnalyzerCmd::Symbols(_) => cli::symbols()?,
51 args::Command::Symbols => cli::symbols()?, 70 flags::RustAnalyzerCmd::Highlight(cmd) => cli::highlight(cmd.rainbow)?,
52 args::Command::Highlight { rainbow } => cli::highlight(rainbow)?, 71 flags::RustAnalyzerCmd::AnalysisStats(cmd) => AnalysisStatsCmd {
53 args::Command::AnalysisStats(cmd) => cmd.run(args.verbosity)?, 72 randomize: cmd.randomize,
54 args::Command::Bench(cmd) => cmd.run(args.verbosity)?, 73 parallel: cmd.parallel,
55 args::Command::Diagnostics { path, load_output_dirs, with_proc_macro } => { 74 memory_usage: cmd.memory_usage,
56 cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro)? 75 only: cmd.only,
76 with_deps: cmd.with_deps,
77 path: cmd.path,
78 load_output_dirs: cmd.load_output_dirs,
79 with_proc_macro: cmd.with_proc_macro,
57 } 80 }
58 args::Command::Ssr { rules } => { 81 .run(verbosity)?,
59 cli::apply_ssr_rules(rules)?; 82 flags::RustAnalyzerCmd::AnalysisBench(cmd) => {
83 let what = cmd.what();
84 BenchCmd {
85 memory_usage: cmd.memory_usage,
86 path: cmd.path,
87 load_output_dirs: cmd.load_output_dirs,
88 with_proc_macro: cmd.with_proc_macro,
89 what,
90 }
91 .run(verbosity)?
60 } 92 }
61 args::Command::StructuredSearch { patterns, debug_snippet } => { 93
62 cli::search_for_patterns(patterns, debug_snippet)?; 94 flags::RustAnalyzerCmd::Diagnostics(cmd) => {
95 cli::diagnostics(&cmd.path, cmd.load_output_dirs, cmd.with_proc_macro)?
63 } 96 }
64 args::Command::Version => println!("rust-analyzer {}", env!("REV")), 97 flags::RustAnalyzerCmd::Ssr(cmd) => cli::apply_ssr_rules(cmd.rule)?,
65 args::Command::Help => {} 98 flags::RustAnalyzerCmd::Search(cmd) => cli::search_for_patterns(cmd.pattern, cmd.debug)?,
66 } 99 }
67 Ok(()) 100 Ok(())
68} 101}
69 102
70fn setup_logging(log_file: Option<PathBuf>, no_buffering: bool) -> Result<()> { 103fn setup_logging(log_file: Option<&Path>, no_buffering: bool) -> Result<()> {
71 env::set_var("RUST_BACKTRACE", "short"); 104 env::set_var("RUST_BACKTRACE", "short");
72 105
73 let log_file = match log_file { 106 let log_file = match log_file {
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 8991f3bdb..3bd7e678d 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -35,6 +35,7 @@ pub enum BenchWhat {
35 GotoDef(Position), 35 GotoDef(Position),
36} 36}
37 37
38#[derive(Debug, Clone)]
38pub struct Position { 39pub struct Position {
39 pub path: AbsPathBuf, 40 pub path: AbsPathBuf,
40 pub line: u32, 41 pub line: u32,
@@ -68,7 +69,7 @@ impl BenchCmd {
68 load_out_dirs_from_check: self.load_output_dirs, 69 load_out_dirs_from_check: self.load_output_dirs,
69 with_proc_macro: self.with_proc_macro, 70 with_proc_macro: self.with_proc_macro,
70 }; 71 };
71 let (mut host, vfs) = 72 let (mut host, vfs, _proc_macro) =
72 load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; 73 load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
73 eprintln!("{:?}\n", start.elapsed()); 74 eprintln!("{:?}\n", start.elapsed());
74 75
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 9072d8944..ad0759bda 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -64,7 +64,7 @@ impl AnalysisStatsCmd {
64 load_out_dirs_from_check: self.load_output_dirs, 64 load_out_dirs_from_check: self.load_output_dirs,
65 with_proc_macro: self.with_proc_macro, 65 with_proc_macro: self.with_proc_macro,
66 }; 66 };
67 let (host, vfs) = 67 let (host, vfs, _proc_macro) =
68 load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; 68 load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
69 let db = host.raw_database(); 69 let db = host.raw_database();
70 eprintln!("{:<20} {}", "Database loaded:", db_load_sw.elapsed()); 70 eprintln!("{:<20} {}", "Database loaded:", db_load_sw.elapsed());
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 876f6c44f..8b985716b 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -35,7 +35,8 @@ pub fn diagnostics(
35) -> Result<()> { 35) -> Result<()> {
36 let cargo_config = Default::default(); 36 let cargo_config = Default::default();
37 let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check, with_proc_macro }; 37 let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check, with_proc_macro };
38 let (host, _vfs) = load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {})?; 38 let (host, _vfs, _proc_macro) =
39 load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {})?;
39 let db = host.raw_database(); 40 let db = host.raw_database();
40 let analysis = host.analysis(); 41 let analysis = host.analysis();
41 42
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 23442afac..310c36904 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -23,7 +23,7 @@ pub fn load_workspace_at(
23 cargo_config: &CargoConfig, 23 cargo_config: &CargoConfig,
24 load_config: &LoadCargoConfig, 24 load_config: &LoadCargoConfig,
25 progress: &dyn Fn(String), 25 progress: &dyn Fn(String),
26) -> Result<(AnalysisHost, vfs::Vfs)> { 26) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroClient>)> {
27 let root = AbsPathBuf::assert(std::env::current_dir()?.join(root)); 27 let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
28 let root = ProjectManifest::discover_single(&root)?; 28 let root = ProjectManifest::discover_single(&root)?;
29 let workspace = ProjectWorkspace::load(root, cargo_config, progress)?; 29 let workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
@@ -35,7 +35,7 @@ pub fn load_workspace(
35 ws: ProjectWorkspace, 35 ws: ProjectWorkspace,
36 config: &LoadCargoConfig, 36 config: &LoadCargoConfig,
37 progress: &dyn Fn(String), 37 progress: &dyn Fn(String),
38) -> Result<(AnalysisHost, vfs::Vfs)> { 38) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroClient>)> {
39 let (sender, receiver) = unbounded(); 39 let (sender, receiver) = unbounded();
40 let mut vfs = vfs::Vfs::default(); 40 let mut vfs = vfs::Vfs::default();
41 let mut loader = { 41 let mut loader = {
@@ -80,7 +80,7 @@ pub fn load_workspace(
80 log::debug!("crate graph: {:?}", crate_graph); 80 log::debug!("crate graph: {:?}", crate_graph);
81 let host = 81 let host =
82 load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver); 82 load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);
83 Ok((host, vfs)) 83 Ok((host, vfs, proc_macro_client))
84} 84}
85 85
86fn load_crate_graph( 86fn load_crate_graph(
@@ -138,7 +138,8 @@ mod tests {
138 let cargo_config = Default::default(); 138 let cargo_config = Default::default();
139 let load_cargo_config = 139 let load_cargo_config =
140 LoadCargoConfig { load_out_dirs_from_check: false, with_proc_macro: false }; 140 LoadCargoConfig { load_out_dirs_from_check: false, with_proc_macro: false };
141 let (host, _vfs) = load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {})?; 141 let (host, _vfs, _proc_macro) =
142 load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {})?;
142 143
143 let n_crates = Crate::all(host.raw_database()).len(); 144 let n_crates = Crate::all(host.raw_database()).len();
144 // RA has quite a few crates, but the exact count doesn't matter 145 // RA has quite a few crates, but the exact count doesn't matter
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 71a8f8fb9..79f426fff 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -11,7 +11,7 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
11 let cargo_config = Default::default(); 11 let cargo_config = Default::default();
12 let load_cargo_config = 12 let load_cargo_config =
13 LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro: true }; 13 LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro: true };
14 let (host, vfs) = 14 let (host, vfs, _proc_macro) =
15 load_workspace_at(&std::env::current_dir()?, &cargo_config, &load_cargo_config, &|_| {})?; 15 load_workspace_at(&std::env::current_dir()?, &cargo_config, &load_cargo_config, &|_| {})?;
16 let db = host.raw_database(); 16 let db = host.raw_database();
17 let mut match_finder = MatchFinder::at_first_file(db)?; 17 let mut match_finder = MatchFinder::at_first_file(db)?;
@@ -38,7 +38,7 @@ pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<Stri
38 let cargo_config = Default::default(); 38 let cargo_config = Default::default();
39 let load_cargo_config = 39 let load_cargo_config =
40 LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro: true }; 40 LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro: true };
41 let (host, _vfs) = 41 let (host, _vfs, _proc_macro) =
42 load_workspace_at(&std::env::current_dir()?, &cargo_config, &load_cargo_config, &|_| {})?; 42 load_workspace_at(&std::env::current_dir()?, &cargo_config, &load_cargo_config, &|_| {})?;
43 let db = host.raw_database(); 43 let db = host.raw_database();
44 let mut match_finder = MatchFinder::at_first_file(db)?; 44 let mut match_finder = MatchFinder::at_first_file(db)?;
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 824ebf41c..0b3b76d4a 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -595,11 +595,14 @@ impl ops::Add<u8> for IndentLevel {
595 595
596impl IndentLevel { 596impl IndentLevel {
597 pub fn from_node(node: &SyntaxNode) -> IndentLevel { 597 pub fn from_node(node: &SyntaxNode) -> IndentLevel {
598 let first_token = match node.first_token() { 598 match node.first_token() {
599 Some(it) => it, 599 Some(it) => Self::from_token(&it),
600 None => return IndentLevel(0), 600 None => return IndentLevel(0),
601 }; 601 }
602 for ws in prev_tokens(first_token).filter_map(ast::Whitespace::cast) { 602 }
603
604 pub fn from_token(token: &SyntaxToken) -> IndentLevel {
605 for ws in prev_tokens(token.clone()).filter_map(ast::Whitespace::cast) {
603 let text = ws.syntax().text(); 606 let text = ws.syntax().text();
604 if let Some(pos) = text.rfind('\n') { 607 if let Some(pos) = text.rfind('\n') {
605 let level = text[pos + 1..].chars().count() / 4; 608 let level = text[pos + 1..].chars().count() / 4;
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 044e3e5e8..977eb8181 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -85,8 +85,9 @@ pub enum CommentPlacement {
85} 85}
86 86
87impl CommentKind { 87impl CommentKind {
88 const BY_PREFIX: [(&'static str, CommentKind); 8] = [ 88 const BY_PREFIX: [(&'static str, CommentKind); 9] = [
89 ("/**/", CommentKind { shape: CommentShape::Block, doc: None }), 89 ("/**/", CommentKind { shape: CommentShape::Block, doc: None }),
90 ("/***", CommentKind { shape: CommentShape::Block, doc: None }),
90 ("////", CommentKind { shape: CommentShape::Line, doc: None }), 91 ("////", CommentKind { shape: CommentShape::Line, doc: None }),
91 ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }), 92 ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }),
92 ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }), 93 ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }),