From a87cd8ecc66f1b644e4b11b8438fde1d5575b73c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 11:16:08 +0200 Subject: Rename WorldState -> GlobalState --- crates/rust-analyzer/src/cargo_target_spec.rs | 4 +- crates/rust-analyzer/src/from_proto.rs | 8 +- crates/rust-analyzer/src/global_state.rs | 347 ++++++++++++++++++++++++ crates/rust-analyzer/src/lib.rs | 2 +- crates/rust-analyzer/src/main_loop.rs | 88 +++--- crates/rust-analyzer/src/main_loop/handlers.rs | 357 +++++++++++++------------ crates/rust-analyzer/src/to_proto.rs | 87 +++--- crates/rust-analyzer/src/world.rs | 347 ------------------------ 8 files changed, 629 insertions(+), 611 deletions(-) create mode 100644 crates/rust-analyzer/src/global_state.rs delete mode 100644 crates/rust-analyzer/src/world.rs diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 008518a08..44f856f6b 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs @@ -4,7 +4,7 @@ use ra_cfg::CfgExpr; use ra_ide::{FileId, RunnableKind, TestId}; use ra_project_model::{self, ProjectWorkspace, TargetKind}; -use crate::{world::WorldSnapshot, Result}; +use crate::{global_state::GlobalStateSnapshot, Result}; /// Abstract representation of Cargo target. /// @@ -89,7 +89,7 @@ impl CargoTargetSpec { } pub(crate) fn for_file( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, file_id: FileId, ) -> Result> { let &crate_id = match world.analysis().crate_for(file_id)?.first() { diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 4bb16a496..206673829 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs @@ -3,7 +3,7 @@ use ra_db::{FileId, FilePosition, FileRange}; use ra_ide::{LineCol, LineIndex}; use ra_syntax::{TextRange, TextSize}; -use crate::{world::WorldSnapshot, Result}; +use crate::{global_state::GlobalStateSnapshot, Result}; pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 }; @@ -16,12 +16,12 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex TextRange::new(start, end) } -pub(crate) fn file_id(world: &WorldSnapshot, url: &lsp_types::Url) -> Result { +pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result { world.uri_to_file_id(url) } pub(crate) fn file_position( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, tdpp: lsp_types::TextDocumentPositionParams, ) -> Result { let file_id = file_id(world, &tdpp.text_document.uri)?; @@ -31,7 +31,7 @@ pub(crate) fn file_position( } pub(crate) fn file_range( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, text_document_identifier: lsp_types::TextDocumentIdentifier, range: lsp_types::Range, ) -> Result { diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs new file mode 100644 index 000000000..0bebb5bf6 --- /dev/null +++ b/crates/rust-analyzer/src/global_state.rs @@ -0,0 +1,347 @@ +//! The context or environment in which the language server functions. In our +//! server implementation this is know as the `WorldState`. +//! +//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`. + +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use crossbeam_channel::{unbounded, Receiver}; +use lsp_types::Url; +use parking_lot::RwLock; +use ra_flycheck::{Flycheck, FlycheckConfig}; +use ra_ide::{ + Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, +}; +use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; +use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; +use relative_path::RelativePathBuf; +use stdx::format_to; + +use crate::{ + config::Config, + diagnostics::{ + to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection, + }, + main_loop::pending_requests::{CompletedRequest, LatestRequests}, + vfs_glob::{Glob, RustPackageFilterBuilder}, + LspError, Result, +}; +use ra_db::ExternSourceId; +use rustc_hash::{FxHashMap, FxHashSet}; + +fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option { + // FIXME: Figure out the multi-workspace situation + workspaces + .iter() + .find_map(|w| match w { + ProjectWorkspace::Cargo { cargo, .. } => Some(cargo), + ProjectWorkspace::Json { .. } => None, + }) + .map(|cargo| { + let cargo_project_root = cargo.workspace_root().to_path_buf(); + Some(Flycheck::new(config.clone(), cargo_project_root)) + }) + .unwrap_or_else(|| { + log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); + None + }) +} + +/// `GlobalState` is the primary mutable state of the language server +/// +/// The most interesting components are `vfs`, which stores a consistent +/// snapshot of the file systems, and `analysis_host`, which stores our +/// incremental salsa database. +#[derive(Debug)] +pub struct GlobalState { + pub config: Config, + pub local_roots: Vec, + pub workspaces: Arc>, + pub analysis_host: AnalysisHost, + pub vfs: Arc>, + pub task_receiver: Receiver, + pub latest_requests: Arc>, + pub flycheck: Option, + pub diagnostics: DiagnosticCollection, + pub proc_macro_client: ProcMacroClient, +} + +/// An immutable snapshot of the world's state at a point in time. +pub struct GlobalStateSnapshot { + pub config: Config, + pub workspaces: Arc>, + pub analysis: Analysis, + pub latest_requests: Arc>, + pub check_fixes: CheckFixes, + vfs: Arc>, +} + +impl GlobalState { + pub fn new( + workspaces: Vec, + lru_capacity: Option, + exclude_globs: &[Glob], + watch: Watch, + config: Config, + ) -> GlobalState { + let mut change = AnalysisChange::new(); + + let extern_dirs: FxHashSet<_> = + workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect(); + + let mut local_roots = Vec::new(); + let roots: Vec<_> = { + let create_filter = |is_member| { + RustPackageFilterBuilder::default() + .set_member(is_member) + .exclude(exclude_globs.iter().cloned()) + .into_vfs_filter() + }; + workspaces + .iter() + .flat_map(ProjectWorkspace::to_roots) + .map(|pkg_root| { + let path = pkg_root.path().to_owned(); + if pkg_root.is_member() { + local_roots.push(path.clone()); + } + RootEntry::new(path, create_filter(pkg_root.is_member())) + }) + .chain( + extern_dirs + .iter() + .map(|path| RootEntry::new(path.to_owned(), create_filter(false))), + ) + .collect() + }; + + let (task_sender, task_receiver) = unbounded(); + let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); + let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); + + let mut extern_source_roots = FxHashMap::default(); + for r in vfs_roots { + let vfs_root_path = vfs.root2path(r); + let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it)); + change.add_root(SourceRootId(r.0), is_local); + change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); + + // FIXME: add path2root in vfs to simpily this logic + if extern_dirs.contains(&vfs_root_path) { + extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0)); + } + } + + // FIXME: Read default cfgs from config + let default_cfg_options = { + let mut opts = get_rustc_cfg_options(config.cargo.target.as_ref()); + opts.insert_atom("test".into()); + opts.insert_atom("debug_assertion".into()); + opts + }; + + let proc_macro_client = match &config.proc_macro_srv { + None => ProcMacroClient::dummy(), + Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) { + Ok(it) => it, + Err(err) => { + log::error!( + "Failed to run ra_proc_macro_srv from path {}, error: {:?}", + path.display(), + err + ); + ProcMacroClient::dummy() + } + }, + }; + + // Create crate graph from all the workspaces + let mut crate_graph = CrateGraph::default(); + let mut load = |path: &Path| { + // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs + let path = path.canonicalize().ok()?; + let vfs_file = vfs.load(&path); + vfs_file.map(|f| FileId(f.0)) + }; + for ws in workspaces.iter() { + crate_graph.extend(ws.to_crate_graph( + &default_cfg_options, + &extern_source_roots, + &proc_macro_client, + &mut load, + )); + } + change.set_crate_graph(crate_graph); + + let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); + + let mut analysis_host = AnalysisHost::new(lru_capacity); + analysis_host.apply_change(change); + GlobalState { + config, + local_roots, + workspaces: Arc::new(workspaces), + analysis_host, + vfs: Arc::new(RwLock::new(vfs)), + task_receiver, + latest_requests: Default::default(), + flycheck, + diagnostics: Default::default(), + proc_macro_client, + } + } + + pub fn update_configuration(&mut self, config: Config) { + self.analysis_host.update_lru_capacity(config.lru_capacity); + if config.check != self.config.check { + self.flycheck = + config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); + } + + self.config = config; + } + + /// Returns a vec of libraries + /// FIXME: better API here + pub fn process_changes( + &mut self, + roots_scanned: &mut usize, + ) -> Option)>)>> { + let changes = self.vfs.write().commit_changes(); + if changes.is_empty() { + return None; + } + let mut libs = Vec::new(); + let mut change = AnalysisChange::new(); + for c in changes { + match c { + VfsChange::AddRoot { root, files } => { + let root_path = self.vfs.read().root2path(root); + let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r)); + if is_local { + *roots_scanned += 1; + for (file, path, text) in files { + change.add_file(SourceRootId(root.0), FileId(file.0), path, text); + } + } else { + let files = files + .into_iter() + .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text)) + .collect(); + libs.push((SourceRootId(root.0), files)); + } + } + VfsChange::AddFile { root, file, path, text } => { + change.add_file(SourceRootId(root.0), FileId(file.0), path, text); + } + VfsChange::RemoveFile { root, file, path } => { + change.remove_file(SourceRootId(root.0), FileId(file.0), path) + } + VfsChange::ChangeFile { file, text } => { + change.change_file(FileId(file.0), text); + } + } + } + self.analysis_host.apply_change(change); + Some(libs) + } + + pub fn add_lib(&mut self, data: LibraryData) { + let mut change = AnalysisChange::new(); + change.add_library(data); + self.analysis_host.apply_change(change); + } + + pub fn snapshot(&self) -> GlobalStateSnapshot { + GlobalStateSnapshot { + config: self.config.clone(), + workspaces: Arc::clone(&self.workspaces), + analysis: self.analysis_host.analysis(), + vfs: Arc::clone(&self.vfs), + latest_requests: Arc::clone(&self.latest_requests), + check_fixes: Arc::clone(&self.diagnostics.check_fixes), + } + } + + pub fn maybe_collect_garbage(&mut self) { + self.analysis_host.maybe_collect_garbage() + } + + pub fn collect_garbage(&mut self) { + self.analysis_host.collect_garbage() + } + + pub fn complete_request(&mut self, request: CompletedRequest) { + self.latest_requests.write().record(request) + } +} + +impl GlobalStateSnapshot { + pub fn analysis(&self) -> &Analysis { + &self.analysis + } + + pub fn uri_to_file_id(&self, uri: &Url) -> Result { + let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; + let file = self.vfs.read().path2file(&path).ok_or_else(|| { + // Show warning as this file is outside current workspace + // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. + LspError { + code: LspError::UNKNOWN_FILE, + message: "Rust file outside current workspace is not supported yet.".to_string(), + } + })?; + Ok(FileId(file.0)) + } + + pub fn file_id_to_uri(&self, id: FileId) -> Result { + let path = self.vfs.read().file2path(VfsFile(id.0)); + let url = url_from_path_with_drive_lowercasing(path)?; + + Ok(url) + } + + pub fn file_id_to_path(&self, id: FileId) -> PathBuf { + self.vfs.read().file2path(VfsFile(id.0)) + } + + pub fn file_line_endings(&self, id: FileId) -> LineEndings { + self.vfs.read().file_line_endings(VfsFile(id.0)) + } + + pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result { + let base = self.vfs.read().root2path(VfsRoot(root.0)); + let path = path.to_path(base); + let url = Url::from_file_path(&path) + .map_err(|_| format!("can't convert path to url: {}", path.display()))?; + Ok(url) + } + + pub fn status(&self) -> String { + let mut buf = String::new(); + if self.workspaces.is_empty() { + buf.push_str("no workspaces\n") + } else { + buf.push_str("workspaces:\n"); + for w in self.workspaces.iter() { + format_to!(buf, "{} packages loaded\n", w.n_packages()); + } + } + buf.push_str("\nanalysis:\n"); + buf.push_str( + &self + .analysis + .status() + .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()), + ); + buf + } + + pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> { + let path = self.vfs.read().file2path(VfsFile(file_id.0)); + self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) + } +} diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 57d0e9218..609cb69d3 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -26,7 +26,7 @@ mod main_loop; mod markdown; pub mod lsp_ext; pub mod config; -mod world; +mod global_state; mod diagnostics; mod semantic_tokens; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 2e5499485..35f2d7001 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -38,12 +38,13 @@ use threadpool::ThreadPool; use crate::{ config::{Config, FilesWatcher}, diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, - from_proto, lsp_ext, + from_proto, + global_state::{GlobalState, GlobalStateSnapshot}, + lsp_ext, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, }, - world::{WorldSnapshot, WorldState}, Result, }; @@ -92,7 +93,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) } let mut loop_state = LoopState::default(); - let mut world_state = { + let mut global_state = { let workspaces = { // FIXME: support dynamic workspace loading. let project_roots: FxHashSet<_> = ws_roots @@ -163,7 +164,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) connection.sender.send(request.into()).unwrap(); } - WorldState::new( + GlobalState::new( workspaces, config.lru_capacity, &globs, @@ -172,7 +173,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) ) }; - loop_state.roots_total = world_state.vfs.read().n_roots(); + loop_state.roots_total = global_state.vfs.read().n_roots(); let pool = ThreadPool::default(); let (task_sender, task_receiver) = unbounded::(); @@ -190,12 +191,12 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) Err(RecvError) => return Err("client exited without shutdown".into()), }, recv(task_receiver) -> task => Event::Task(task.unwrap()), - recv(world_state.task_receiver) -> task => match task { + recv(global_state.task_receiver) -> task => match task { Ok(task) => Event::Vfs(task), Err(RecvError) => return Err("vfs died".into()), }, recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), - recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { + recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { Ok(task) => Event::CheckWatcher(task), Err(RecvError) => return Err("check watcher died".into()), } @@ -210,16 +211,16 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) &task_sender, &libdata_sender, &connection, - &mut world_state, + &mut global_state, &mut loop_state, event, )?; } } - world_state.analysis_host.request_cancellation(); + global_state.analysis_host.request_cancellation(); log::info!("waiting for tasks to finish..."); task_receiver.into_iter().for_each(|task| { - on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) + on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) }); libdata_receiver.into_iter().for_each(drop); log::info!("...tasks have finished"); @@ -228,7 +229,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) drop(pool); log::info!("...threadpool has finished"); - let vfs = Arc::try_unwrap(world_state.vfs).expect("all snapshots should be dead"); + let vfs = Arc::try_unwrap(global_state.vfs).expect("all snapshots should be dead"); drop(vfs); Ok(()) @@ -319,7 +320,7 @@ fn loop_turn( task_sender: &Sender, libdata_sender: &Sender, connection: &Connection, - world_state: &mut WorldState, + global_state: &mut GlobalState, loop_state: &mut LoopState, event: Event, ) -> Result<()> { @@ -335,22 +336,22 @@ fn loop_turn( match event { Event::Task(task) => { - on_task(task, &connection.sender, &mut loop_state.pending_requests, world_state); - world_state.maybe_collect_garbage(); + on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state); + global_state.maybe_collect_garbage(); } Event::Vfs(task) => { - world_state.vfs.write().handle_task(task); + global_state.vfs.write().handle_task(task); } Event::Lib(lib) => { - world_state.add_lib(lib); - world_state.maybe_collect_garbage(); + global_state.add_lib(lib); + global_state.maybe_collect_garbage(); loop_state.in_flight_libraries -= 1; loop_state.roots_scanned += 1; } - Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, + Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, Event::Msg(msg) => match msg { Message::Request(req) => on_request( - world_state, + global_state, &mut loop_state.pending_requests, pool, task_sender, @@ -359,7 +360,7 @@ fn loop_turn( req, )?, Message::Notification(not) => { - on_notification(&connection.sender, world_state, loop_state, not)?; + on_notification(&connection.sender, global_state, loop_state, not)?; } Message::Response(resp) => { let removed = loop_state.pending_responses.remove(&resp.id); @@ -378,9 +379,9 @@ fn loop_turn( } (None, Some(configs)) => { if let Some(new_config) = configs.get(0) { - let mut config = world_state.config.clone(); + let mut config = global_state.config.clone(); config.update(&new_config); - world_state.update_configuration(config); + global_state.update_configuration(config); } } (None, None) => { @@ -393,7 +394,7 @@ fn loop_turn( }; let mut state_changed = false; - if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) { + if let Some(changes) = global_state.process_changes(&mut loop_state.roots_scanned) { state_changed = true; loop_state.pending_libraries.extend(changes); } @@ -415,7 +416,7 @@ fn loop_turn( } let show_progress = - !loop_state.workspace_loaded && world_state.config.client_caps.work_done_progress; + !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total @@ -424,7 +425,7 @@ fn loop_turn( { state_changed = true; loop_state.workspace_loaded = true; - if let Some(flycheck) = &world_state.flycheck { + if let Some(flycheck) = &global_state.flycheck { flycheck.update(); } } @@ -436,13 +437,13 @@ fn loop_turn( if state_changed && loop_state.workspace_loaded { update_file_notifications_on_threadpool( pool, - world_state.snapshot(), + global_state.snapshot(), task_sender.clone(), loop_state.subscriptions.subscriptions(), ); pool.execute({ let subs = loop_state.subscriptions.subscriptions(); - let snap = world_state.snapshot(); + let snap = global_state.snapshot(); move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) }); } @@ -466,7 +467,7 @@ fn on_task( task: Task, msg_sender: &Sender, pending_requests: &mut PendingRequests, - state: &mut WorldState, + state: &mut GlobalState, ) { match task { Task::Respond(response) => { @@ -484,7 +485,7 @@ fn on_task( } fn on_request( - world: &mut WorldState, + global_state: &mut GlobalState, pending_requests: &mut PendingRequests, pool: &ThreadPool, task_sender: &Sender, @@ -495,7 +496,7 @@ fn on_request( let mut pool_dispatcher = PoolDispatcher { req: Some(req), pool, - world, + global_state, task_sender, msg_sender, pending_requests, @@ -551,7 +552,7 @@ fn on_request( fn on_notification( msg_sender: &Sender, - state: &mut WorldState, + state: &mut GlobalState, loop_state: &mut LoopState, not: Notification, ) -> Result<()> { @@ -725,7 +726,7 @@ fn apply_document_changes( fn on_check_task( task: CheckTask, - world_state: &mut WorldState, + global_state: &mut GlobalState, task_sender: &Sender, ) -> Result<()> { match task { @@ -744,7 +745,7 @@ fn on_check_task( .uri .to_file_path() .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; - let file_id = match world_state.vfs.read().path2file(&path) { + let file_id = match global_state.vfs.read().path2file(&path) { Some(file) => FileId(file.0), None => { log::error!( @@ -764,7 +765,7 @@ fn on_check_task( } CheckTask::Status(status) => { - if world_state.config.client_caps.work_done_progress { + if global_state.config.client_caps.work_done_progress { let progress = match status { Status::Being => { lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { @@ -803,7 +804,7 @@ fn on_check_task( Ok(()) } -fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: &mut WorldState) { +fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: &mut GlobalState) { let subscriptions = state.diagnostics.handle_task(task); for file_id in subscriptions { @@ -878,7 +879,7 @@ fn send_startup_progress(sender: &Sender, loop_state: &mut LoopState) { struct PoolDispatcher<'a> { req: Option, pool: &'a ThreadPool, - world: &'a mut WorldState, + global_state: &'a mut GlobalState, pending_requests: &'a mut PendingRequests, msg_sender: &'a Sender, task_sender: &'a Sender, @@ -889,7 +890,7 @@ impl<'a> PoolDispatcher<'a> { /// Dispatches the request onto the current thread fn on_sync( &mut self, - f: fn(&mut WorldState, R::Params) -> Result, + f: fn(&mut GlobalState, R::Params) -> Result, ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, @@ -902,18 +903,21 @@ impl<'a> PoolDispatcher<'a> { return Ok(self); } }; - let world = panic::AssertUnwindSafe(&mut *self.world); + let world = panic::AssertUnwindSafe(&mut *self.global_state); let task = panic::catch_unwind(move || { let result = f(world.0, params); result_to_task::(id, result) }) .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?; - on_task(task, self.msg_sender, self.pending_requests, self.world); + on_task(task, self.msg_sender, self.pending_requests, self.global_state); Ok(self) } /// Dispatches the request onto thread pool - fn on(&mut self, f: fn(WorldSnapshot, R::Params) -> Result) -> Result<&mut Self> + fn on( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> Result, + ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + Send + 'static, @@ -927,7 +931,7 @@ impl<'a> PoolDispatcher<'a> { }; self.pool.execute({ - let world = self.world.snapshot(); + let world = self.global_state.snapshot(); let sender = self.task_sender.clone(); move || { let result = f(world, params); @@ -1011,7 +1015,7 @@ where fn update_file_notifications_on_threadpool( pool: &ThreadPool, - world: WorldSnapshot, + world: GlobalStateSnapshot, task_sender: Sender, subscriptions: Vec, ) { diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 7fd691764..1bb8e4473 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -32,17 +32,16 @@ use crate::{ config::RustfmtConfig, diagnostics::DiagnosticTask, from_json, from_proto, + global_state::GlobalStateSnapshot, lsp_ext::{self, InlayHint, InlayHintsParams}, - to_proto, - world::WorldSnapshot, - LspError, Result, + to_proto, LspError, Result, }; -pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { +pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result { let _p = profile("handle_analyzer_status"); - let mut buf = world.status(); + let mut buf = snap.status(); format_to!(buf, "\n\nrequests:\n"); - let requests = world.latest_requests.read(); + let requests = snap.latest_requests.read(); for (is_last, r) in requests.iter() { let mark = if is_last { "*" } else { " " }; format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis()); @@ -51,37 +50,37 @@ pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { } pub fn handle_syntax_tree( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::SyntaxTreeParams, ) -> Result { let _p = profile("handle_syntax_tree"); - let id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(id)?; + let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(id)?; let text_range = params.range.map(|r| from_proto::text_range(&line_index, r)); - let res = world.analysis().syntax_tree(id, text_range)?; + let res = snap.analysis().syntax_tree(id, text_range)?; Ok(res) } pub fn handle_expand_macro( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::ExpandMacroParams, ) -> Result> { let _p = profile("handle_expand_macro"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let offset = from_proto::offset(&line_index, params.position); - let res = world.analysis().expand_macro(FilePosition { file_id, offset })?; + let res = snap.analysis().expand_macro(FilePosition { file_id, offset })?; Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion })) } pub fn handle_selection_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::SelectionRangeParams, ) -> Result>> { let _p = profile("handle_selection_range"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let res: Result> = params .positions .into_iter() @@ -93,7 +92,7 @@ pub fn handle_selection_range( loop { ranges.push(range); let frange = FileRange { file_id, range }; - let next = world.analysis().extend_selection(frange)?; + let next = snap.analysis().extend_selection(frange)?; if next == range { break; } else { @@ -119,18 +118,18 @@ pub fn handle_selection_range( } pub fn handle_matching_brace( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::MatchingBraceParams, ) -> Result> { let _p = profile("handle_matching_brace"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let res = params .positions .into_iter() .map(|position| { let offset = from_proto::offset(&line_index, position); - let offset = match world.analysis().matching_brace(FilePosition { file_id, offset }) { + let offset = match snap.analysis().matching_brace(FilePosition { file_id, offset }) { Ok(Some(matching_brace_offset)) => matching_brace_offset, Err(_) | Ok(None) => offset, }; @@ -141,17 +140,17 @@ pub fn handle_matching_brace( } pub fn handle_join_lines( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::JoinLinesParams, ) -> Result> { let _p = profile("handle_join_lines"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let line_endings = world.file_line_endings(file_id); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let line_endings = snap.file_line_endings(file_id); let mut res = TextEdit::default(); for range in params.ranges { let range = from_proto::text_range(&line_index, range); - let edit = world.analysis().join_lines(FileRange { file_id, range })?; + let edit = snap.analysis().join_lines(FileRange { file_id, range })?; match res.union(edit) { Ok(()) => (), Err(_edit) => { @@ -164,37 +163,37 @@ pub fn handle_join_lines( } pub fn handle_on_enter( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result>> { let _p = profile("handle_on_enter"); - let position = from_proto::file_position(&world, params)?; - let edit = match world.analysis().on_enter(position)? { + let position = from_proto::file_position(&snap, params)?; + let edit = match snap.analysis().on_enter(position)? { None => return Ok(None), Some(it) => it, }; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit); Ok(Some(edit)) } // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. pub fn handle_on_type_formatting( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentOnTypeFormattingParams, ) -> Result>> { let _p = profile("handle_on_type_formatting"); - let mut position = from_proto::file_position(&world, params.text_document_position)?; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let mut position = from_proto::file_position(&snap, params.text_document_position)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); // in `ra_ide`, the `on_type` invariant is that // `text.char_at(position) == typed_char`. position.offset -= TextSize::of('.'); let char_typed = params.ch.chars().next().unwrap_or('\0'); assert!({ - let text = world.analysis().file_text(position.file_id)?; + let text = snap.analysis().file_text(position.file_id)?; text[usize::from(position.offset)..].starts_with(char_typed) }); @@ -206,7 +205,7 @@ pub fn handle_on_type_formatting( return Ok(None); } - let edit = world.analysis().on_char_typed(position, char_typed)?; + let edit = snap.analysis().on_char_typed(position, char_typed)?; let mut edit = match edit { Some(it) => it, None => return Ok(None), @@ -220,16 +219,16 @@ pub fn handle_on_type_formatting( } pub fn handle_document_symbol( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentSymbolParams, ) -> Result> { let _p = profile("handle_document_symbol"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let mut parents: Vec<(DocumentSymbol, Option)> = Vec::new(); - for symbol in world.analysis().file_structure(file_id)? { + for symbol in snap.analysis().file_structure(file_id)? { let doc_symbol = DocumentSymbol { name: symbol.label, detail: symbol.detail, @@ -255,10 +254,10 @@ pub fn handle_document_symbol( } } - let res = if world.config.client_caps.hierarchical_symbols { + let res = if snap.config.client_caps.hierarchical_symbols { document_symbols.into() } else { - let url = to_proto::url(&world, file_id)?; + let url = to_proto::url(&snap, file_id)?; let mut symbol_information = Vec::::new(); for symbol in document_symbols { flatten_document_symbol(&symbol, None, &url, &mut symbol_information); @@ -288,7 +287,7 @@ pub fn handle_document_symbol( } pub fn handle_workspace_symbol( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::WorkspaceSymbolParams, ) -> Result>> { let _p = profile("handle_workspace_symbol"); @@ -306,22 +305,22 @@ pub fn handle_workspace_symbol( q.limit(128); q }; - let mut res = exec_query(&world, query)?; + let mut res = exec_query(&snap, query)?; if res.is_empty() && !all_symbols { let mut query = Query::new(params.query); query.limit(128); - res = exec_query(&world, query)?; + res = exec_query(&snap, query)?; } return Ok(Some(res)); - fn exec_query(world: &WorldSnapshot, query: Query) -> Result> { + fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result> { let mut res = Vec::new(); - for nav in world.analysis().symbol_search(query)? { + for nav in snap.analysis().symbol_search(query)? { let info = SymbolInformation { name: nav.name().to_string(), kind: to_proto::symbol_kind(nav.kind()), - location: to_proto::location(world, nav.file_range())?, + location: to_proto::location(snap, nav.file_range())?, container_name: nav.container_name().map(|v| v.to_string()), deprecated: None, }; @@ -332,73 +331,73 @@ pub fn handle_workspace_symbol( } pub fn handle_goto_definition( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::GotoDefinitionParams, ) -> Result> { let _p = profile("handle_goto_definition"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_definition(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_definition(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_goto_implementation( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::request::GotoImplementationParams, ) -> Result> { let _p = profile("handle_goto_implementation"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_implementation(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_implementation(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_goto_type_definition( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::request::GotoTypeDefinitionParams, ) -> Result> { let _p = profile("handle_goto_type_definition"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_type_definition(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_type_definition(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_parent_module( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result> { let _p = profile("handle_parent_module"); - let position = from_proto::file_position(&world, params)?; - let navs = world.analysis().parent_module(position)?; - let res = to_proto::goto_definition_response(&world, None, navs)?; + let position = from_proto::file_position(&snap, params)?; + let navs = snap.analysis().parent_module(position)?; + let res = to_proto::goto_definition_response(&snap, None, navs)?; Ok(Some(res)) } pub fn handle_runnables( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::RunnablesParams, ) -> Result> { let _p = profile("handle_runnables"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let offset = params.position.map(|it| from_proto::offset(&line_index, it)); let mut res = Vec::new(); - let workspace_root = world.workspace_root_for(file_id); - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; - for runnable in world.analysis().runnables(file_id)? { + let workspace_root = snap.workspace_root_for(file_id); + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; + for runnable in snap.analysis().runnables(file_id)? { if let Some(offset) = offset { if !runnable.nav.full_range().contains_inclusive(offset) { continue; @@ -413,7 +412,7 @@ pub fn handle_runnables( } } } - res.push(to_proto::runnable(&world, file_id, runnable)?); + res.push(to_proto::runnable(&snap, file_id, runnable)?); } // Add `cargo check` and `cargo test` for the whole package @@ -453,16 +452,16 @@ pub fn handle_runnables( } pub fn handle_completion( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CompletionParams, ) -> Result> { let _p = profile("handle_completion"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; let completion_triggered_after_single_colon = { let mut res = false; if let Some(ctx) = params.context { if ctx.trigger_character.unwrap_or_default() == ":" { - let source_file = world.analysis().parse(position.file_id)?; + let source_file = snap.analysis().parse(position.file_id)?; let syntax = source_file.syntax(); let text = syntax.text(); if let Some(next_char) = text.char_at(position.offset) { @@ -480,12 +479,12 @@ pub fn handle_completion( return Ok(None); } - let items = match world.analysis().completions(&world.config.completion, position)? { + let items = match snap.analysis().completions(&snap.config.completion, position)? { None => return Ok(None), Some(items) => items, }; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); let items: Vec = items .into_iter() .map(|item| to_proto::completion_item(&line_index, line_endings, item)) @@ -495,15 +494,15 @@ pub fn handle_completion( } pub fn handle_folding_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: FoldingRangeParams, ) -> Result>> { let _p = profile("handle_folding_range"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let folds = world.analysis().folding_ranges(file_id)?; - let text = world.analysis().file_text(file_id)?; - let line_index = world.analysis().file_line_index(file_id)?; - let line_folding_only = world.config.client_caps.line_folding_only; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let folds = snap.analysis().folding_ranges(file_id)?; + let text = snap.analysis().file_text(file_id)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let line_folding_only = snap.config.client_caps.line_folding_only; let res = folds .into_iter() .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it)) @@ -512,16 +511,16 @@ pub fn handle_folding_range( } pub fn handle_signature_help( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::SignatureHelpParams, ) -> Result> { let _p = profile("handle_signature_help"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let call_info = match world.analysis().call_info(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let call_info = match snap.analysis().call_info(position)? { None => return Ok(None), Some(it) => it, }; - let concise = !world.config.call_info_full; + let concise = !snap.config.call_info_full; let mut active_parameter = call_info.active_parameter.map(|it| it as i64); if concise && call_info.signature.has_self_param { active_parameter = active_parameter.map(|it| it.saturating_sub(1)); @@ -535,14 +534,17 @@ pub fn handle_signature_help( })) } -pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Result> { +pub fn handle_hover( + snap: GlobalStateSnapshot, + params: lsp_types::HoverParams, +) -> Result> { let _p = profile("handle_hover"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let info = match world.analysis().hover(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let info = match snap.analysis().hover(position)? { None => return Ok(None), Some(info) => info, }; - let line_index = world.analysis.file_line_index(position.file_id)?; + let line_index = snap.analysis.file_line_index(position.file_id)?; let range = to_proto::range(&line_index, info.range); let res = Hover { contents: HoverContents::Markup(MarkupContent { @@ -555,26 +557,29 @@ pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Res } pub fn handle_prepare_rename( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result> { let _p = profile("handle_prepare_rename"); - let position = from_proto::file_position(&world, params)?; + let position = from_proto::file_position(&snap, params)?; - let optional_change = world.analysis().rename(position, "dummy")?; + let optional_change = snap.analysis().rename(position, "dummy")?; let range = match optional_change { None => return Ok(None), Some(it) => it.range, }; - let line_index = world.analysis().file_line_index(position.file_id)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; let range = to_proto::range(&line_index, range); Ok(Some(PrepareRenameResponse::Range(range))) } -pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result> { +pub fn handle_rename( + snap: GlobalStateSnapshot, + params: RenameParams, +) -> Result> { let _p = profile("handle_rename"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; if params.new_name.is_empty() { return Err(LspError::new( @@ -584,36 +589,36 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result return Ok(None), Some(it) => it.info, }; - let workspace_edit = to_proto::workspace_edit(&world, source_change)?; + let workspace_edit = to_proto::workspace_edit(&snap, source_change)?; Ok(Some(workspace_edit)) } pub fn handle_references( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::ReferenceParams, ) -> Result>> { let _p = profile("handle_references"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; - let refs = match world.analysis().find_all_refs(position, None)? { + let refs = match snap.analysis().find_all_refs(position, None)? { None => return Ok(None), Some(refs) => refs, }; let locations = if params.context.include_declaration { refs.into_iter() - .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) + .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .collect() } else { // Only iterate over the references if include_declaration was false refs.references() .iter() - .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) + .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .collect() }; @@ -621,24 +626,24 @@ pub fn handle_references( } pub fn handle_formatting( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: DocumentFormattingParams, ) -> Result>> { let _p = profile("handle_formatting"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let file = world.analysis().file_text(file_id)?; - let crate_ids = world.analysis().crate_for(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let file = snap.analysis().file_text(file_id)?; + let crate_ids = snap.analysis().crate_for(file_id)?; - let file_line_index = world.analysis().file_line_index(file_id)?; + let file_line_index = snap.analysis().file_line_index(file_id)?; let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str())); - let mut rustfmt = match &world.config.rustfmt { + let mut rustfmt = match &snap.config.rustfmt { RustfmtConfig::Rustfmt { extra_args } => { let mut cmd = process::Command::new("rustfmt"); cmd.args(extra_args); if let Some(&crate_id) = crate_ids.first() { // Assume all crates are in the same edition - let edition = world.analysis().crate_edition(crate_id)?; + let edition = snap.analysis().crate_edition(crate_id)?; cmd.arg("--edition"); cmd.arg(edition.to_string()); } @@ -697,23 +702,23 @@ pub fn handle_formatting( } pub fn handle_code_action( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CodeActionParams, ) -> Result>> { let _p = profile("handle_code_action"); // We intentionally don't support command-based actions, as those either // requires custom client-code anyway, or requires server-initiated edits. // Server initiated edits break causality, so we avoid those as well. - if !world.config.client_caps.code_action_literals { + if !snap.config.client_caps.code_action_literals { return Ok(None); } - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.range); let frange = FileRange { file_id, range }; - let diagnostics = world.analysis().diagnostics(file_id)?; + let diagnostics = snap.analysis().diagnostics(file_id)?; let mut res: Vec = Vec::new(); let fixes_from_diagnostics = diagnostics @@ -724,13 +729,13 @@ pub fn handle_code_action( for fix in fixes_from_diagnostics { let title = fix.label; - let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?; + let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; let action = lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None }; res.push(action); } - for fix in world.check_fixes.get(&file_id).into_iter().flatten() { + for fix in snap.check_fixes.get(&file_id).into_iter().flatten() { let fix_range = from_proto::text_range(&line_index, fix.range); if fix_range.intersect(range).is_none() { continue; @@ -738,31 +743,31 @@ pub fn handle_code_action( res.push(fix.action.clone()); } - for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() { - res.push(to_proto::code_action(&world, assist)?.into()); + for assist in snap.analysis().assists(&snap.config.assist, frange)?.into_iter() { + res.push(to_proto::code_action(&snap, assist)?.into()); } Ok(Some(res)) } pub fn handle_code_lens( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CodeLensParams, ) -> Result>> { let _p = profile("handle_code_lens"); let mut lenses: Vec = Default::default(); - if world.config.lens.none() { + if snap.config.lens.none() { // early return before any db query! return Ok(Some(lenses)); } - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; - if world.config.lens.runnable() { + if snap.config.lens.runnable() { // Gather runnables - for runnable in world.analysis().runnables(file_id)? { + for runnable in snap.analysis().runnables(file_id)? { let (run_title, debugee) = match &runnable.kind { RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { ("▶\u{fe0e} Run Test", true) @@ -788,8 +793,8 @@ pub fn handle_code_lens( }; let range = to_proto::range(&line_index, runnable.nav.range()); - let r = to_proto::runnable(&world, file_id, runnable)?; - if world.config.lens.run { + let r = to_proto::runnable(&snap, file_id, runnable)?; + if snap.config.lens.run { let lens = CodeLens { range, command: Some(Command { @@ -802,7 +807,7 @@ pub fn handle_code_lens( lenses.push(lens); } - if debugee && world.config.lens.debug { + if debugee && snap.config.lens.debug { let debug_lens = CodeLens { range, command: Some(Command { @@ -817,11 +822,10 @@ pub fn handle_code_lens( } } - if world.config.lens.impementations { + if snap.config.lens.impementations { // Handle impls lenses.extend( - world - .analysis() + snap.analysis() .file_structure(file_id)? .into_iter() .filter(|it| match it.kind { @@ -856,14 +860,17 @@ enum CodeLensResolveData { Impls(lsp_types::request::GotoImplementationParams), } -pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result { +pub fn handle_code_lens_resolve( + snap: GlobalStateSnapshot, + code_lens: CodeLens, +) -> Result { let _p = profile("handle_code_lens_resolve"); let data = code_lens.data.unwrap(); let resolve = from_json::>("CodeLensResolveData", data)?; match resolve { Some(CodeLensResolveData::Impls(lens_params)) => { let locations: Vec = - match handle_goto_implementation(world, lens_params.clone())? { + match handle_goto_implementation(snap, lens_params.clone())? { Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc], Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs, Some(lsp_types::GotoDefinitionResponse::Link(links)) => links @@ -902,14 +909,14 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re } pub fn handle_document_highlight( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentHighlightParams, ) -> Result>> { let _p = profile("handle_document_highlight"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let line_index = world.analysis().file_line_index(position.file_id)?; + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; - let refs = match world + let refs = match snap .analysis() .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))? { @@ -929,19 +936,19 @@ pub fn handle_document_highlight( } pub fn handle_ssr( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::SsrParams, ) -> Result { let _p = profile("handle_ssr"); let source_change = - world.analysis().structural_search_replace(¶ms.query, params.parse_only)??; - to_proto::workspace_edit(&world, source_change) + snap.analysis().structural_search_replace(¶ms.query, params.parse_only)??; + to_proto::workspace_edit(&snap, source_change) } -pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result { +pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result { let _p = profile("publish_diagnostics"); - let line_index = world.analysis().file_line_index(file_id)?; - let diagnostics: Vec = world + let line_index = snap.analysis().file_line_index(file_id)?; + let diagnostics: Vec = snap .analysis() .diagnostics(file_id)? .into_iter() @@ -959,28 +966,28 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result Result> { let _p = profile("handle_inlay_hints"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let analysis = world.analysis(); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let analysis = snap.analysis(); let line_index = analysis.file_line_index(file_id)?; Ok(analysis - .inlay_hints(file_id, &world.config.inlay_hints)? + .inlay_hints(file_id, &snap.config.inlay_hints)? .into_iter() .map(|it| to_proto::inlay_int(&line_index, it)) .collect()) } pub fn handle_call_hierarchy_prepare( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyPrepareParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_prepare"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; + let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let nav_info = match world.analysis().call_hierarchy(position)? { + let nav_info = match snap.analysis().call_hierarchy(position)? { None => return Ok(None), Some(it) => it, }; @@ -989,24 +996,24 @@ pub fn handle_call_hierarchy_prepare( let res = navs .into_iter() .filter(|it| it.kind() == SyntaxKind::FN_DEF) - .map(|it| to_proto::call_hierarchy_item(&world, it)) + .map(|it| to_proto::call_hierarchy_item(&snap, it)) .collect::>>()?; Ok(Some(res)) } pub fn handle_call_hierarchy_incoming( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyIncomingCallsParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_incoming"); let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&world, doc, item.range)?; + let frange = from_proto::file_range(&snap, doc, item.range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match world.analysis().incoming_calls(fpos)? { + let call_items = match snap.analysis().incoming_calls(fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1015,8 +1022,8 @@ pub fn handle_call_hierarchy_incoming( for call_item in call_items.into_iter() { let file_id = call_item.target.file_id(); - let line_index = world.analysis().file_line_index(file_id)?; - let item = to_proto::call_hierarchy_item(&world, call_item.target)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; res.push(CallHierarchyIncomingCall { from: item, from_ranges: call_item @@ -1031,17 +1038,17 @@ pub fn handle_call_hierarchy_incoming( } pub fn handle_call_hierarchy_outgoing( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyOutgoingCallsParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_outgoing"); let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&world, doc, item.range)?; + let frange = from_proto::file_range(&snap, doc, item.range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match world.analysis().outgoing_calls(fpos)? { + let call_items = match snap.analysis().outgoing_calls(fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1050,8 +1057,8 @@ pub fn handle_call_hierarchy_outgoing( for call_item in call_items.into_iter() { let file_id = call_item.target.file_id(); - let line_index = world.analysis().file_line_index(file_id)?; - let item = to_proto::call_hierarchy_item(&world, call_item.target)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; res.push(CallHierarchyOutgoingCall { to: item, from_ranges: call_item @@ -1066,31 +1073,31 @@ pub fn handle_call_hierarchy_outgoing( } pub fn handle_semantic_tokens( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: SemanticTokensParams, ) -> Result> { let _p = profile("handle_semantic_tokens"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let text = world.analysis().file_text(file_id)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let text = snap.analysis().file_text(file_id)?; + let line_index = snap.analysis().file_line_index(file_id)?; - let highlights = world.analysis().highlight(file_id)?; + let highlights = snap.analysis().highlight(file_id)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } pub fn handle_semantic_tokens_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: SemanticTokensRangeParams, ) -> Result> { let _p = profile("handle_semantic_tokens_range"); - let frange = from_proto::file_range(&world, params.text_document, params.range)?; - let text = world.analysis().file_text(frange.file_id)?; - let line_index = world.analysis().file_line_index(frange.file_id)?; + let frange = from_proto::file_range(&snap, params.text_document, params.range)?; + let text = snap.analysis().file_text(frange.file_id)?; + let line_index = snap.analysis().file_line_index(frange.file_id)?; - let highlights = world.analysis().highlight_range(frange)?; + let highlights = snap.analysis().highlight_range(frange)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 85304aa87..0915a7fcb 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -10,7 +10,8 @@ use ra_syntax::{SyntaxKind, TextRange, TextSize}; use ra_vfs::LineEndings; use crate::{ - cargo_target_spec::CargoTargetSpec, lsp_ext, semantic_tokens, world::WorldSnapshot, Result, + cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext, + semantic_tokens, Result, }; pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { @@ -384,41 +385,44 @@ pub(crate) fn folding_range( } } -pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result { - world.file_id_to_uri(file_id) +pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result { + snap.file_id_to_uri(file_id) } pub(crate) fn versioned_text_document_identifier( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_id: FileId, version: Option, ) -> Result { - let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(world, file_id)?, version }; + let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version }; Ok(res) } -pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result { - let url = url(world, frange.file_id)?; - let line_index = world.analysis().file_line_index(frange.file_id)?; +pub(crate) fn location( + snap: &GlobalStateSnapshot, + frange: FileRange, +) -> Result { + let url = url(snap, frange.file_id)?; + let line_index = snap.analysis().file_line_index(frange.file_id)?; let range = range(&line_index, frange.range); let loc = lsp_types::Location::new(url, range); Ok(loc) } pub(crate) fn location_link( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, src: Option, target: NavigationTarget, ) -> Result { let origin_selection_range = match src { Some(src) => { - let line_index = world.analysis().file_line_index(src.file_id)?; + let line_index = snap.analysis().file_line_index(src.file_id)?; let range = range(&line_index, src.range); Some(range) } None => None, }; - let (target_uri, target_range, target_selection_range) = location_info(world, target)?; + let (target_uri, target_range, target_selection_range) = location_info(snap, target)?; let res = lsp_types::LocationLink { origin_selection_range, target_uri, @@ -429,12 +433,12 @@ pub(crate) fn location_link( } fn location_info( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { - let line_index = world.analysis().file_line_index(target.file_id())?; + let line_index = snap.analysis().file_line_index(target.file_id())?; - let target_uri = url(world, target.file_id())?; + let target_uri = url(snap, target.file_id())?; let target_range = range(&line_index, target.full_range()); let target_selection_range = target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); @@ -442,14 +446,14 @@ fn location_info( } pub(crate) fn goto_definition_response( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, src: Option, targets: Vec, ) -> Result { - if world.config.client_caps.location_link { + if snap.config.client_caps.location_link { let links = targets .into_iter() - .map(|nav| location_link(world, src, nav)) + .map(|nav| location_link(snap, src, nav)) .collect::>>()?; Ok(links.into()) } else { @@ -457,7 +461,7 @@ pub(crate) fn goto_definition_response( .into_iter() .map(|nav| { location( - world, + snap, FileRange { file_id: nav.file_id(), range: nav.focus_range().unwrap_or(nav.range()), @@ -470,13 +474,13 @@ pub(crate) fn goto_definition_response( } pub(crate) fn snippet_text_document_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, is_snippet: bool, source_file_edit: SourceFileEdit, ) -> Result { - let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; - let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; - let line_endings = world.file_line_endings(source_file_edit.file_id); + let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?; + let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; + let line_endings = snap.file_line_endings(source_file_edit.file_id); let edits = source_file_edit .edit .into_iter() @@ -486,17 +490,17 @@ pub(crate) fn snippet_text_document_edit( } pub(crate) fn resource_op( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_system_edit: FileSystemEdit, ) -> Result { let res = match file_system_edit { FileSystemEdit::CreateFile { source_root, path } => { - let uri = world.path_to_uri(source_root, &path)?; + let uri = snap.path_to_uri(source_root, &path)?; lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) } FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { - let old_uri = world.file_id_to_uri(src)?; - let new_uri = world.path_to_uri(dst_source_root, &dst_path)?; + let old_uri = snap.file_id_to_uri(src)?; + let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?; lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) } }; @@ -504,16 +508,16 @@ pub(crate) fn resource_op( } pub(crate) fn snippet_workspace_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Result { let mut document_changes: Vec = Vec::new(); for op in source_change.file_system_edits { - let op = resource_op(&world, op)?; + let op = resource_op(&snap, op)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); } for edit in source_change.source_file_edits { - let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?; + let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } let workspace_edit = @@ -522,11 +526,11 @@ pub(crate) fn snippet_workspace_edit( } pub(crate) fn workspace_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Result { assert!(!source_change.is_snippet); - snippet_workspace_edit(world, source_change).map(|it| it.into()) + snippet_workspace_edit(snap, source_change).map(|it| it.into()) } impl From for lsp_types::WorkspaceEdit { @@ -565,13 +569,13 @@ impl From for lsp_types::WorkspaceEdit { } pub fn call_hierarchy_item( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Result { let name = target.name().to_string(); let detail = target.description().map(|it| it.to_string()); let kind = symbol_kind(target.kind()); - let (uri, range, selection_range) = location_info(world, target)?; + let (uri, range, selection_range) = location_info(snap, target)?; Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) } @@ -619,23 +623,26 @@ fn main() { } } -pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result { +pub(crate) fn code_action( + snap: &GlobalStateSnapshot, + assist: Assist, +) -> Result { let res = lsp_ext::CodeAction { title: assist.label, - group: if world.config.client_caps.code_action_group { assist.group_label } else { None }, + group: if snap.config.client_caps.code_action_group { assist.group_label } else { None }, kind: Some(String::new()), - edit: Some(snippet_workspace_edit(world, assist.source_change)?), + edit: Some(snippet_workspace_edit(snap, assist.source_change)?), command: None, }; Ok(res) } pub(crate) fn runnable( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_id: FileId, runnable: Runnable, ) -> Result { - let spec = CargoTargetSpec::for_file(world, file_id)?; + let spec = CargoTargetSpec::for_file(snap, file_id)?; let target = spec.as_ref().map(|s| s.target.clone()); let (cargo_args, executable_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; @@ -648,14 +655,14 @@ pub(crate) fn runnable( target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) } }; - let location = location_link(world, None, runnable.nav)?; + let location = location_link(snap, None, runnable.nav)?; Ok(lsp_ext::Runnable { label, location: Some(location), kind: lsp_ext::RunnableKind::Cargo, args: lsp_ext::CargoRunnable { - workspace_root: world.workspace_root_for(file_id).map(|root| root.to_owned()), + workspace_root: snap.workspace_root_for(file_id).map(|root| root.to_owned()), cargo_args, executable_args, }, diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs deleted file mode 100644 index c1010e86a..000000000 --- a/crates/rust-analyzer/src/world.rs +++ /dev/null @@ -1,347 +0,0 @@ -//! The context or environment in which the language server functions. In our -//! server implementation this is know as the `WorldState`. -//! -//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`. - -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; - -use crossbeam_channel::{unbounded, Receiver}; -use lsp_types::Url; -use parking_lot::RwLock; -use ra_flycheck::{Flycheck, FlycheckConfig}; -use ra_ide::{ - Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, -}; -use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; -use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; -use relative_path::RelativePathBuf; -use stdx::format_to; - -use crate::{ - config::Config, - diagnostics::{ - to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection, - }, - main_loop::pending_requests::{CompletedRequest, LatestRequests}, - vfs_glob::{Glob, RustPackageFilterBuilder}, - LspError, Result, -}; -use ra_db::ExternSourceId; -use rustc_hash::{FxHashMap, FxHashSet}; - -fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option { - // FIXME: Figure out the multi-workspace situation - workspaces - .iter() - .find_map(|w| match w { - ProjectWorkspace::Cargo { cargo, .. } => Some(cargo), - ProjectWorkspace::Json { .. } => None, - }) - .map(|cargo| { - let cargo_project_root = cargo.workspace_root().to_path_buf(); - Some(Flycheck::new(config.clone(), cargo_project_root)) - }) - .unwrap_or_else(|| { - log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); - None - }) -} - -/// `WorldState` is the primary mutable state of the language server -/// -/// The most interesting components are `vfs`, which stores a consistent -/// snapshot of the file systems, and `analysis_host`, which stores our -/// incremental salsa database. -#[derive(Debug)] -pub struct WorldState { - pub config: Config, - pub local_roots: Vec, - pub workspaces: Arc>, - pub analysis_host: AnalysisHost, - pub vfs: Arc>, - pub task_receiver: Receiver, - pub latest_requests: Arc>, - pub flycheck: Option, - pub diagnostics: DiagnosticCollection, - pub proc_macro_client: ProcMacroClient, -} - -/// An immutable snapshot of the world's state at a point in time. -pub struct WorldSnapshot { - pub config: Config, - pub workspaces: Arc>, - pub analysis: Analysis, - pub latest_requests: Arc>, - pub check_fixes: CheckFixes, - vfs: Arc>, -} - -impl WorldState { - pub fn new( - workspaces: Vec, - lru_capacity: Option, - exclude_globs: &[Glob], - watch: Watch, - config: Config, - ) -> WorldState { - let mut change = AnalysisChange::new(); - - let extern_dirs: FxHashSet<_> = - workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect(); - - let mut local_roots = Vec::new(); - let roots: Vec<_> = { - let create_filter = |is_member| { - RustPackageFilterBuilder::default() - .set_member(is_member) - .exclude(exclude_globs.iter().cloned()) - .into_vfs_filter() - }; - workspaces - .iter() - .flat_map(ProjectWorkspace::to_roots) - .map(|pkg_root| { - let path = pkg_root.path().to_owned(); - if pkg_root.is_member() { - local_roots.push(path.clone()); - } - RootEntry::new(path, create_filter(pkg_root.is_member())) - }) - .chain( - extern_dirs - .iter() - .map(|path| RootEntry::new(path.to_owned(), create_filter(false))), - ) - .collect() - }; - - let (task_sender, task_receiver) = unbounded(); - let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); - let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); - - let mut extern_source_roots = FxHashMap::default(); - for r in vfs_roots { - let vfs_root_path = vfs.root2path(r); - let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it)); - change.add_root(SourceRootId(r.0), is_local); - change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); - - // FIXME: add path2root in vfs to simpily this logic - if extern_dirs.contains(&vfs_root_path) { - extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0)); - } - } - - // FIXME: Read default cfgs from config - let default_cfg_options = { - let mut opts = get_rustc_cfg_options(config.cargo.target.as_ref()); - opts.insert_atom("test".into()); - opts.insert_atom("debug_assertion".into()); - opts - }; - - let proc_macro_client = match &config.proc_macro_srv { - None => ProcMacroClient::dummy(), - Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) { - Ok(it) => it, - Err(err) => { - log::error!( - "Failed to run ra_proc_macro_srv from path {}, error: {:?}", - path.display(), - err - ); - ProcMacroClient::dummy() - } - }, - }; - - // Create crate graph from all the workspaces - let mut crate_graph = CrateGraph::default(); - let mut load = |path: &Path| { - // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs - let path = path.canonicalize().ok()?; - let vfs_file = vfs.load(&path); - vfs_file.map(|f| FileId(f.0)) - }; - for ws in workspaces.iter() { - crate_graph.extend(ws.to_crate_graph( - &default_cfg_options, - &extern_source_roots, - &proc_macro_client, - &mut load, - )); - } - change.set_crate_graph(crate_graph); - - let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); - - let mut analysis_host = AnalysisHost::new(lru_capacity); - analysis_host.apply_change(change); - WorldState { - config, - local_roots, - workspaces: Arc::new(workspaces), - analysis_host, - vfs: Arc::new(RwLock::new(vfs)), - task_receiver, - latest_requests: Default::default(), - flycheck, - diagnostics: Default::default(), - proc_macro_client, - } - } - - pub fn update_configuration(&mut self, config: Config) { - self.analysis_host.update_lru_capacity(config.lru_capacity); - if config.check != self.config.check { - self.flycheck = - config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); - } - - self.config = config; - } - - /// Returns a vec of libraries - /// FIXME: better API here - pub fn process_changes( - &mut self, - roots_scanned: &mut usize, - ) -> Option)>)>> { - let changes = self.vfs.write().commit_changes(); - if changes.is_empty() { - return None; - } - let mut libs = Vec::new(); - let mut change = AnalysisChange::new(); - for c in changes { - match c { - VfsChange::AddRoot { root, files } => { - let root_path = self.vfs.read().root2path(root); - let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r)); - if is_local { - *roots_scanned += 1; - for (file, path, text) in files { - change.add_file(SourceRootId(root.0), FileId(file.0), path, text); - } - } else { - let files = files - .into_iter() - .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text)) - .collect(); - libs.push((SourceRootId(root.0), files)); - } - } - VfsChange::AddFile { root, file, path, text } => { - change.add_file(SourceRootId(root.0), FileId(file.0), path, text); - } - VfsChange::RemoveFile { root, file, path } => { - change.remove_file(SourceRootId(root.0), FileId(file.0), path) - } - VfsChange::ChangeFile { file, text } => { - change.change_file(FileId(file.0), text); - } - } - } - self.analysis_host.apply_change(change); - Some(libs) - } - - pub fn add_lib(&mut self, data: LibraryData) { - let mut change = AnalysisChange::new(); - change.add_library(data); - self.analysis_host.apply_change(change); - } - - pub fn snapshot(&self) -> WorldSnapshot { - WorldSnapshot { - config: self.config.clone(), - workspaces: Arc::clone(&self.workspaces), - analysis: self.analysis_host.analysis(), - vfs: Arc::clone(&self.vfs), - latest_requests: Arc::clone(&self.latest_requests), - check_fixes: Arc::clone(&self.diagnostics.check_fixes), - } - } - - pub fn maybe_collect_garbage(&mut self) { - self.analysis_host.maybe_collect_garbage() - } - - pub fn collect_garbage(&mut self) { - self.analysis_host.collect_garbage() - } - - pub fn complete_request(&mut self, request: CompletedRequest) { - self.latest_requests.write().record(request) - } -} - -impl WorldSnapshot { - pub fn analysis(&self) -> &Analysis { - &self.analysis - } - - pub fn uri_to_file_id(&self, uri: &Url) -> Result { - let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; - let file = self.vfs.read().path2file(&path).ok_or_else(|| { - // Show warning as this file is outside current workspace - // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. - LspError { - code: LspError::UNKNOWN_FILE, - message: "Rust file outside current workspace is not supported yet.".to_string(), - } - })?; - Ok(FileId(file.0)) - } - - pub fn file_id_to_uri(&self, id: FileId) -> Result { - let path = self.vfs.read().file2path(VfsFile(id.0)); - let url = url_from_path_with_drive_lowercasing(path)?; - - Ok(url) - } - - pub fn file_id_to_path(&self, id: FileId) -> PathBuf { - self.vfs.read().file2path(VfsFile(id.0)) - } - - pub fn file_line_endings(&self, id: FileId) -> LineEndings { - self.vfs.read().file_line_endings(VfsFile(id.0)) - } - - pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result { - let base = self.vfs.read().root2path(VfsRoot(root.0)); - let path = path.to_path(base); - let url = Url::from_file_path(&path) - .map_err(|_| format!("can't convert path to url: {}", path.display()))?; - Ok(url) - } - - pub fn status(&self) -> String { - let mut buf = String::new(); - if self.workspaces.is_empty() { - buf.push_str("no workspaces\n") - } else { - buf.push_str("workspaces:\n"); - for w in self.workspaces.iter() { - format_to!(buf, "{} packages loaded\n", w.n_packages()); - } - } - buf.push_str("\nanalysis:\n"); - buf.push_str( - &self - .analysis - .status() - .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()), - ); - buf - } - - pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> { - let path = self.vfs.read().file2path(VfsFile(file_id.0)); - self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) - } -} -- cgit v1.2.3