From 659b0e73cfd9ef7755d032f90622a08576f1d86d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 17 Feb 2020 19:03:03 +0100 Subject: Merge cli and ra_lsp_server --- Cargo.lock | 32 +-- crates/ra_cli/Cargo.toml | 31 --- crates/ra_cli/src/analysis_bench.rs | 127 --------- crates/ra_cli/src/analysis_stats.rs | 259 ------------------ crates/ra_cli/src/load_cargo.rs | 152 ----------- crates/ra_cli/src/main.rs | 336 ------------------------ crates/ra_cli/src/progress_report.rs | 120 --------- crates/ra_lsp_server/Cargo.toml | 11 + crates/ra_lsp_server/src/args.rs | 237 +++++++++++++++++ crates/ra_lsp_server/src/cli.rs | 75 ++++++ crates/ra_lsp_server/src/cli/analysis_bench.rs | 158 +++++++++++ crates/ra_lsp_server/src/cli/analysis_stats.rs | 259 ++++++++++++++++++ crates/ra_lsp_server/src/cli/load_cargo.rs | 152 +++++++++++ crates/ra_lsp_server/src/cli/progress_report.rs | 120 +++++++++ crates/ra_lsp_server/src/lib.rs | 2 + crates/ra_lsp_server/src/main.rs | 46 ++-- 16 files changed, 1051 insertions(+), 1066 deletions(-) delete mode 100644 crates/ra_cli/Cargo.toml delete mode 100644 crates/ra_cli/src/analysis_bench.rs delete mode 100644 crates/ra_cli/src/analysis_stats.rs delete mode 100644 crates/ra_cli/src/load_cargo.rs delete mode 100644 crates/ra_cli/src/main.rs delete mode 100644 crates/ra_cli/src/progress_report.rs create mode 100644 crates/ra_lsp_server/src/args.rs create mode 100644 crates/ra_lsp_server/src/cli.rs create mode 100644 crates/ra_lsp_server/src/cli/analysis_bench.rs create mode 100644 crates/ra_lsp_server/src/cli/analysis_stats.rs create mode 100644 crates/ra_lsp_server/src/cli/load_cargo.rs create mode 100644 crates/ra_lsp_server/src/cli/progress_report.rs diff --git a/Cargo.lock b/Cargo.lock index 82f910c1b..ccdcfbee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -941,30 +941,6 @@ dependencies = [ "rustc-hash", ] -[[package]] -name = "ra_cli" -version = "0.1.0" -dependencies = [ - "anyhow", - "crossbeam-channel", - "env_logger", - "itertools", - "log", - "pico-args", - "ra_db", - "ra_hir", - "ra_hir_def", - "ra_hir_ty", - "ra_ide", - "ra_prof", - "ra_project_model", - "ra_syntax", - "ra_vfs", - "ra_vfs_glob", - "rand", - "rustc-hash", -] - [[package]] name = "ra_db" version = "0.1.0" @@ -1122,15 +1098,22 @@ dependencies = [ name = "ra_lsp_server" version = "0.1.0" dependencies = [ + "anyhow", "crossbeam-channel", "either", "env_logger", + "itertools", "jod-thread", "log", "lsp-server", "lsp-types", "parking_lot", + "pico-args", "ra_cargo_watch", + "ra_db", + "ra_hir", + "ra_hir_def", + "ra_hir_ty", "ra_ide", "ra_prof", "ra_project_model", @@ -1138,6 +1121,7 @@ dependencies = [ "ra_text_edit", "ra_vfs", "ra_vfs_glob", + "rand", "relative-path", "rustc-hash", "serde", diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml deleted file mode 100644 index ce88a76b1..000000000 --- a/crates/ra_cli/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -edition = "2018" -name = "ra_cli" -version = "0.1.0" -authors = ["rust-analyzer developers"] -publish = false - -[dependencies] -crossbeam-channel = "0.4.0" -env_logger = { version = "0.7.1", default-features = false } -itertools = "0.8.0" -log = "0.4.5" -pico-args = "0.3.0" -rand = { version = "0.7.0", features = ["small_rng"] } -rustc-hash = "1.0" -anyhow = "1.0" - -hir = { path = "../ra_hir", package = "ra_hir" } -hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } -hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } -ra_db = { path = "../ra_db" } -ra_ide = { path = "../ra_ide" } -ra_project_model = { path = "../ra_project_model" } -ra_syntax = { path = "../ra_syntax" } -ra_vfs = "0.5.0" -ra_vfs_glob = { path = "../ra_vfs_glob" } - -[dependencies.ra_prof] -path = "../ra_prof" -# features = [ "cpu_profiler" ] -# features = [ "jemalloc" ] diff --git a/crates/ra_cli/src/analysis_bench.rs b/crates/ra_cli/src/analysis_bench.rs deleted file mode 100644 index 91fc55fe2..000000000 --- a/crates/ra_cli/src/analysis_bench.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! FIXME: write short doc here - -use std::{path::Path, sync::Arc, time::Instant}; - -use anyhow::format_err; -use ra_db::{ - salsa::{Database, Durability}, - FileId, SourceDatabaseExt, -}; -use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol}; - -use crate::{load_cargo::load_cargo, BenchWhat, Result, Verbosity}; - -pub(crate) fn run(verbosity: Verbosity, path: &Path, what: BenchWhat) -> Result<()> { - ra_prof::init(); - - let start = Instant::now(); - eprint!("loading: "); - let (mut host, roots) = load_cargo(path)?; - let db = host.raw_database(); - eprintln!("{:?}\n", start.elapsed()); - - let file_id = { - let path = match &what { - BenchWhat::Highlight { path } => path, - BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path, - }; - let path = std::env::current_dir()?.join(path).canonicalize()?; - roots - .iter() - .find_map(|(source_root_id, project_root)| { - if project_root.is_member() { - for file_id in db.source_root(*source_root_id).walk() { - let rel_path = db.file_relative_path(file_id); - let abs_path = rel_path.to_path(project_root.path()); - if abs_path == path { - return Some(file_id); - } - } - } - None - }) - .ok_or_else(|| format_err!("Can't find {}", path.display()))? - }; - - match &what { - BenchWhat::Highlight { .. } => { - let res = do_work(&mut host, file_id, |analysis| { - analysis.diagnostics(file_id).unwrap(); - analysis.highlight_as_html(file_id, false).unwrap() - }); - if verbosity.is_verbose() { - println!("\n{}", res); - } - } - BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => { - let is_completion = match what { - BenchWhat::Complete(..) => true, - _ => false, - }; - - let offset = host - .analysis() - .file_line_index(file_id)? - .offset(LineCol { line: pos.line - 1, col_utf16: pos.column }); - let file_postion = FilePosition { file_id, offset }; - - if is_completion { - let res = - do_work(&mut host, file_id, |analysis| analysis.completions(file_postion)); - if verbosity.is_verbose() { - println!("\n{:#?}", res); - } - } else { - let res = - do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_postion)); - if verbosity.is_verbose() { - println!("\n{:#?}", res); - } - } - } - } - Ok(()) -} - -fn do_work T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T { - { - let start = Instant::now(); - eprint!("from scratch: "); - work(&host.analysis()); - eprintln!("{:?}", start.elapsed()); - } - { - let start = Instant::now(); - eprint!("no change: "); - work(&host.analysis()); - eprintln!("{:?}", start.elapsed()); - } - { - let start = Instant::now(); - eprint!("trivial change: "); - host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW); - work(&host.analysis()); - eprintln!("{:?}", start.elapsed()); - } - { - let start = Instant::now(); - eprint!("comment change: "); - { - let mut text = host.analysis().file_text(file_id).unwrap().to_string(); - text.push_str("\n/* Hello world */\n"); - let mut change = AnalysisChange::new(); - change.change_file(file_id, Arc::new(text)); - host.apply_change(change); - } - work(&host.analysis()); - eprintln!("{:?}", start.elapsed()); - } - { - let start = Instant::now(); - eprint!("const change: "); - host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH); - let res = work(&host.analysis()); - eprintln!("{:?}", start.elapsed()); - res - } -} diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs deleted file mode 100644 index d40f04391..000000000 --- a/crates/ra_cli/src/analysis_stats.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! FIXME: write short doc here - -use std::{collections::HashSet, fmt::Write, path::Path, time::Instant}; - -use hir::{ - db::{DefDatabase, HirDatabase}, - AssocItem, Crate, HasSource, HirDisplay, ModuleDef, -}; -use hir_def::FunctionId; -use hir_ty::{Ty, TypeWalk}; -use itertools::Itertools; -use ra_db::SourceDatabaseExt; -use ra_syntax::AstNode; -use rand::{seq::SliceRandom, thread_rng}; - -use crate::{load_cargo::load_cargo, progress_report::ProgressReport, Result, Verbosity}; - -pub fn run( - verbosity: Verbosity, - memory_usage: bool, - path: &Path, - only: Option<&str>, - with_deps: bool, - randomize: bool, -) -> Result<()> { - let db_load_time = Instant::now(); - let (mut host, roots) = load_cargo(path)?; - let db = host.raw_database(); - println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); - let analysis_time = Instant::now(); - let mut num_crates = 0; - let mut visited_modules = HashSet::new(); - let mut visit_queue = Vec::new(); - - let members = - roots - .into_iter() - .filter_map(|(source_root_id, project_root)| { - if with_deps || project_root.is_member() { - Some(source_root_id) - } else { - None - } - }) - .collect::>(); - - let mut krates = Crate::all(db); - if randomize { - krates.shuffle(&mut thread_rng()); - } - for krate in krates { - let module = krate.root_module(db).expect("crate without root module"); - let file_id = module.definition_source(db).file_id; - if members.contains(&db.file_source_root(file_id.original_file(db))) { - num_crates += 1; - visit_queue.push(module); - } - } - - if randomize { - visit_queue.shuffle(&mut thread_rng()); - } - - println!("Crates in this dir: {}", num_crates); - let mut num_decls = 0; - let mut funcs = Vec::new(); - while let Some(module) = visit_queue.pop() { - if visited_modules.insert(module) { - visit_queue.extend(module.children(db)); - - for decl in module.declarations(db) { - num_decls += 1; - if let ModuleDef::Function(f) = decl { - funcs.push(f); - } - } - - for impl_block in module.impl_blocks(db) { - for item in impl_block.items(db) { - num_decls += 1; - if let AssocItem::Function(f) = item { - funcs.push(f); - } - } - } - } - } - println!("Total modules found: {}", visited_modules.len()); - println!("Total declarations: {}", num_decls); - println!("Total functions: {}", funcs.len()); - println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage()); - - if randomize { - funcs.shuffle(&mut thread_rng()); - } - - let inference_time = Instant::now(); - let mut bar = match verbosity { - Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(), - _ => ProgressReport::new(funcs.len() as u64), - }; - - bar.tick(); - let mut num_exprs = 0; - let mut num_exprs_unknown = 0; - let mut num_exprs_partially_unknown = 0; - let mut num_type_mismatches = 0; - for f in funcs { - let name = f.name(db); - let full_name = f - .module(db) - .path_to_root(db) - .into_iter() - .rev() - .filter_map(|it| it.name(db)) - .chain(Some(f.name(db))) - .join("::"); - if let Some(only_name) = only { - if name.to_string() != only_name && full_name != only_name { - continue; - } - } - let mut msg = format!("processing: {}", full_name); - if verbosity.is_verbose() { - let src = f.source(db); - let original_file = src.file_id.original_file(db); - let path = db.file_relative_path(original_file); - let syntax_range = src.value.syntax().text_range(); - write!(msg, " ({:?} {})", path, syntax_range).unwrap(); - } - if verbosity.is_spammy() { - bar.println(format!("{}", msg)); - } - bar.set_message(&msg); - let f_id = FunctionId::from(f); - let body = db.body(f_id.into()); - let inference_result = db.infer(f_id.into()); - let (previous_exprs, previous_unknown, previous_partially_unknown) = - (num_exprs, num_exprs_unknown, num_exprs_partially_unknown); - for (expr_id, _) in body.exprs.iter() { - let ty = &inference_result[expr_id]; - num_exprs += 1; - if let Ty::Unknown = ty { - num_exprs_unknown += 1; - } else { - let mut is_partially_unknown = false; - ty.walk(&mut |ty| { - if let Ty::Unknown = ty { - is_partially_unknown = true; - } - }); - if is_partially_unknown { - num_exprs_partially_unknown += 1; - } - } - if only.is_some() && verbosity.is_spammy() { - // in super-verbose mode for just one function, we print every single expression - let (_, sm) = db.body_with_source_map(f_id.into()); - let src = sm.expr_syntax(expr_id); - if let Some(src) = src { - let original_file = src.file_id.original_file(db); - let line_index = host.analysis().file_line_index(original_file).unwrap(); - let text_range = src.value.either( - |it| it.syntax_node_ptr().range(), - |it| it.syntax_node_ptr().range(), - ); - let (start, end) = ( - line_index.line_col(text_range.start()), - line_index.line_col(text_range.end()), - ); - bar.println(format!( - "{}:{}-{}:{}: {}", - start.line + 1, - start.col_utf16, - end.line + 1, - end.col_utf16, - ty.display(db) - )); - } else { - bar.println(format!("unknown location: {}", ty.display(db))); - } - } - if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { - num_type_mismatches += 1; - if verbosity.is_verbose() { - let (_, sm) = db.body_with_source_map(f_id.into()); - let src = sm.expr_syntax(expr_id); - if let Some(src) = src { - // FIXME: it might be nice to have a function (on Analysis?) that goes from Source -> (LineCol, LineCol) directly - let original_file = src.file_id.original_file(db); - let path = db.file_relative_path(original_file); - let line_index = host.analysis().file_line_index(original_file).unwrap(); - let text_range = src.value.either( - |it| it.syntax_node_ptr().range(), - |it| it.syntax_node_ptr().range(), - ); - let (start, end) = ( - line_index.line_col(text_range.start()), - line_index.line_col(text_range.end()), - ); - bar.println(format!( - "{} {}:{}-{}:{}: Expected {}, got {}", - path, - start.line + 1, - start.col_utf16, - end.line + 1, - end.col_utf16, - mismatch.expected.display(db), - mismatch.actual.display(db) - )); - } else { - bar.println(format!( - "{}: Expected {}, got {}", - name, - mismatch.expected.display(db), - mismatch.actual.display(db) - )); - } - } - } - } - if verbosity.is_spammy() { - bar.println(format!( - "In {}: {} exprs, {} unknown, {} partial", - full_name, - num_exprs - previous_exprs, - num_exprs_unknown - previous_unknown, - num_exprs_partially_unknown - previous_partially_unknown - )); - } - bar.inc(1); - } - bar.finish_and_clear(); - println!("Total expressions: {}", num_exprs); - println!( - "Expressions of unknown type: {} ({}%)", - num_exprs_unknown, - if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 } - ); - println!( - "Expressions of partially unknown type: {} ({}%)", - num_exprs_partially_unknown, - if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 } - ); - println!("Type mismatches: {}", num_type_mismatches); - println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage()); - println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage()); - - if memory_usage { - for (name, bytes) in host.per_query_memory_usage() { - println!("{:>8} {}", bytes, name) - } - let before = ra_prof::memory_usage(); - drop(host); - println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated) - } - - Ok(()) -} diff --git a/crates/ra_cli/src/load_cargo.rs b/crates/ra_cli/src/load_cargo.rs deleted file mode 100644 index b9a4e6aba..000000000 --- a/crates/ra_cli/src/load_cargo.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! FIXME: write short doc here - -use std::{collections::HashSet, path::Path}; - -use crossbeam_channel::{unbounded, Receiver}; -use ra_db::{CrateGraph, FileId, SourceRootId}; -use ra_ide::{AnalysisChange, AnalysisHost, FeatureFlags}; -use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace}; -use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; -use ra_vfs_glob::RustPackageFilterBuilder; -use rustc_hash::FxHashMap; - -use anyhow::Result; - -fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId { - FileId(f.0) -} -fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId { - SourceRootId(r.0) -} - -pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap)> { - let root = std::env::current_dir()?.join(root); - let ws = ProjectWorkspace::discover(root.as_ref(), &Default::default())?; - let project_roots = ws.to_roots(); - let (sender, receiver) = unbounded(); - let sender = Box::new(move |t| sender.send(t).unwrap()); - let (mut vfs, roots) = Vfs::new( - project_roots - .iter() - .map(|pkg_root| { - RootEntry::new( - pkg_root.path().clone(), - RustPackageFilterBuilder::default() - .set_member(pkg_root.is_member()) - .into_vfs_filter(), - ) - }) - .collect(), - sender, - Watch(false), - ); - - // FIXME: cfg options? - let default_cfg_options = { - let mut opts = get_rustc_cfg_options(); - opts.insert_atom("test".into()); - opts.insert_atom("debug_assertion".into()); - opts - }; - - let (crate_graph, _crate_names) = - ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| { - let vfs_file = vfs.load(path); - log::debug!("vfs file {:?} -> {:?}", path, vfs_file); - vfs_file.map(vfs_file_to_id) - }); - log::debug!("crate graph: {:?}", crate_graph); - - let source_roots = roots - .iter() - .map(|&vfs_root| { - let source_root_id = vfs_root_to_id(vfs_root); - let project_root = project_roots - .iter() - .find(|it| it.path() == &vfs.root2path(vfs_root)) - .unwrap() - .clone(); - (source_root_id, project_root) - }) - .collect::>(); - let host = load(&source_roots, crate_graph, &mut vfs, receiver); - Ok((host, source_roots)) -} - -pub fn load( - source_roots: &FxHashMap, - crate_graph: CrateGraph, - vfs: &mut Vfs, - receiver: Receiver, -) -> AnalysisHost { - let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::().ok()); - let mut host = AnalysisHost::new(lru_cap, FeatureFlags::default()); - let mut analysis_change = AnalysisChange::new(); - analysis_change.set_crate_graph(crate_graph); - - // wait until Vfs has loaded all roots - let mut roots_loaded = HashSet::new(); - for task in receiver { - vfs.handle_task(task); - let mut done = false; - for change in vfs.commit_changes() { - match change { - VfsChange::AddRoot { root, files } => { - let source_root_id = vfs_root_to_id(root); - let is_local = source_roots[&source_root_id].is_member(); - log::debug!( - "loaded source root {:?} with path {:?}", - source_root_id, - vfs.root2path(root) - ); - analysis_change.add_root(source_root_id, is_local); - analysis_change.set_debug_root_path( - source_root_id, - source_roots[&source_root_id].path().display().to_string(), - ); - - let mut file_map = FxHashMap::default(); - for (vfs_file, path, text) in files { - let file_id = vfs_file_to_id(vfs_file); - analysis_change.add_file(source_root_id, file_id, path.clone(), text); - file_map.insert(path, file_id); - } - roots_loaded.insert(source_root_id); - if roots_loaded.len() == vfs.n_roots() { - done = true; - } - } - VfsChange::AddFile { root, file, path, text } => { - let source_root_id = vfs_root_to_id(root); - let file_id = vfs_file_to_id(file); - analysis_change.add_file(source_root_id, file_id, path, text); - } - VfsChange::RemoveFile { .. } | VfsChange::ChangeFile { .. } => { - // We just need the first scan, so just ignore these - } - } - } - if done { - break; - } - } - - host.apply_change(analysis_change); - host -} - -#[cfg(test)] -mod tests { - use super::*; - - use hir::Crate; - - #[test] - fn test_loading_rust_analyzer() { - let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); - let (host, _roots) = load_cargo(path).unwrap(); - let n_crates = Crate::all(host.raw_database()).len(); - // RA has quite a few crates, but the exact count doesn't matter - assert!(n_crates > 20); - } -} diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs deleted file mode 100644 index 4cf062f47..000000000 --- a/crates/ra_cli/src/main.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! FIXME: write short doc here - -mod load_cargo; -mod analysis_stats; -mod analysis_bench; -mod progress_report; - -use std::{fmt::Write, io::Read, path::PathBuf, str::FromStr}; - -use pico_args::Arguments; -use ra_ide::{file_structure, Analysis}; -use ra_prof::profile; -use ra_syntax::{AstNode, SourceFile}; - -use anyhow::{bail, format_err, Result}; - -fn main() -> Result<()> { - env_logger::try_init()?; - - let command = match Command::from_env_args()? { - Ok(it) => it, - Err(HelpPrinted) => return Ok(()), - }; - match command { - Command::Parse { no_dump } => { - let _p = profile("parsing"); - let file = file()?; - if !no_dump { - println!("{:#?}", file.syntax()); - } - std::mem::forget(file); - } - Command::Symbols => { - let file = file()?; - for s in file_structure(&file) { - println!("{:?}", s); - } - } - Command::Highlight { rainbow } => { - let (analysis, file_id) = Analysis::from_single_file(read_stdin()?); - let html = analysis.highlight_as_html(file_id, rainbow).unwrap(); - println!("{}", html); - } - Command::Stats { verbosity, randomize, memory_usage, only, with_deps, path } => { - analysis_stats::run( - verbosity, - memory_usage, - path.as_ref(), - only.as_ref().map(String::as_ref), - with_deps, - randomize, - )?; - } - Command::Bench { verbosity, path, what } => { - analysis_bench::run(verbosity, path.as_ref(), what)?; - } - } - - Ok(()) -} - -enum Command { - Parse { - no_dump: bool, - }, - Symbols, - Highlight { - rainbow: bool, - }, - Stats { - verbosity: Verbosity, - randomize: bool, - memory_usage: bool, - only: Option, - with_deps: bool, - path: PathBuf, - }, - Bench { - verbosity: Verbosity, - path: PathBuf, - what: BenchWhat, - }, -} - -#[derive(Clone, Copy)] -pub enum Verbosity { - Spammy, - Verbose, - Normal, - Quiet, -} - -impl Verbosity { - fn is_verbose(self) -> bool { - match self { - Verbosity::Verbose | Verbosity::Spammy => true, - _ => false, - } - } - fn is_spammy(self) -> bool { - match self { - Verbosity::Spammy => true, - _ => false, - } - } -} - -enum BenchWhat { - Highlight { path: PathBuf }, - Complete(Position), - GotoDef(Position), -} - -pub(crate) struct Position { - path: PathBuf, - line: u32, - column: u32, -} - -impl FromStr for Position { - type Err = anyhow::Error; - fn from_str(s: &str) -> Result { - let (path_line, column) = rsplit_at_char(s, ':')?; - let (path, line) = rsplit_at_char(path_line, ':')?; - Ok(Position { path: path.into(), line: line.parse()?, column: column.parse()? }) - } -} - -fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> { - let idx = s.rfind(c).ok_or_else(|| format_err!("no `{}` in {}", c, s))?; - Ok((&s[..idx], &s[idx + 1..])) -} - -struct HelpPrinted; - -impl Command { - fn from_env_args() -> Result> { - let mut matches = Arguments::from_env(); - let subcommand = matches.subcommand()?.unwrap_or_default(); - - let verbosity = match ( - matches.contains(["-vv", "--spammy"]), - matches.contains(["-v", "--verbose"]), - matches.contains(["-q", "--quiet"]), - ) { - (true, _, true) => bail!("Invalid flags: -q conflicts with -vv"), - (true, _, false) => Verbosity::Spammy, - (false, false, false) => Verbosity::Normal, - (false, false, true) => Verbosity::Quiet, - (false, true, false) => Verbosity::Verbose, - (false, true, true) => bail!("Invalid flags: -q conflicts with -v"), - }; - - let command = match subcommand.as_str() { - "parse" => { - if matches.contains(["-h", "--help"]) { - eprintln!( - "\ -ra-cli-parse - -USAGE: - ra_cli parse [FLAGS] - -FLAGS: - -h, --help Prints help inforamtion - --no-dump" - ); - return Ok(Err(HelpPrinted)); - } - - let no_dump = matches.contains("--no-dump"); - matches.finish().or_else(handle_extra_flags)?; - Command::Parse { no_dump } - } - "symbols" => { - if matches.contains(["-h", "--help"]) { - eprintln!( - "\ -ra-cli-symbols - -USAGE: - ra_cli highlight [FLAGS] - -FLAGS: - -h, --help Prints help inforamtion" - ); - return Ok(Err(HelpPrinted)); - } - - matches.finish().or_else(handle_extra_flags)?; - - Command::Symbols - } - "highlight" => { - if matches.contains(["-h", "--help"]) { - eprintln!( - "\ -ra-cli-highlight - -USAGE: - ra_cli highlight [FLAGS] - -FLAGS: - -h, --help Prints help information - -r, --rainbow" - ); - return Ok(Err(HelpPrinted)); - } - - let rainbow = matches.contains(["-r", "--rainbow"]); - matches.finish().or_else(handle_extra_flags)?; - Command::Highlight { rainbow } - } - "analysis-stats" => { - if matches.contains(["-h", "--help"]) { - eprintln!( - "\ -ra-cli-analysis-stats - -USAGE: - ra_cli analysis-stats [FLAGS] [OPTIONS] [PATH] - -FLAGS: - -h, --help Prints help information - --memory-usage - -v, --verbose - -q, --quiet - -OPTIONS: - -o - -ARGS: - " - ); - return Ok(Err(HelpPrinted)); - } - - let randomize = matches.contains("--randomize"); - let memory_usage = matches.contains("--memory-usage"); - let only: Option = matches.opt_value_from_str(["-o", "--only"])?; - let with_deps: bool = matches.contains("--with-deps"); - let path = { - let mut trailing = matches.free()?; - if trailing.len() != 1 { - bail!("Invalid flags"); - } - trailing.pop().unwrap().into() - }; - - Command::Stats { verbosity, randomize, memory_usage, only, with_deps, path } - } - "analysis-bench" => { - if matches.contains(["-h", "--help"]) { - eprintln!( - "\ -ra_cli-analysis-bench - -USAGE: - ra_cli analysis-bench [FLAGS] [OPTIONS] [PATH] - -FLAGS: - -h, --help Prints help information - -v, --verbose - -OPTIONS: - --complete Compute completions at this location - --highlight Hightlight this file - -ARGS: - Project to analyse" - ); - return Ok(Err(HelpPrinted)); - } - - let path: PathBuf = matches.opt_value_from_str("--path")?.unwrap_or_default(); - let highlight_path: Option = matches.opt_value_from_str("--highlight")?; - let complete_path: Option = matches.opt_value_from_str("--complete")?; - let goto_def_path: Option = matches.opt_value_from_str("--goto-def")?; - let what = match (highlight_path, complete_path, goto_def_path) { - (Some(path), None, None) => BenchWhat::Highlight { path: path.into() }, - (None, Some(position), None) => BenchWhat::Complete(position), - (None, None, Some(position)) => BenchWhat::GotoDef(position), - _ => panic!( - "exactly one of `--highlight`, `--complete` or `--goto-def` must be set" - ), - }; - Command::Bench { verbosity, path, what } - } - _ => { - eprintln!( - "\ -ra-cli - -USAGE: - ra_cli - -FLAGS: - -h, --help Prints help information - -SUBCOMMANDS: - analysis-bench - analysis-stats - highlight - parse - symbols" - ); - return Ok(Err(HelpPrinted)); - } - }; - Ok(Ok(command)) - } -} - -fn handle_extra_flags(e: pico_args::Error) -> Result<()> { - if let pico_args::Error::UnusedArgsLeft(flags) = e { - let mut invalid_flags = String::new(); - for flag in flags { - write!(&mut invalid_flags, "{}, ", flag)?; - } - let (invalid_flags, _) = invalid_flags.split_at(invalid_flags.len() - 2); - bail!("Invalid flags: {}", invalid_flags); - } else { - bail!(e); - } -} - -fn file() -> Result { - let text = read_stdin()?; - Ok(SourceFile::parse(&text).tree()) -} - -fn read_stdin() -> Result { - let mut buff = String::new(); - std::io::stdin().read_to_string(&mut buff)?; - Ok(buff) -} diff --git a/crates/ra_cli/src/progress_report.rs b/crates/ra_cli/src/progress_report.rs deleted file mode 100644 index 31867a1e9..000000000 --- a/crates/ra_cli/src/progress_report.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! A simple progress bar -//! -//! A single thread non-optimized progress bar -use std::io::Write; - -/// A Simple ASCII Progress Bar -pub struct ProgressReport { - curr: f32, - text: String, - hidden: bool, - - len: u64, - pos: u64, - msg: String, -} - -impl ProgressReport { - pub fn new(len: u64) -> ProgressReport { - ProgressReport { - curr: 0.0, - text: String::new(), - hidden: false, - len, - pos: 0, - msg: String::new(), - } - } - - pub fn hidden() -> ProgressReport { - ProgressReport { - curr: 0.0, - text: String::new(), - hidden: true, - len: 0, - pos: 0, - msg: String::new(), - } - } - - pub fn set_message(&mut self, msg: &str) { - self.msg = msg.to_string(); - self.tick(); - } - - pub fn println>(&mut self, msg: I) { - self.clear(); - println!("{}", msg.into()); - self.tick(); - } - - pub fn inc(&mut self, delta: u64) { - self.pos += delta; - if self.len == 0 { - self.set_value(0.0) - } else { - self.set_value((self.pos as f32) / (self.len as f32)) - } - self.tick(); - } - - pub fn finish_and_clear(&mut self) { - self.clear(); - } - - pub fn tick(&mut self) { - if self.hidden { - return; - } - let percent = (self.curr * 100.0) as u32; - let text = format!("{}/{} {:3>}% {}", self.pos, self.len, percent, self.msg); - self.update_text(&text); - } - - fn update_text(&mut self, text: &str) { - // Get length of common portion - let mut common_prefix_length = 0; - let common_length = usize::min(self.text.len(), text.len()); - - while common_prefix_length < common_length - && text.chars().nth(common_prefix_length).unwrap() - == self.text.chars().nth(common_prefix_length).unwrap() - { - common_prefix_length += 1; - } - - // Backtrack to the first differing character - let mut output = String::new(); - output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); - // Output new suffix - output += &text[common_prefix_length..text.len()]; - - // If the new text is shorter than the old one: delete overlapping characters - if let Some(overlap_count) = self.text.len().checked_sub(text.len()) { - if overlap_count > 0 { - output += &" ".repeat(overlap_count); - output += &"\x08".repeat(overlap_count); - } - } - - let _ = std::io::stdout().write(output.as_bytes()); - let _ = std::io::stdout().flush(); - self.text = text.to_string(); - } - - fn set_value(&mut self, value: f32) { - self.curr = f32::max(0.0, f32::min(1.0, value)); - } - - fn clear(&mut self) { - if self.hidden { - return; - } - - // Fill all last text to space and return the cursor - let spaces = " ".repeat(self.text.len()); - let backspaces = "\x08".repeat(self.text.len()); - print!("{}{}{}", backspaces, spaces, backspaces); - self.text = String::new(); - } -} diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 0066929c0..be7982b2a 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -8,13 +8,17 @@ authors = ["rust-analyzer developers"] doctest = false [dependencies] +anyhow = "1.0" crossbeam-channel = "0.4" either = "1.5" env_logger = { version = "0.7.1", default-features = false } +itertools = "0.8.0" jod-thread = "0.1.0" log = "0.4.3" lsp-types = { version = "0.70.0", features = ["proposed"] } parking_lot = "0.10.0" +pico-args = "0.3.0" +rand = { version = "0.7.0", features = ["small_rng"] } relative-path = "1.0.0" rustc-hash = "1.0" serde = { version = "1.0.83", features = ["derive"] } @@ -31,6 +35,13 @@ ra_text_edit = { path = "../ra_text_edit" } ra_vfs = "0.5.0" ra_vfs_glob = { path = "../ra_vfs_glob" } +# This should only be used in CLI +ra_db = { path = "../ra_db" } +hir = { path = "../ra_hir", package = "ra_hir" } +hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } +hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } + + [target.'cfg(windows)'.dependencies] winapi = "0.3" diff --git a/crates/ra_lsp_server/src/args.rs b/crates/ra_lsp_server/src/args.rs new file mode 100644 index 000000000..41959797c --- /dev/null +++ b/crates/ra_lsp_server/src/args.rs @@ -0,0 +1,237 @@ +use anyhow::{bail, Result}; +use pico_args::Arguments; +use ra_lsp_server::cli::{BenchWhat, Position, Verbosity}; + +use std::{fmt::Write, path::PathBuf}; + +pub(crate) struct Args { + pub(crate) verbosity: Verbosity, + pub(crate) command: Command, +} + +pub(crate) enum Command { + Parse { + no_dump: bool, + }, + Symbols, + Highlight { + rainbow: bool, + }, + Stats { + randomize: bool, + memory_usage: bool, + only: Option, + with_deps: bool, + path: PathBuf, + }, + Bench { + path: PathBuf, + what: BenchWhat, + }, + RunServer, + Version, +} + +impl Args { + pub(crate) fn parse() -> Result> { + let mut matches = Arguments::from_env(); + + if matches.contains("--version") { + matches.finish().or_else(handle_extra_flags)?; + return Ok(Ok(Args { verbosity: Verbosity::Normal, command: Command::Version })); + } + + let verbosity = match ( + matches.contains(["-vv", "--spammy"]), + matches.contains(["-v", "--verbose"]), + matches.contains(["-q", "--quiet"]), + ) { + (true, _, true) => bail!("Invalid flags: -q conflicts with -vv"), + (true, _, false) => Verbosity::Spammy, + (false, false, false) => Verbosity::Normal, + (false, false, true) => Verbosity::Quiet, + (false, true, false) => Verbosity::Verbose, + (false, true, true) => bail!("Invalid flags: -q conflicts with -v"), + }; + + let subcommand = match matches.subcommand()? { + Some(it) => it, + None => { + matches.finish().or_else(handle_extra_flags)?; + return Ok(Ok(Args { verbosity, command: Command::RunServer })); + } + }; + let command = match subcommand.as_str() { + "parse" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +ra-cli-parse + +USAGE: + ra_cli parse [FLAGS] + +FLAGS: + -h, --help Prints help inforamtion + --no-dump" + ); + return Ok(Err(HelpPrinted)); + } + + let no_dump = matches.contains("--no-dump"); + matches.finish().or_else(handle_extra_flags)?; + Command::Parse { no_dump } + } + "symbols" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +ra-cli-symbols + +USAGE: + ra_cli highlight [FLAGS] + +FLAGS: + -h, --help Prints help inforamtion" + ); + return Ok(Err(HelpPrinted)); + } + + matches.finish().or_else(handle_extra_flags)?; + + Command::Symbols + } + "highlight" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +ra-cli-highlight + +USAGE: + ra_cli highlight [FLAGS] + +FLAGS: + -h, --help Prints help information + -r, --rainbow" + ); + return Ok(Err(HelpPrinted)); + } + + let rainbow = matches.contains(["-r", "--rainbow"]); + matches.finish().or_else(handle_extra_flags)?; + Command::Highlight { rainbow } + } + "analysis-stats" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +ra-cli-analysis-stats + +USAGE: + ra_cli analysis-stats [FLAGS] [OPTIONS] [PATH] + +FLAGS: + -h, --help Prints help information + --memory-usage + -v, --verbose + -q, --quiet + +OPTIONS: + -o + +ARGS: + " + ); + return Ok(Err(HelpPrinted)); + } + + let randomize = matches.contains("--randomize"); + let memory_usage = matches.contains("--memory-usage"); + let only: Option = matches.opt_value_from_str(["-o", "--only"])?; + let with_deps: bool = matches.contains("--with-deps"); + let path = { + let mut trailing = matches.free()?; + if trailing.len() != 1 { + bail!("Invalid flags"); + } + trailing.pop().unwrap().into() + }; + + Command::Stats { randomize, memory_usage, only, with_deps, path } + } + "analysis-bench" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +ra_cli-analysis-bench + +USAGE: + ra_cli analysis-bench [FLAGS] [OPTIONS] [PATH] + +FLAGS: + -h, --help Prints help information + -v, --verbose + +OPTIONS: + --complete Compute completions at this location + --highlight Hightlight this file + +ARGS: + Project to analyse" + ); + return Ok(Err(HelpPrinted)); + } + + let path: PathBuf = matches.opt_value_from_str("--path")?.unwrap_or_default(); + let highlight_path: Option = matches.opt_value_from_str("--highlight")?; + let complete_path: Option = matches.opt_value_from_str("--complete")?; + let goto_def_path: Option = matches.opt_value_from_str("--goto-def")?; + let what = match (highlight_path, complete_path, goto_def_path) { + (Some(path), None, None) => BenchWhat::Highlight { path: path.into() }, + (None, Some(position), None) => BenchWhat::Complete(position), + (None, None, Some(position)) => BenchWhat::GotoDef(position), + _ => panic!( + "exactly one of `--highlight`, `--complete` or `--goto-def` must be set" + ), + }; + Command::Bench { path, what } + } + _ => { + eprintln!( + "\ +ra-cli + +USAGE: + ra_cli + +FLAGS: + -h, --help Prints help information + +SUBCOMMANDS: + analysis-bench + analysis-stats + highlight + parse + symbols" + ); + return Ok(Err(HelpPrinted)); + } + }; + Ok(Ok(Args { verbosity, command })) + } +} + +pub(crate) struct HelpPrinted; + +fn handle_extra_flags(e: pico_args::Error) -> Result<()> { + if let pico_args::Error::UnusedArgsLeft(flags) = e { + let mut invalid_flags = String::new(); + for flag in flags { + write!(&mut invalid_flags, "{}, ", flag)?; + } + let (invalid_flags, _) = invalid_flags.split_at(invalid_flags.len() - 2); + bail!("Invalid flags: {}", invalid_flags); + } else { + bail!(e); + } +} 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 @@ +//! FIXME: write short doc here + +mod load_cargo; +mod analysis_stats; +mod analysis_bench; +mod progress_report; + +use std::io::Read; + +use anyhow::Result; +use ra_ide::{file_structure, Analysis}; +use ra_prof::profile; +use ra_syntax::{AstNode, SourceFile}; + +#[derive(Clone, Copy)] +pub enum Verbosity { + Spammy, + Verbose, + Normal, + Quiet, +} + +impl Verbosity { + pub fn is_verbose(self) -> bool { + match self { + Verbosity::Verbose | Verbosity::Spammy => true, + _ => false, + } + } + pub fn is_spammy(self) -> bool { + match self { + Verbosity::Spammy => true, + _ => false, + } + } +} + +pub fn parse(no_dump: bool) -> Result<()> { + let _p = profile("parsing"); + let file = file()?; + if !no_dump { + println!("{:#?}", file.syntax()); + } + std::mem::forget(file); + Ok(()) +} + +pub fn symbols() -> Result<()> { + let file = file()?; + for s in file_structure(&file) { + println!("{:?}", s); + } + Ok(()) +} + +pub fn highlight(rainbow: bool) -> Result<()> { + let (analysis, file_id) = Analysis::from_single_file(read_stdin()?); + let html = analysis.highlight_as_html(file_id, rainbow).unwrap(); + println!("{}", html); + Ok(()) +} + +pub use analysis_bench::{analysis_bench, BenchWhat, Position}; +pub use analysis_stats::analysis_stats; + +fn file() -> Result { + let text = read_stdin()?; + Ok(SourceFile::parse(&text).tree()) +} + +fn read_stdin() -> Result { + let mut buff = String::new(); + std::io::stdin().read_to_string(&mut buff)?; + Ok(buff) +} 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 @@ +//! FIXME: write short doc here + +use std::{ + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, + time::Instant, +}; + +use anyhow::{format_err, Result}; +use ra_db::{ + salsa::{Database, Durability}, + FileId, SourceDatabaseExt, +}; +use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol}; + +use crate::cli::{load_cargo::load_cargo, Verbosity}; + +pub enum BenchWhat { + Highlight { path: PathBuf }, + Complete(Position), + GotoDef(Position), +} + +pub struct Position { + pub path: PathBuf, + pub line: u32, + pub column: u32, +} + +impl FromStr for Position { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + let (path_line, column) = rsplit_at_char(s, ':')?; + let (path, line) = rsplit_at_char(path_line, ':')?; + Ok(Position { path: path.into(), line: line.parse()?, column: column.parse()? }) + } +} + +fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> { + let idx = s.rfind(c).ok_or_else(|| format_err!("no `{}` in {}", c, s))?; + Ok((&s[..idx], &s[idx + 1..])) +} + +pub fn analysis_bench(verbosity: Verbosity, path: &Path, what: BenchWhat) -> Result<()> { + ra_prof::init(); + + let start = Instant::now(); + eprint!("loading: "); + let (mut host, roots) = load_cargo(path)?; + let db = host.raw_database(); + eprintln!("{:?}\n", start.elapsed()); + + let file_id = { + let path = match &what { + BenchWhat::Highlight { path } => path, + BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path, + }; + let path = std::env::current_dir()?.join(path).canonicalize()?; + roots + .iter() + .find_map(|(source_root_id, project_root)| { + if project_root.is_member() { + for file_id in db.source_root(*source_root_id).walk() { + let rel_path = db.file_relative_path(file_id); + let abs_path = rel_path.to_path(project_root.path()); + if abs_path == path { + return Some(file_id); + } + } + } + None + }) + .ok_or_else(|| format_err!("Can't find {}", path.display()))? + }; + + match &what { + BenchWhat::Highlight { .. } => { + let res = do_work(&mut host, file_id, |analysis| { + analysis.diagnostics(file_id).unwrap(); + analysis.highlight_as_html(file_id, false).unwrap() + }); + if verbosity.is_verbose() { + println!("\n{}", res); + } + } + BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => { + let is_completion = match what { + BenchWhat::Complete(..) => true, + _ => false, + }; + + let offset = host + .analysis() + .file_line_index(file_id)? + .offset(LineCol { line: pos.line - 1, col_utf16: pos.column }); + let file_postion = FilePosition { file_id, offset }; + + if is_completion { + let res = + do_work(&mut host, file_id, |analysis| analysis.completions(file_postion)); + if verbosity.is_verbose() { + println!("\n{:#?}", res); + } + } else { + let res = + do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_postion)); + if verbosity.is_verbose() { + println!("\n{:#?}", res); + } + } + } + } + Ok(()) +} + +fn do_work T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T { + { + let start = Instant::now(); + eprint!("from scratch: "); + work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + } + { + let start = Instant::now(); + eprint!("no change: "); + work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + } + { + let start = Instant::now(); + eprint!("trivial change: "); + host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW); + work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + } + { + let start = Instant::now(); + eprint!("comment change: "); + { + let mut text = host.analysis().file_text(file_id).unwrap().to_string(); + text.push_str("\n/* Hello world */\n"); + let mut change = AnalysisChange::new(); + change.change_file(file_id, Arc::new(text)); + host.apply_change(change); + } + work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + } + { + let start = Instant::now(); + eprint!("const change: "); + host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH); + let res = work(&host.analysis()); + eprintln!("{:?}", start.elapsed()); + res + } +} 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 @@ +//! FIXME: write short doc here + +use std::{collections::HashSet, fmt::Write, path::Path, time::Instant}; + +use hir::{ + db::{DefDatabase, HirDatabase}, + AssocItem, Crate, HasSource, HirDisplay, ModuleDef, +}; +use hir_def::FunctionId; +use hir_ty::{Ty, TypeWalk}; +use itertools::Itertools; +use ra_db::SourceDatabaseExt; +use ra_syntax::AstNode; +use rand::{seq::SliceRandom, thread_rng}; + +use crate::cli::{load_cargo::load_cargo, progress_report::ProgressReport, Result, Verbosity}; + +pub fn analysis_stats( + verbosity: Verbosity, + memory_usage: bool, + path: &Path, + only: Option<&str>, + with_deps: bool, + randomize: bool, +) -> Result<()> { + let db_load_time = Instant::now(); + let (mut host, roots) = load_cargo(path)?; + let db = host.raw_database(); + println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); + let analysis_time = Instant::now(); + let mut num_crates = 0; + let mut visited_modules = HashSet::new(); + let mut visit_queue = Vec::new(); + + let members = + roots + .into_iter() + .filter_map(|(source_root_id, project_root)| { + if with_deps || project_root.is_member() { + Some(source_root_id) + } else { + None + } + }) + .collect::>(); + + let mut krates = Crate::all(db); + if randomize { + krates.shuffle(&mut thread_rng()); + } + for krate in krates { + let module = krate.root_module(db).expect("crate without root module"); + let file_id = module.definition_source(db).file_id; + if members.contains(&db.file_source_root(file_id.original_file(db))) { + num_crates += 1; + visit_queue.push(module); + } + } + + if randomize { + visit_queue.shuffle(&mut thread_rng()); + } + + println!("Crates in this dir: {}", num_crates); + let mut num_decls = 0; + let mut funcs = Vec::new(); + while let Some(module) = visit_queue.pop() { + if visited_modules.insert(module) { + visit_queue.extend(module.children(db)); + + for decl in module.declarations(db) { + num_decls += 1; + if let ModuleDef::Function(f) = decl { + funcs.push(f); + } + } + + for impl_block in module.impl_blocks(db) { + for item in impl_block.items(db) { + num_decls += 1; + if let AssocItem::Function(f) = item { + funcs.push(f); + } + } + } + } + } + println!("Total modules found: {}", visited_modules.len()); + println!("Total declarations: {}", num_decls); + println!("Total functions: {}", funcs.len()); + println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage()); + + if randomize { + funcs.shuffle(&mut thread_rng()); + } + + let inference_time = Instant::now(); + let mut bar = match verbosity { + Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(), + _ => ProgressReport::new(funcs.len() as u64), + }; + + bar.tick(); + let mut num_exprs = 0; + let mut num_exprs_unknown = 0; + let mut num_exprs_partially_unknown = 0; + let mut num_type_mismatches = 0; + for f in funcs { + let name = f.name(db); + let full_name = f + .module(db) + .path_to_root(db) + .into_iter() + .rev() + .filter_map(|it| it.name(db)) + .chain(Some(f.name(db))) + .join("::"); + if let Some(only_name) = only { + if name.to_string() != only_name && full_name != only_name { + continue; + } + } + let mut msg = format!("processing: {}", full_name); + if verbosity.is_verbose() { + let src = f.source(db); + let original_file = src.file_id.original_file(db); + let path = db.file_relative_path(original_file); + let syntax_range = src.value.syntax().text_range(); + write!(msg, " ({:?} {})", path, syntax_range).unwrap(); + } + if verbosity.is_spammy() { + bar.println(format!("{}", msg)); + } + bar.set_message(&msg); + let f_id = FunctionId::from(f); + let body = db.body(f_id.into()); + let inference_result = db.infer(f_id.into()); + let (previous_exprs, previous_unknown, previous_partially_unknown) = + (num_exprs, num_exprs_unknown, num_exprs_partially_unknown); + for (expr_id, _) in body.exprs.iter() { + let ty = &inference_result[expr_id]; + num_exprs += 1; + if let Ty::Unknown = ty { + num_exprs_unknown += 1; + } else { + let mut is_partially_unknown = false; + ty.walk(&mut |ty| { + if let Ty::Unknown = ty { + is_partially_unknown = true; + } + }); + if is_partially_unknown { + num_exprs_partially_unknown += 1; + } + } + if only.is_some() && verbosity.is_spammy() { + // in super-verbose mode for just one function, we print every single expression + let (_, sm) = db.body_with_source_map(f_id.into()); + let src = sm.expr_syntax(expr_id); + if let Some(src) = src { + let original_file = src.file_id.original_file(db); + let line_index = host.analysis().file_line_index(original_file).unwrap(); + let text_range = src.value.either( + |it| it.syntax_node_ptr().range(), + |it| it.syntax_node_ptr().range(), + ); + let (start, end) = ( + line_index.line_col(text_range.start()), + line_index.line_col(text_range.end()), + ); + bar.println(format!( + "{}:{}-{}:{}: {}", + start.line + 1, + start.col_utf16, + end.line + 1, + end.col_utf16, + ty.display(db) + )); + } else { + bar.println(format!("unknown location: {}", ty.display(db))); + } + } + if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { + num_type_mismatches += 1; + if verbosity.is_verbose() { + let (_, sm) = db.body_with_source_map(f_id.into()); + let src = sm.expr_syntax(expr_id); + if let Some(src) = src { + // FIXME: it might be nice to have a function (on Analysis?) that goes from Source -> (LineCol, LineCol) directly + let original_file = src.file_id.original_file(db); + let path = db.file_relative_path(original_file); + let line_index = host.analysis().file_line_index(original_file).unwrap(); + let text_range = src.value.either( + |it| it.syntax_node_ptr().range(), + |it| it.syntax_node_ptr().range(), + ); + let (start, end) = ( + line_index.line_col(text_range.start()), + line_index.line_col(text_range.end()), + ); + bar.println(format!( + "{} {}:{}-{}:{}: Expected {}, got {}", + path, + start.line + 1, + start.col_utf16, + end.line + 1, + end.col_utf16, + mismatch.expected.display(db), + mismatch.actual.display(db) + )); + } else { + bar.println(format!( + "{}: Expected {}, got {}", + name, + mismatch.expected.display(db), + mismatch.actual.display(db) + )); + } + } + } + } + if verbosity.is_spammy() { + bar.println(format!( + "In {}: {} exprs, {} unknown, {} partial", + full_name, + num_exprs - previous_exprs, + num_exprs_unknown - previous_unknown, + num_exprs_partially_unknown - previous_partially_unknown + )); + } + bar.inc(1); + } + bar.finish_and_clear(); + println!("Total expressions: {}", num_exprs); + println!( + "Expressions of unknown type: {} ({}%)", + num_exprs_unknown, + if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 } + ); + println!( + "Expressions of partially unknown type: {} ({}%)", + num_exprs_partially_unknown, + if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 } + ); + println!("Type mismatches: {}", num_type_mismatches); + println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage()); + println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage()); + + if memory_usage { + for (name, bytes) in host.per_query_memory_usage() { + println!("{:>8} {}", bytes, name) + } + let before = ra_prof::memory_usage(); + drop(host); + println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated) + } + + Ok(()) +} 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..b9a4e6aba --- /dev/null +++ b/crates/ra_lsp_server/src/cli/load_cargo.rs @@ -0,0 +1,152 @@ +//! FIXME: write short doc here + +use std::{collections::HashSet, path::Path}; + +use crossbeam_channel::{unbounded, Receiver}; +use ra_db::{CrateGraph, FileId, SourceRootId}; +use ra_ide::{AnalysisChange, AnalysisHost, FeatureFlags}; +use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace}; +use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; +use ra_vfs_glob::RustPackageFilterBuilder; +use rustc_hash::FxHashMap; + +use anyhow::Result; + +fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId { + FileId(f.0) +} +fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId { + SourceRootId(r.0) +} + +pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap)> { + let root = std::env::current_dir()?.join(root); + let ws = ProjectWorkspace::discover(root.as_ref(), &Default::default())?; + let project_roots = ws.to_roots(); + let (sender, receiver) = unbounded(); + let sender = Box::new(move |t| sender.send(t).unwrap()); + let (mut vfs, roots) = Vfs::new( + project_roots + .iter() + .map(|pkg_root| { + RootEntry::new( + pkg_root.path().clone(), + RustPackageFilterBuilder::default() + .set_member(pkg_root.is_member()) + .into_vfs_filter(), + ) + }) + .collect(), + sender, + Watch(false), + ); + + // FIXME: cfg options? + let default_cfg_options = { + let mut opts = get_rustc_cfg_options(); + opts.insert_atom("test".into()); + opts.insert_atom("debug_assertion".into()); + opts + }; + + let (crate_graph, _crate_names) = + ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| { + let vfs_file = vfs.load(path); + log::debug!("vfs file {:?} -> {:?}", path, vfs_file); + vfs_file.map(vfs_file_to_id) + }); + log::debug!("crate graph: {:?}", crate_graph); + + let source_roots = roots + .iter() + .map(|&vfs_root| { + let source_root_id = vfs_root_to_id(vfs_root); + let project_root = project_roots + .iter() + .find(|it| it.path() == &vfs.root2path(vfs_root)) + .unwrap() + .clone(); + (source_root_id, project_root) + }) + .collect::>(); + let host = load(&source_roots, crate_graph, &mut vfs, receiver); + Ok((host, source_roots)) +} + +pub fn load( + source_roots: &FxHashMap, + crate_graph: CrateGraph, + vfs: &mut Vfs, + receiver: Receiver, +) -> AnalysisHost { + let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::().ok()); + let mut host = AnalysisHost::new(lru_cap, FeatureFlags::default()); + let mut analysis_change = AnalysisChange::new(); + analysis_change.set_crate_graph(crate_graph); + + // wait until Vfs has loaded all roots + let mut roots_loaded = HashSet::new(); + for task in receiver { + vfs.handle_task(task); + let mut done = false; + for change in vfs.commit_changes() { + match change { + VfsChange::AddRoot { root, files } => { + let source_root_id = vfs_root_to_id(root); + let is_local = source_roots[&source_root_id].is_member(); + log::debug!( + "loaded source root {:?} with path {:?}", + source_root_id, + vfs.root2path(root) + ); + analysis_change.add_root(source_root_id, is_local); + analysis_change.set_debug_root_path( + source_root_id, + source_roots[&source_root_id].path().display().to_string(), + ); + + let mut file_map = FxHashMap::default(); + for (vfs_file, path, text) in files { + let file_id = vfs_file_to_id(vfs_file); + analysis_change.add_file(source_root_id, file_id, path.clone(), text); + file_map.insert(path, file_id); + } + roots_loaded.insert(source_root_id); + if roots_loaded.len() == vfs.n_roots() { + done = true; + } + } + VfsChange::AddFile { root, file, path, text } => { + let source_root_id = vfs_root_to_id(root); + let file_id = vfs_file_to_id(file); + analysis_change.add_file(source_root_id, file_id, path, text); + } + VfsChange::RemoveFile { .. } | VfsChange::ChangeFile { .. } => { + // We just need the first scan, so just ignore these + } + } + } + if done { + break; + } + } + + host.apply_change(analysis_change); + host +} + +#[cfg(test)] +mod tests { + use super::*; + + use hir::Crate; + + #[test] + fn test_loading_rust_analyzer() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); + let (host, _roots) = load_cargo(path).unwrap(); + let n_crates = Crate::all(host.raw_database()).len(); + // RA has quite a few crates, but the exact count doesn't matter + assert!(n_crates > 20); + } +} 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 @@ +//! A simple progress bar +//! +//! A single thread non-optimized progress bar +use std::io::Write; + +/// A Simple ASCII Progress Bar +pub struct ProgressReport { + curr: f32, + text: String, + hidden: bool, + + len: u64, + pos: u64, + msg: String, +} + +impl ProgressReport { + pub fn new(len: u64) -> ProgressReport { + ProgressReport { + curr: 0.0, + text: String::new(), + hidden: false, + len, + pos: 0, + msg: String::new(), + } + } + + pub fn hidden() -> ProgressReport { + ProgressReport { + curr: 0.0, + text: String::new(), + hidden: true, + len: 0, + pos: 0, + msg: String::new(), + } + } + + pub fn set_message(&mut self, msg: &str) { + self.msg = msg.to_string(); + self.tick(); + } + + pub fn println>(&mut self, msg: I) { + self.clear(); + println!("{}", msg.into()); + self.tick(); + } + + pub fn inc(&mut self, delta: u64) { + self.pos += delta; + if self.len == 0 { + self.set_value(0.0) + } else { + self.set_value((self.pos as f32) / (self.len as f32)) + } + self.tick(); + } + + pub fn finish_and_clear(&mut self) { + self.clear(); + } + + pub fn tick(&mut self) { + if self.hidden { + return; + } + let percent = (self.curr * 100.0) as u32; + let text = format!("{}/{} {:3>}% {}", self.pos, self.len, percent, self.msg); + self.update_text(&text); + } + + fn update_text(&mut self, text: &str) { + // Get length of common portion + let mut common_prefix_length = 0; + let common_length = usize::min(self.text.len(), text.len()); + + while common_prefix_length < common_length + && text.chars().nth(common_prefix_length).unwrap() + == self.text.chars().nth(common_prefix_length).unwrap() + { + common_prefix_length += 1; + } + + // Backtrack to the first differing character + let mut output = String::new(); + output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); + // Output new suffix + output += &text[common_prefix_length..text.len()]; + + // If the new text is shorter than the old one: delete overlapping characters + if let Some(overlap_count) = self.text.len().checked_sub(text.len()) { + if overlap_count > 0 { + output += &" ".repeat(overlap_count); + output += &"\x08".repeat(overlap_count); + } + } + + let _ = std::io::stdout().write(output.as_bytes()); + let _ = std::io::stdout().flush(); + self.text = text.to_string(); + } + + fn set_value(&mut self, value: f32) { + self.curr = f32::max(0.0, f32::min(1.0, value)); + } + + fn clear(&mut self) { + if self.hidden { + return; + } + + // Fill all last text to space and return the cursor + let spaces = " ".repeat(self.text.len()); + let backspaces = "\x08".repeat(self.text.len()); + print!("{}{}{}", backspaces, spaces, backspaces); + self.text = String::new(); + } +} diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index a3464a5a3..2832b2605 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -7,6 +7,8 @@ //! state, and `main_loop` module defines the rules for modifying it. #![recursion_limit = "512"] +pub mod cli; + #[allow(unused)] macro_rules! println { ($($tt:tt)*) => { 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 @@ //! `ra_lsp_server` binary +mod args; use lsp_server::Connection; -use ra_lsp_server::{from_json, show_message, Result, ServerConfig}; +use ra_lsp_server::{cli, from_json, show_message, Result, ServerConfig}; use ra_prof; +use crate::args::HelpPrinted; + fn main() -> Result<()> { setup_logging()?; - match Args::parse()? { - Args::Version => println!("rust-analyzer {}", env!("REV")), - Args::Run => run_server()?, + let args = match args::Args::parse()? { + Ok(it) => it, + Err(HelpPrinted) => return Ok(()), + }; + match args.command { + args::Command::Parse { no_dump } => cli::parse(no_dump)?, + args::Command::Symbols => cli::symbols()?, + args::Command::Highlight { rainbow } => cli::highlight(rainbow)?, + args::Command::Stats { randomize, memory_usage, only, with_deps, path } => { + cli::analysis_stats( + args.verbosity, + memory_usage, + path.as_ref(), + only.as_ref().map(String::as_ref), + with_deps, + randomize, + )? + } + + args::Command::Bench { path, what } => { + cli::analysis_bench(args.verbosity, path.as_ref(), what)? + } + + args::Command::RunServer => run_server()?, + args::Command::Version => println!("rust-analyzer {}", env!("REV")), } Ok(()) } @@ -20,19 +45,6 @@ fn setup_logging() -> Result<()> { Ok(()) } -enum Args { - Version, - Run, -} - -impl Args { - fn parse() -> Result { - let res = - if std::env::args().any(|it| it == "--version") { Args::Version } else { Args::Run }; - Ok(res) - } -} - fn run_server() -> Result<()> { log::info!("lifecycle: server started"); -- cgit v1.2.3