1 //! Driver for rust-analyzer.
3 //! Based on cli flags, either spawns an LSP server, or runs a batch analysis
5 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
10 use std::{env, fs, path::Path, process};
12 use lsp_server::Connection;
13 use project_model::ProjectManifest;
14 use rust_analyzer::{cli::flags, config::Config, from_json, lsp_ext::supports_utf8, Result};
17 #[cfg(all(feature = "mimalloc"))]
19 static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
21 #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))]
23 static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
26 if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
27 let mut args = std::env::args_os();
28 let _me = args.next().unwrap();
29 let rustc = args.next().unwrap();
30 let code = match rustc_wrapper::run_rustc_skipping_cargo_checking(rustc, args.collect()) {
31 Ok(rustc_wrapper::ExitCode(code)) => code.unwrap_or(102),
40 if let Err(err) = try_main() {
41 tracing::error!("Unexpected error: {}", err);
47 fn try_main() -> Result<()> {
48 let flags = flags::RustAnalyzer::from_env()?;
50 #[cfg(debug_assertions)]
51 if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
59 let mut log_file = flags.log_file.as_deref();
61 let env_log_file = env::var("RA_LOG_FILE").ok();
62 if let Some(env_log_file) = env_log_file.as_deref() {
63 log_file = Some(Path::new(env_log_file));
66 setup_logging(log_file)?;
67 let verbosity = flags.verbosity();
69 match flags.subcommand {
70 flags::RustAnalyzerCmd::LspServer(cmd) => {
71 if cmd.print_config_schema {
72 println!("{:#}", Config::json_schema());
76 println!("rust-analyzer {}", rust_analyzer::version());
80 println!("{}", flags::RustAnalyzer::HELP);
83 with_extra_thread("LspServer", run_server)?;
85 flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
86 with_extra_thread("MacroExpander", || proc_macro_srv::cli::run().map_err(Into::into))?;
88 flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
89 flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
90 flags::RustAnalyzerCmd::Highlight(cmd) => cmd.run()?,
91 flags::RustAnalyzerCmd::AnalysisStats(cmd) => cmd.run(verbosity)?,
92 flags::RustAnalyzerCmd::Diagnostics(cmd) => cmd.run()?,
93 flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?,
94 flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
95 flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
96 flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
101 fn setup_logging(log_file: Option<&Path>) -> Result<()> {
103 // This is required so that windows finds our pdb that is placed right beside the exe.
104 // By default it doesn't look at the folder the exe resides in, only in the current working
105 // directory which we set to the project workspace.
106 // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/general-environment-variables
107 // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
108 if let Ok(path) = env::current_exe() {
109 if let Some(path) = path.parent() {
110 env::set_var("_NT_SYMBOL_PATH", path);
114 if env::var("RUST_BACKTRACE").is_err() {
115 env::set_var("RUST_BACKTRACE", "short");
118 let log_file = match log_file {
120 if let Some(parent) = path.parent() {
121 let _ = fs::create_dir_all(parent);
123 Some(fs::File::create(path)?)
127 let filter = env::var("RA_LOG").ok();
128 // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
129 // information in there for debugging
130 logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;
137 const STACK_SIZE: usize = 1024 * 1024 * 8;
139 /// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us
140 /// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack
142 fn with_extra_thread(
143 thread_name: impl Into<String>,
144 f: impl FnOnce() -> Result<()> + Send + 'static,
147 std::thread::Builder::new().name(thread_name.into()).stack_size(STACK_SIZE).spawn(f)?;
148 match handle.join() {
150 Err(panic) => std::panic::resume_unwind(panic),
154 fn run_server() -> Result<()> {
155 tracing::info!("server version {} will start", rust_analyzer::version());
157 let (connection, io_threads) = Connection::stdio();
159 let (initialize_id, initialize_params) = connection.initialize_start()?;
160 tracing::info!("InitializeParams: {}", initialize_params);
161 let initialize_params =
162 from_json::<lsp_types::InitializeParams>("InitializeParams", &initialize_params)?;
164 let root_path = match initialize_params
166 .and_then(|it| it.to_file_path().ok())
167 .and_then(|it| AbsPathBuf::try_from(it).ok())
171 let cwd = env::current_dir()?;
172 AbsPathBuf::assert(cwd)
176 let mut config = Config::new(root_path, initialize_params.capabilities);
177 if let Some(json) = initialize_params.initialization_options {
178 if let Err(e) = config.update(json) {
180 notification::{Notification, ShowMessage},
181 MessageType, ShowMessageParams,
183 let not = lsp_server::Notification::new(
184 ShowMessage::METHOD.to_string(),
185 ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
187 connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
191 let server_capabilities = rust_analyzer::server_capabilities(&config);
193 let initialize_result = lsp_types::InitializeResult {
194 capabilities: server_capabilities,
195 server_info: Some(lsp_types::ServerInfo {
196 name: String::from("rust-analyzer"),
197 version: Some(rust_analyzer::version().to_string()),
199 offset_encoding: if supports_utf8(config.caps()) {
200 Some("utf-8".to_string())
206 let initialize_result = serde_json::to_value(initialize_result).unwrap();
208 connection.initialize_finish(initialize_id, initialize_result)?;
210 if let Some(client_info) = initialize_params.client_info {
211 tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
214 if config.linked_projects().is_empty() && config.detached_files().is_empty() {
215 let workspace_roots = initialize_params
220 .filter_map(|it| it.uri.to_file_path().ok())
221 .filter_map(|it| AbsPathBuf::try_from(it).ok())
224 .filter(|workspaces| !workspaces.is_empty())
225 .unwrap_or_else(|| vec![config.root_path().clone()]);
227 let discovered = ProjectManifest::discover_all(&workspace_roots);
228 tracing::info!("discovered projects: {:?}", discovered);
229 if discovered.is_empty() {
230 tracing::error!("failed to find any projects in {:?}", workspace_roots);
232 config.discovered_projects = Some(discovered);
235 rust_analyzer::main_loop(config, connection)?;
238 tracing::info!("server did shut down");