aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/src')
-rw-r--r--crates/ra_lsp_server/src/args.rs242
-rw-r--r--crates/ra_lsp_server/src/cli.rs75
-rw-r--r--crates/ra_lsp_server/src/cli/analysis_bench.rs158
-rw-r--r--crates/ra_lsp_server/src/cli/analysis_stats.rs259
-rw-r--r--crates/ra_lsp_server/src/cli/load_cargo.rs153
-rw-r--r--crates/ra_lsp_server/src/cli/progress_report.rs120
-rw-r--r--crates/ra_lsp_server/src/lib.rs3
-rw-r--r--crates/ra_lsp_server/src/main.rs46
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs2
-rw-r--r--crates/ra_lsp_server/src/vfs_glob.rs94
-rw-r--r--crates/ra_lsp_server/src/world.rs2
11 files changed, 1135 insertions, 19 deletions
diff --git a/crates/ra_lsp_server/src/args.rs b/crates/ra_lsp_server/src/args.rs
new file mode 100644
index 000000000..3890fe13a
--- /dev/null
+++ b/crates/ra_lsp_server/src/args.rs
@@ -0,0 +1,242 @@
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 anyhow::{bail, Result};
7use pico_args::Arguments;
8use ra_lsp_server::cli::{BenchWhat, Position, Verbosity};
9
10use std::{fmt::Write, path::PathBuf};
11
12pub(crate) struct Args {
13 pub(crate) verbosity: Verbosity,
14 pub(crate) command: Command,
15}
16
17pub(crate) enum Command {
18 Parse {
19 no_dump: bool,
20 },
21 Symbols,
22 Highlight {
23 rainbow: bool,
24 },
25 Stats {
26 randomize: bool,
27 memory_usage: bool,
28 only: Option<String>,
29 with_deps: bool,
30 path: PathBuf,
31 },
32 Bench {
33 path: PathBuf,
34 what: BenchWhat,
35 },
36 RunServer,
37 Version,
38}
39
40impl Args {
41 pub(crate) fn parse() -> Result<Result<Args, HelpPrinted>> {
42 let mut matches = Arguments::from_env();
43
44 if matches.contains("--version") {
45 matches.finish().or_else(handle_extra_flags)?;
46 return Ok(Ok(Args { verbosity: Verbosity::Normal, command: Command::Version }));
47 }
48
49 let verbosity = match (
50 matches.contains(["-vv", "--spammy"]),
51 matches.contains(["-v", "--verbose"]),
52 matches.contains(["-q", "--quiet"]),
53 ) {
54 (true, _, true) => bail!("Invalid flags: -q conflicts with -vv"),
55 (true, _, false) => Verbosity::Spammy,
56 (false, false, false) => Verbosity::Normal,
57 (false, false, true) => Verbosity::Quiet,
58 (false, true, false) => Verbosity::Verbose,
59 (false, true, true) => bail!("Invalid flags: -q conflicts with -v"),
60 };
61
62 let subcommand = match matches.subcommand()? {
63 Some(it) => it,
64 None => {
65 matches.finish().or_else(handle_extra_flags)?;
66 return Ok(Ok(Args { verbosity, command: Command::RunServer }));
67 }
68 };
69 let command = match subcommand.as_str() {
70 "parse" => {
71 if matches.contains(["-h", "--help"]) {
72 eprintln!(
73 "\
74ra-cli-parse
75
76USAGE:
77 ra_lsp_server parse [FLAGS]
78
79FLAGS:
80 -h, --help Prints help inforamtion
81 --no-dump"
82 );
83 return Ok(Err(HelpPrinted));
84 }
85
86 let no_dump = matches.contains("--no-dump");
87 matches.finish().or_else(handle_extra_flags)?;
88 Command::Parse { no_dump }
89 }
90 "symbols" => {
91 if matches.contains(["-h", "--help"]) {
92 eprintln!(
93 "\
94ra-cli-symbols
95
96USAGE:
97 ra_lsp_server highlight [FLAGS]
98
99FLAGS:
100 -h, --help Prints help inforamtion"
101 );
102 return Ok(Err(HelpPrinted));
103 }
104
105 matches.finish().or_else(handle_extra_flags)?;
106
107 Command::Symbols
108 }
109 "highlight" => {
110 if matches.contains(["-h", "--help"]) {
111 eprintln!(
112 "\
113ra-cli-highlight
114
115USAGE:
116 ra_lsp_server highlight [FLAGS]
117
118FLAGS:
119 -h, --help Prints help information
120 -r, --rainbow"
121 );
122 return Ok(Err(HelpPrinted));
123 }
124
125 let rainbow = matches.contains(["-r", "--rainbow"]);
126 matches.finish().or_else(handle_extra_flags)?;
127 Command::Highlight { rainbow }
128 }
129 "analysis-stats" => {
130 if matches.contains(["-h", "--help"]) {
131 eprintln!(
132 "\
133ra-cli-analysis-stats
134
135USAGE:
136 ra_lsp_server analysis-stats [FLAGS] [OPTIONS] [PATH]
137
138FLAGS:
139 -h, --help Prints help information
140 --memory-usage
141 -v, --verbose
142 -q, --quiet
143
144OPTIONS:
145 -o <ONLY>
146
147ARGS:
148 <PATH>"
149 );
150 return Ok(Err(HelpPrinted));
151 }
152
153 let randomize = matches.contains("--randomize");
154 let memory_usage = matches.contains("--memory-usage");
155 let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?;
156 let with_deps: bool = matches.contains("--with-deps");
157 let path = {
158 let mut trailing = matches.free()?;
159 if trailing.len() != 1 {
160 bail!("Invalid flags");
161 }
162 trailing.pop().unwrap().into()
163 };
164
165 Command::Stats { randomize, memory_usage, only, with_deps, path }
166 }
167 "analysis-bench" => {
168 if matches.contains(["-h", "--help"]) {
169 eprintln!(
170 "\
171ra_lsp_server-analysis-bench
172
173USAGE:
174 ra_lsp_server analysis-bench [FLAGS] [OPTIONS] [PATH]
175
176FLAGS:
177 -h, --help Prints help information
178 -v, --verbose
179
180OPTIONS:
181 --complete <PATH:LINE:COLUMN> Compute completions at this location
182 --highlight <PATH> Hightlight this file
183
184ARGS:
185 <PATH> Project to analyse"
186 );
187 return Ok(Err(HelpPrinted));
188 }
189
190 let path: PathBuf = matches.opt_value_from_str("--path")?.unwrap_or_default();
191 let highlight_path: Option<String> = matches.opt_value_from_str("--highlight")?;
192 let complete_path: Option<Position> = matches.opt_value_from_str("--complete")?;
193 let goto_def_path: Option<Position> = matches.opt_value_from_str("--goto-def")?;
194 let what = match (highlight_path, complete_path, goto_def_path) {
195 (Some(path), None, None) => BenchWhat::Highlight { path: path.into() },
196 (None, Some(position), None) => BenchWhat::Complete(position),
197 (None, None, Some(position)) => BenchWhat::GotoDef(position),
198 _ => panic!(
199 "exactly one of `--highlight`, `--complete` or `--goto-def` must be set"
200 ),
201 };
202 Command::Bench { path, what }
203 }
204 _ => {
205 eprintln!(
206 "\
207ra-cli
208
209USAGE:
210 ra_lsp_server <SUBCOMMAND>
211
212FLAGS:
213 -h, --help Prints help information
214
215SUBCOMMANDS:
216 analysis-bench
217 analysis-stats
218 highlight
219 parse
220 symbols"
221 );
222 return Ok(Err(HelpPrinted));
223 }
224 };
225 Ok(Ok(Args { verbosity, command }))
226 }
227}
228
229pub(crate) struct HelpPrinted;
230
231fn handle_extra_flags(e: pico_args::Error) -> Result<()> {
232 if let pico_args::Error::UnusedArgsLeft(flags) = e {
233 let mut invalid_flags = String::new();
234 for flag in flags {
235 write!(&mut invalid_flags, "{}, ", flag)?;
236 }
237 let (invalid_flags, _) = invalid_flags.split_at(invalid_flags.len() - 2);
238 bail!("Invalid flags: {}", invalid_flags);
239 } else {
240 bail!(e);
241 }
242}
diff --git a/crates/ra_lsp_server/src/cli.rs b/crates/ra_lsp_server/src/cli.rs
new file mode 100644
index 000000000..3c7b8e250
--- /dev/null
+++ b/crates/ra_lsp_server/src/cli.rs
@@ -0,0 +1,75 @@
1//! FIXME: write short doc here
2
3mod load_cargo;
4mod analysis_stats;
5mod analysis_bench;
6mod progress_report;
7
8use std::io::Read;
9
10use anyhow::Result;
11use ra_ide::{file_structure, Analysis};
12use ra_prof::profile;
13use ra_syntax::{AstNode, SourceFile};
14
15#[derive(Clone, Copy)]
16pub enum Verbosity {
17 Spammy,
18 Verbose,
19 Normal,
20 Quiet,
21}
22
23impl Verbosity {
24 pub fn is_verbose(self) -> bool {
25 match self {
26 Verbosity::Verbose | Verbosity::Spammy => true,
27 _ => false,
28 }
29 }
30 pub fn is_spammy(self) -> bool {
31 match self {
32 Verbosity::Spammy => true,
33 _ => false,
34 }
35 }
36}
37
38pub fn parse(no_dump: bool) -> Result<()> {
39 let _p = profile("parsing");
40 let file = file()?;
41 if !no_dump {
42 println!("{:#?}", file.syntax());
43 }
44 std::mem::forget(file);
45 Ok(())
46}
47
48pub fn symbols() -> Result<()> {
49 let file = file()?;
50 for s in file_structure(&file) {
51 println!("{:?}", s);
52 }
53 Ok(())
54}
55
56pub fn highlight(rainbow: bool) -> Result<()> {
57 let (analysis, file_id) = Analysis::from_single_file(read_stdin()?);
58 let html = analysis.highlight_as_html(file_id, rainbow).unwrap();
59 println!("{}", html);
60 Ok(())
61}
62
63pub use analysis_bench::{analysis_bench, BenchWhat, Position};
64pub use analysis_stats::analysis_stats;
65
66fn file() -> Result<SourceFile> {
67 let text = read_stdin()?;
68 Ok(SourceFile::parse(&text).tree())
69}
70
71fn read_stdin() -> Result<String> {
72 let mut buff = String::new();
73 std::io::stdin().read_to_string(&mut buff)?;
74 Ok(buff)
75}
diff --git a/crates/ra_lsp_server/src/cli/analysis_bench.rs b/crates/ra_lsp_server/src/cli/analysis_bench.rs
new file mode 100644
index 000000000..e00f81073
--- /dev/null
+++ b/crates/ra_lsp_server/src/cli/analysis_bench.rs
@@ -0,0 +1,158 @@
1//! FIXME: write short doc here
2
3use std::{
4 path::{Path, PathBuf},
5 str::FromStr,
6 sync::Arc,
7 time::Instant,
8};
9
10use anyhow::{format_err, Result};
11use ra_db::{
12 salsa::{Database, Durability},
13 FileId, SourceDatabaseExt,
14};
15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol};
16
17use crate::cli::{load_cargo::load_cargo, Verbosity};
18
19pub enum BenchWhat {
20 Highlight { path: PathBuf },
21 Complete(Position),
22 GotoDef(Position),
23}
24
25pub struct Position {
26 pub path: PathBuf,
27 pub line: u32,
28 pub column: u32,
29}
30
31impl FromStr for Position {
32 type Err = anyhow::Error;
33 fn from_str(s: &str) -> Result<Self> {
34 let (path_line, column) = rsplit_at_char(s, ':')?;
35 let (path, line) = rsplit_at_char(path_line, ':')?;
36 Ok(Position { path: path.into(), line: line.parse()?, column: column.parse()? })
37 }
38}
39
40fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
41 let idx = s.rfind(c).ok_or_else(|| format_err!("no `{}` in {}", c, s))?;
42 Ok((&s[..idx], &s[idx + 1..]))
43}
44
45pub fn analysis_bench(verbosity: Verbosity, path: &Path, what: BenchWhat) -> Result<()> {
46 ra_prof::init();
47
48 let start = Instant::now();
49 eprint!("loading: ");
50 let (mut host, roots) = load_cargo(path)?;
51 let db = host.raw_database();
52 eprintln!("{:?}\n", start.elapsed());
53
54 let file_id = {
55 let path = match &what {
56 BenchWhat::Highlight { path } => path,
57 BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path,
58 };
59 let path = std::env::current_dir()?.join(path).canonicalize()?;
60 roots
61 .iter()
62 .find_map(|(source_root_id, project_root)| {
63 if project_root.is_member() {
64 for file_id in db.source_root(*source_root_id).walk() {
65 let rel_path = db.file_relative_path(file_id);
66 let abs_path = rel_path.to_path(project_root.path());
67 if abs_path == path {
68 return Some(file_id);
69 }
70 }
71 }
72 None
73 })
74 .ok_or_else(|| format_err!("Can't find {}", path.display()))?
75 };
76
77 match &what {
78 BenchWhat::Highlight { .. } => {
79 let res = do_work(&mut host, file_id, |analysis| {
80 analysis.diagnostics(file_id).unwrap();
81 analysis.highlight_as_html(file_id, false).unwrap()
82 });
83 if verbosity.is_verbose() {
84 println!("\n{}", res);
85 }
86 }
87 BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => {
88 let is_completion = match what {
89 BenchWhat::Complete(..) => true,
90 _ => false,
91 };
92
93 let offset = host
94 .analysis()
95 .file_line_index(file_id)?
96 .offset(LineCol { line: pos.line - 1, col_utf16: pos.column });
97 let file_postion = FilePosition { file_id, offset };
98
99 if is_completion {
100 let res =
101 do_work(&mut host, file_id, |analysis| analysis.completions(file_postion));
102 if verbosity.is_verbose() {
103 println!("\n{:#?}", res);
104 }
105 } else {
106 let res =
107 do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_postion));
108 if verbosity.is_verbose() {
109 println!("\n{:#?}", res);
110 }
111 }
112 }
113 }
114 Ok(())
115}
116
117fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T {
118 {
119 let start = Instant::now();
120 eprint!("from scratch: ");
121 work(&host.analysis());
122 eprintln!("{:?}", start.elapsed());
123 }
124 {
125 let start = Instant::now();
126 eprint!("no change: ");
127 work(&host.analysis());
128 eprintln!("{:?}", start.elapsed());
129 }
130 {
131 let start = Instant::now();
132 eprint!("trivial change: ");
133 host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW);
134 work(&host.analysis());
135 eprintln!("{:?}", start.elapsed());
136 }
137 {
138 let start = Instant::now();
139 eprint!("comment change: ");
140 {
141 let mut text = host.analysis().file_text(file_id).unwrap().to_string();
142 text.push_str("\n/* Hello world */\n");
143 let mut change = AnalysisChange::new();
144 change.change_file(file_id, Arc::new(text));
145 host.apply_change(change);
146 }
147 work(&host.analysis());
148 eprintln!("{:?}", start.elapsed());
149 }
150 {
151 let start = Instant::now();
152 eprint!("const change: ");
153 host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH);
154 let res = work(&host.analysis());
155 eprintln!("{:?}", start.elapsed());
156 res
157 }
158}
diff --git a/crates/ra_lsp_server/src/cli/analysis_stats.rs b/crates/ra_lsp_server/src/cli/analysis_stats.rs
new file mode 100644
index 000000000..c27fabe3c
--- /dev/null
+++ b/crates/ra_lsp_server/src/cli/analysis_stats.rs
@@ -0,0 +1,259 @@
1//! FIXME: write short doc here
2
3use std::{collections::HashSet, fmt::Write, path::Path, time::Instant};
4
5use hir::{
6 db::{DefDatabase, HirDatabase},
7 AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
8};
9use hir_def::FunctionId;
10use hir_ty::{Ty, TypeWalk};
11use itertools::Itertools;
12use ra_db::SourceDatabaseExt;
13use ra_syntax::AstNode;
14use rand::{seq::SliceRandom, thread_rng};
15
16use crate::cli::{load_cargo::load_cargo, progress_report::ProgressReport, Result, Verbosity};
17
18pub fn analysis_stats(
19 verbosity: Verbosity,
20 memory_usage: bool,
21 path: &Path,
22 only: Option<&str>,
23 with_deps: bool,
24 randomize: bool,
25) -> Result<()> {
26 let db_load_time = Instant::now();
27 let (mut host, roots) = load_cargo(path)?;
28 let db = host.raw_database();
29 println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
30 let analysis_time = Instant::now();
31 let mut num_crates = 0;
32 let mut visited_modules = HashSet::new();
33 let mut visit_queue = Vec::new();
34
35 let members =
36 roots
37 .into_iter()
38 .filter_map(|(source_root_id, project_root)| {
39 if with_deps || project_root.is_member() {
40 Some(source_root_id)
41 } else {
42 None
43 }
44 })
45 .collect::<HashSet<_>>();
46
47 let mut krates = Crate::all(db);
48 if randomize {
49 krates.shuffle(&mut thread_rng());
50 }
51 for krate in krates {
52 let module = krate.root_module(db).expect("crate without root module");
53 let file_id = module.definition_source(db).file_id;
54 if members.contains(&db.file_source_root(file_id.original_file(db))) {
55 num_crates += 1;
56 visit_queue.push(module);
57 }
58 }
59
60 if randomize {
61 visit_queue.shuffle(&mut thread_rng());
62 }
63
64 println!("Crates in this dir: {}", num_crates);
65 let mut num_decls = 0;
66 let mut funcs = Vec::new();
67 while let Some(module) = visit_queue.pop() {
68 if visited_modules.insert(module) {
69 visit_queue.extend(module.children(db));
70
71 for decl in module.declarations(db) {
72 num_decls += 1;
73 if let ModuleDef::Function(f) = decl {
74 funcs.push(f);
75 }
76 }
77
78 for impl_block in module.impl_blocks(db) {
79 for item in impl_block.items(db) {
80 num_decls += 1;
81 if let AssocItem::Function(f) = item {
82 funcs.push(f);
83 }
84 }
85 }
86 }
87 }
88 println!("Total modules found: {}", visited_modules.len());
89 println!("Total declarations: {}", num_decls);
90 println!("Total functions: {}", funcs.len());
91 println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
92
93 if randomize {
94 funcs.shuffle(&mut thread_rng());
95 }
96
97 let inference_time = Instant::now();
98 let mut bar = match verbosity {
99 Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
100 _ => ProgressReport::new(funcs.len() as u64),
101 };
102
103 bar.tick();
104 let mut num_exprs = 0;
105 let mut num_exprs_unknown = 0;
106 let mut num_exprs_partially_unknown = 0;
107 let mut num_type_mismatches = 0;
108 for f in funcs {
109 let name = f.name(db);
110 let full_name = f
111 .module(db)
112 .path_to_root(db)
113 .into_iter()
114 .rev()
115 .filter_map(|it| it.name(db))
116 .chain(Some(f.name(db)))
117 .join("::");
118 if let Some(only_name) = only {
119 if name.to_string() != only_name && full_name != only_name {
120 continue;
121 }
122 }
123 let mut msg = format!("processing: {}", full_name);
124 if verbosity.is_verbose() {
125 let src = f.source(db);
126 let original_file = src.file_id.original_file(db);
127 let path = db.file_relative_path(original_file);
128 let syntax_range = src.value.syntax().text_range();
129 write!(msg, " ({:?} {})", path, syntax_range).unwrap();
130 }
131 if verbosity.is_spammy() {
132 bar.println(format!("{}", msg));
133 }
134 bar.set_message(&msg);
135 let f_id = FunctionId::from(f);
136 let body = db.body(f_id.into());
137 let inference_result = db.infer(f_id.into());
138 let (previous_exprs, previous_unknown, previous_partially_unknown) =
139 (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
140 for (expr_id, _) in body.exprs.iter() {
141 let ty = &inference_result[expr_id];
142 num_exprs += 1;
143 if let Ty::Unknown = ty {
144 num_exprs_unknown += 1;
145 } else {
146 let mut is_partially_unknown = false;
147 ty.walk(&mut |ty| {
148 if let Ty::Unknown = ty {
149 is_partially_unknown = true;
150 }
151 });
152 if is_partially_unknown {
153 num_exprs_partially_unknown += 1;
154 }
155 }
156 if only.is_some() && verbosity.is_spammy() {
157 // in super-verbose mode for just one function, we print every single expression
158 let (_, sm) = db.body_with_source_map(f_id.into());
159 let src = sm.expr_syntax(expr_id);
160 if let Some(src) = src {
161 let original_file = src.file_id.original_file(db);
162 let line_index = host.analysis().file_line_index(original_file).unwrap();
163 let text_range = src.value.either(
164 |it| it.syntax_node_ptr().range(),
165 |it| it.syntax_node_ptr().range(),
166 );
167 let (start, end) = (
168 line_index.line_col(text_range.start()),
169 line_index.line_col(text_range.end()),
170 );
171 bar.println(format!(
172 "{}:{}-{}:{}: {}",
173 start.line + 1,
174 start.col_utf16,
175 end.line + 1,
176 end.col_utf16,
177 ty.display(db)
178 ));
179 } else {
180 bar.println(format!("unknown location: {}", ty.display(db)));
181 }
182 }
183 if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
184 num_type_mismatches += 1;
185 if verbosity.is_verbose() {
186 let (_, sm) = db.body_with_source_map(f_id.into());
187 let src = sm.expr_syntax(expr_id);
188 if let Some(src) = src {
189 // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
190 let original_file = src.file_id.original_file(db);
191 let path = db.file_relative_path(original_file);
192 let line_index = host.analysis().file_line_index(original_file).unwrap();
193 let text_range = src.value.either(
194 |it| it.syntax_node_ptr().range(),
195 |it| it.syntax_node_ptr().range(),
196 );
197 let (start, end) = (
198 line_index.line_col(text_range.start()),
199 line_index.line_col(text_range.end()),
200 );
201 bar.println(format!(
202 "{} {}:{}-{}:{}: Expected {}, got {}",
203 path,
204 start.line + 1,
205 start.col_utf16,
206 end.line + 1,
207 end.col_utf16,
208 mismatch.expected.display(db),
209 mismatch.actual.display(db)
210 ));
211 } else {
212 bar.println(format!(
213 "{}: Expected {}, got {}",
214 name,
215 mismatch.expected.display(db),
216 mismatch.actual.display(db)
217 ));
218 }
219 }
220 }
221 }
222 if verbosity.is_spammy() {
223 bar.println(format!(
224 "In {}: {} exprs, {} unknown, {} partial",
225 full_name,
226 num_exprs - previous_exprs,
227 num_exprs_unknown - previous_unknown,
228 num_exprs_partially_unknown - previous_partially_unknown
229 ));
230 }
231 bar.inc(1);
232 }
233 bar.finish_and_clear();
234 println!("Total expressions: {}", num_exprs);
235 println!(
236 "Expressions of unknown type: {} ({}%)",
237 num_exprs_unknown,
238 if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
239 );
240 println!(
241 "Expressions of partially unknown type: {} ({}%)",
242 num_exprs_partially_unknown,
243 if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
244 );
245 println!("Type mismatches: {}", num_type_mismatches);
246 println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage());
247 println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
248
249 if memory_usage {
250 for (name, bytes) in host.per_query_memory_usage() {
251 println!("{:>8} {}", bytes, name)
252 }
253 let before = ra_prof::memory_usage();
254 drop(host);
255 println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated)
256 }
257
258 Ok(())
259}
diff --git a/crates/ra_lsp_server/src/cli/load_cargo.rs b/crates/ra_lsp_server/src/cli/load_cargo.rs
new file mode 100644
index 000000000..bb3e1513b
--- /dev/null
+++ b/crates/ra_lsp_server/src/cli/load_cargo.rs
@@ -0,0 +1,153 @@
1//! FIXME: write short doc here
2
3use std::{collections::HashSet, path::Path};
4
5use crossbeam_channel::{unbounded, Receiver};
6use ra_db::{CrateGraph, FileId, SourceRootId};
7use ra_ide::{AnalysisChange, AnalysisHost, FeatureFlags};
8use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace};
9use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
10use rustc_hash::FxHashMap;
11
12use crate::vfs_glob::RustPackageFilterBuilder;
13
14use anyhow::Result;
15
16fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId {
17 FileId(f.0)
18}
19fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
20 SourceRootId(r.0)
21}
22
23pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
24 let root = std::env::current_dir()?.join(root);
25 let ws = ProjectWorkspace::discover(root.as_ref(), &Default::default())?;
26 let project_roots = ws.to_roots();
27 let (sender, receiver) = unbounded();
28 let sender = Box::new(move |t| sender.send(t).unwrap());
29 let (mut vfs, roots) = Vfs::new(
30 project_roots
31 .iter()
32 .map(|pkg_root| {
33 RootEntry::new(
34 pkg_root.path().clone(),
35 RustPackageFilterBuilder::default()
36 .set_member(pkg_root.is_member())
37 .into_vfs_filter(),
38 )
39 })
40 .collect(),
41 sender,
42 Watch(false),
43 );
44
45 // FIXME: cfg options?
46 let default_cfg_options = {
47 let mut opts = get_rustc_cfg_options();
48 opts.insert_atom("test".into());
49 opts.insert_atom("debug_assertion".into());
50 opts
51 };
52
53 let (crate_graph, _crate_names) =
54 ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| {
55 let vfs_file = vfs.load(path);
56 log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
57 vfs_file.map(vfs_file_to_id)
58 });
59 log::debug!("crate graph: {:?}", crate_graph);
60
61 let source_roots = roots
62 .iter()
63 .map(|&vfs_root| {
64 let source_root_id = vfs_root_to_id(vfs_root);
65 let project_root = project_roots
66 .iter()
67 .find(|it| it.path() == &vfs.root2path(vfs_root))
68 .unwrap()
69 .clone();
70 (source_root_id, project_root)
71 })
72 .collect::<FxHashMap<_, _>>();
73 let host = load(&source_roots, crate_graph, &mut vfs, receiver);
74 Ok((host, source_roots))
75}
76
77pub fn load(
78 source_roots: &FxHashMap<SourceRootId, PackageRoot>,
79 crate_graph: CrateGraph,
80 vfs: &mut Vfs,
81 receiver: Receiver<VfsTask>,
82) -> AnalysisHost {
83 let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
84 let mut host = AnalysisHost::new(lru_cap, FeatureFlags::default());
85 let mut analysis_change = AnalysisChange::new();
86 analysis_change.set_crate_graph(crate_graph);
87
88 // wait until Vfs has loaded all roots
89 let mut roots_loaded = HashSet::new();
90 for task in receiver {
91 vfs.handle_task(task);
92 let mut done = false;
93 for change in vfs.commit_changes() {
94 match change {
95 VfsChange::AddRoot { root, files } => {
96 let source_root_id = vfs_root_to_id(root);
97 let is_local = source_roots[&source_root_id].is_member();
98 log::debug!(
99 "loaded source root {:?} with path {:?}",
100 source_root_id,
101 vfs.root2path(root)
102 );
103 analysis_change.add_root(source_root_id, is_local);
104 analysis_change.set_debug_root_path(
105 source_root_id,
106 source_roots[&source_root_id].path().display().to_string(),
107 );
108
109 let mut file_map = FxHashMap::default();
110 for (vfs_file, path, text) in files {
111 let file_id = vfs_file_to_id(vfs_file);
112 analysis_change.add_file(source_root_id, file_id, path.clone(), text);
113 file_map.insert(path, file_id);
114 }
115 roots_loaded.insert(source_root_id);
116 if roots_loaded.len() == vfs.n_roots() {
117 done = true;
118 }
119 }
120 VfsChange::AddFile { root, file, path, text } => {
121 let source_root_id = vfs_root_to_id(root);
122 let file_id = vfs_file_to_id(file);
123 analysis_change.add_file(source_root_id, file_id, path, text);
124 }
125 VfsChange::RemoveFile { .. } | VfsChange::ChangeFile { .. } => {
126 // We just need the first scan, so just ignore these
127 }
128 }
129 }
130 if done {
131 break;
132 }
133 }
134
135 host.apply_change(analysis_change);
136 host
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 use hir::Crate;
144
145 #[test]
146 fn test_loading_rust_analyzer() {
147 let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
148 let (host, _roots) = load_cargo(path).unwrap();
149 let n_crates = Crate::all(host.raw_database()).len();
150 // RA has quite a few crates, but the exact count doesn't matter
151 assert!(n_crates > 20);
152 }
153}
diff --git a/crates/ra_lsp_server/src/cli/progress_report.rs b/crates/ra_lsp_server/src/cli/progress_report.rs
new file mode 100644
index 000000000..31867a1e9
--- /dev/null
+++ b/crates/ra_lsp_server/src/cli/progress_report.rs
@@ -0,0 +1,120 @@
1//! A simple progress bar
2//!
3//! A single thread non-optimized progress bar
4use std::io::Write;
5
6/// A Simple ASCII Progress Bar
7pub struct ProgressReport {
8 curr: f32,
9 text: String,
10 hidden: bool,
11
12 len: u64,
13 pos: u64,
14 msg: String,
15}
16
17impl ProgressReport {
18 pub fn new(len: u64) -> ProgressReport {
19 ProgressReport {
20 curr: 0.0,
21 text: String::new(),
22 hidden: false,
23 len,
24 pos: 0,
25 msg: String::new(),
26 }
27 }
28
29 pub fn hidden() -> ProgressReport {
30 ProgressReport {
31 curr: 0.0,
32 text: String::new(),
33 hidden: true,
34 len: 0,
35 pos: 0,
36 msg: String::new(),
37 }
38 }
39
40 pub fn set_message(&mut self, msg: &str) {
41 self.msg = msg.to_string();
42 self.tick();
43 }
44
45 pub fn println<I: Into<String>>(&mut self, msg: I) {
46 self.clear();
47 println!("{}", msg.into());
48 self.tick();
49 }
50
51 pub fn inc(&mut self, delta: u64) {
52 self.pos += delta;
53 if self.len == 0 {
54 self.set_value(0.0)
55 } else {
56 self.set_value((self.pos as f32) / (self.len as f32))
57 }
58 self.tick();
59 }
60
61 pub fn finish_and_clear(&mut self) {
62 self.clear();
63 }
64
65 pub fn tick(&mut self) {
66 if self.hidden {
67 return;
68 }
69 let percent = (self.curr * 100.0) as u32;
70 let text = format!("{}/{} {:3>}% {}", self.pos, self.len, percent, self.msg);
71 self.update_text(&text);
72 }
73
74 fn update_text(&mut self, text: &str) {
75 // Get length of common portion
76 let mut common_prefix_length = 0;
77 let common_length = usize::min(self.text.len(), text.len());
78
79 while common_prefix_length < common_length
80 && text.chars().nth(common_prefix_length).unwrap()
81 == self.text.chars().nth(common_prefix_length).unwrap()
82 {
83 common_prefix_length += 1;
84 }
85
86 // Backtrack to the first differing character
87 let mut output = String::new();
88 output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length);
89 // Output new suffix
90 output += &text[common_prefix_length..text.len()];
91
92 // If the new text is shorter than the old one: delete overlapping characters
93 if let Some(overlap_count) = self.text.len().checked_sub(text.len()) {
94 if overlap_count > 0 {
95 output += &" ".repeat(overlap_count);
96 output += &"\x08".repeat(overlap_count);
97 }
98 }
99
100 let _ = std::io::stdout().write(output.as_bytes());
101 let _ = std::io::stdout().flush();
102 self.text = text.to_string();
103 }
104
105 fn set_value(&mut self, value: f32) {
106 self.curr = f32::max(0.0, f32::min(1.0, value));
107 }
108
109 fn clear(&mut self) {
110 if self.hidden {
111 return;
112 }
113
114 // Fill all last text to space and return the cursor
115 let spaces = " ".repeat(self.text.len());
116 let backspaces = "\x08".repeat(self.text.len());
117 print!("{}{}{}", backspaces, spaces, backspaces);
118 self.text = String::new();
119 }
120}
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs
index a3464a5a3..958c70fe5 100644
--- a/crates/ra_lsp_server/src/lib.rs
+++ b/crates/ra_lsp_server/src/lib.rs
@@ -7,6 +7,8 @@
7//! state, and `main_loop` module defines the rules for modifying it. 7//! state, and `main_loop` module defines the rules for modifying it.
8#![recursion_limit = "512"] 8#![recursion_limit = "512"]
9 9
10pub mod cli;
11
10#[allow(unused)] 12#[allow(unused)]
11macro_rules! println { 13macro_rules! println {
12 ($($tt:tt)*) => { 14 ($($tt:tt)*) => {
@@ -21,6 +23,7 @@ macro_rules! print {
21 }; 23 };
22} 24}
23 25
26mod vfs_glob;
24mod caps; 27mod caps;
25mod cargo_target_spec; 28mod cargo_target_spec;
26mod conv; 29mod conv;
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index ed2eaabd4..a549e5ff1 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -1,14 +1,39 @@
1//! `ra_lsp_server` binary 1//! `ra_lsp_server` binary
2mod args;
2 3
3use lsp_server::Connection; 4use lsp_server::Connection;
4use ra_lsp_server::{from_json, show_message, Result, ServerConfig}; 5use ra_lsp_server::{cli, from_json, show_message, Result, ServerConfig};
5use ra_prof; 6use ra_prof;
6 7
8use crate::args::HelpPrinted;
9
7fn main() -> Result<()> { 10fn main() -> Result<()> {
8 setup_logging()?; 11 setup_logging()?;
9 match Args::parse()? { 12 let args = match args::Args::parse()? {
10 Args::Version => println!("rust-analyzer {}", env!("REV")), 13 Ok(it) => it,
11 Args::Run => run_server()?, 14 Err(HelpPrinted) => return Ok(()),
15 };
16 match args.command {
17 args::Command::Parse { no_dump } => cli::parse(no_dump)?,
18 args::Command::Symbols => cli::symbols()?,
19 args::Command::Highlight { rainbow } => cli::highlight(rainbow)?,
20 args::Command::Stats { randomize, memory_usage, only, with_deps, path } => {
21 cli::analysis_stats(
22 args.verbosity,
23 memory_usage,
24 path.as_ref(),
25 only.as_ref().map(String::as_ref),
26 with_deps,
27 randomize,
28 )?
29 }
30
31 args::Command::Bench { path, what } => {
32 cli::analysis_bench(args.verbosity, path.as_ref(), what)?
33 }
34
35 args::Command::RunServer => run_server()?,
36 args::Command::Version => println!("rust-analyzer {}", env!("REV")),
12 } 37 }
13 Ok(()) 38 Ok(())
14} 39}
@@ -20,19 +45,6 @@ fn setup_logging() -> Result<()> {
20 Ok(()) 45 Ok(())
21} 46}
22 47
23enum Args {
24 Version,
25 Run,
26}
27
28impl Args {
29 fn parse() -> Result<Args> {
30 let res =
31 if std::env::args().any(|it| it == "--version") { Args::Version } else { Args::Run };
32 Ok(res)
33 }
34}
35
36fn run_server() -> Result<()> { 48fn run_server() -> Result<()> {
37 log::info!("lifecycle: server started"); 49 log::info!("lifecycle: server started");
38 50
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 7ae2e1e6f..944074118 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -135,7 +135,7 @@ pub fn main_loop(
135 let globs = config 135 let globs = config
136 .exclude_globs 136 .exclude_globs
137 .iter() 137 .iter()
138 .map(|glob| ra_vfs_glob::Glob::new(glob)) 138 .map(|glob| crate::vfs_glob::Glob::new(glob))
139 .collect::<std::result::Result<Vec<_>, _>>()?; 139 .collect::<std::result::Result<Vec<_>, _>>()?;
140 140
141 if config.use_client_watching { 141 if config.use_client_watching {
diff --git a/crates/ra_lsp_server/src/vfs_glob.rs b/crates/ra_lsp_server/src/vfs_glob.rs
new file mode 100644
index 000000000..12401d75a
--- /dev/null
+++ b/crates/ra_lsp_server/src/vfs_glob.rs
@@ -0,0 +1,94 @@
1//! `ra_vfs_glob` crate implements exclusion rules for vfs.
2//!
3//! By default, we include only `.rs` files, and skip some know offenders like
4//! `/target` or `/node_modules` altogether.
5//!
6//! It's also possible to add custom exclusion globs.
7
8use globset::{GlobSet, GlobSetBuilder};
9use ra_vfs::{Filter, RelativePath};
10
11pub use globset::{Glob, GlobBuilder};
12
13const ALWAYS_IGNORED: &[&str] = &["target/**", "**/node_modules/**", "**/.git/**"];
14const IGNORED_FOR_NON_MEMBERS: &[&str] = &["examples/**", "tests/**", "benches/**"];
15
16pub struct RustPackageFilterBuilder {
17 is_member: bool,
18 exclude: GlobSetBuilder,
19}
20
21impl Default for RustPackageFilterBuilder {
22 fn default() -> RustPackageFilterBuilder {
23 RustPackageFilterBuilder { is_member: false, exclude: GlobSetBuilder::new() }
24 }
25}
26
27impl RustPackageFilterBuilder {
28 pub fn set_member(mut self, is_member: bool) -> RustPackageFilterBuilder {
29 self.is_member = is_member;
30 self
31 }
32 pub fn exclude(mut self, glob: Glob) -> RustPackageFilterBuilder {
33 self.exclude.add(glob);
34 self
35 }
36 pub fn into_vfs_filter(self) -> Box<dyn Filter> {
37 let RustPackageFilterBuilder { is_member, mut exclude } = self;
38 for &glob in ALWAYS_IGNORED {
39 exclude.add(Glob::new(glob).unwrap());
40 }
41 if !is_member {
42 for &glob in IGNORED_FOR_NON_MEMBERS {
43 exclude.add(Glob::new(glob).unwrap());
44 }
45 }
46 Box::new(RustPackageFilter { exclude: exclude.build().unwrap() })
47 }
48}
49
50struct RustPackageFilter {
51 exclude: GlobSet,
52}
53
54impl Filter for RustPackageFilter {
55 fn include_dir(&self, dir_path: &RelativePath) -> bool {
56 !self.exclude.is_match(dir_path.as_str())
57 }
58
59 fn include_file(&self, file_path: &RelativePath) -> bool {
60 file_path.extension() == Some("rs")
61 }
62}
63
64#[test]
65fn test_globs() {
66 let filter = RustPackageFilterBuilder::default().set_member(true).into_vfs_filter();
67
68 assert!(filter.include_dir(RelativePath::new("src/tests")));
69 assert!(filter.include_dir(RelativePath::new("src/target")));
70 assert!(filter.include_dir(RelativePath::new("tests")));
71 assert!(filter.include_dir(RelativePath::new("benches")));
72
73 assert!(!filter.include_dir(RelativePath::new("target")));
74 assert!(!filter.include_dir(RelativePath::new("src/foo/.git")));
75 assert!(!filter.include_dir(RelativePath::new("foo/node_modules")));
76
77 let filter = RustPackageFilterBuilder::default().set_member(false).into_vfs_filter();
78
79 assert!(filter.include_dir(RelativePath::new("src/tests")));
80 assert!(filter.include_dir(RelativePath::new("src/target")));
81
82 assert!(!filter.include_dir(RelativePath::new("target")));
83 assert!(!filter.include_dir(RelativePath::new("src/foo/.git")));
84 assert!(!filter.include_dir(RelativePath::new("foo/node_modules")));
85 assert!(!filter.include_dir(RelativePath::new("tests")));
86 assert!(!filter.include_dir(RelativePath::new("benches")));
87
88 let filter = RustPackageFilterBuilder::default()
89 .set_member(true)
90 .exclude(Glob::new("src/llvm-project/**").unwrap())
91 .into_vfs_filter();
92
93 assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang")));
94}
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index d993c5fc4..71c95d4af 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -19,12 +19,12 @@ use ra_ide::{
19}; 19};
20use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace}; 20use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
21use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; 21use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
22use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
23use relative_path::RelativePathBuf; 22use relative_path::RelativePathBuf;
24 23
25use crate::{ 24use crate::{
26 diagnostics::{CheckFixes, DiagnosticCollection}, 25 diagnostics::{CheckFixes, DiagnosticCollection},
27 main_loop::pending_requests::{CompletedRequest, LatestRequests}, 26 main_loop::pending_requests::{CompletedRequest, LatestRequests},
27 vfs_glob::{Glob, RustPackageFilterBuilder},
28 LspError, Result, 28 LspError, Result,
29}; 29};
30 30