1 //! The context or environment in which the language server functions. In our
2 //! server implementation this is know as the `WorldState`.
4 //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
6 use std::{convert::TryFrom, path::Path, sync::Arc};
8 use crossbeam_channel::{unbounded, Receiver};
10 use parking_lot::RwLock;
11 use ra_db::{CrateId, SourceRoot, VfsPath};
12 use ra_flycheck::{Flycheck, FlycheckConfig};
13 use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
14 use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
16 use vfs::{file_set::FileSetConfig, loader::Handle, AbsPathBuf};
19 config::{Config, FilesWatcher},
20 diagnostics::{CheckFixes, DiagnosticCollection},
22 line_endings::LineEndings,
23 main_loop::request_metrics::{LatestRequests, RequestMetrics},
24 to_proto::url_from_abs_path,
27 use rustc_hash::FxHashMap;
29 fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
30 // FIXME: Figure out the multi-workspace situation
31 workspaces.iter().find_map(|w| match w {
32 ProjectWorkspace::Cargo { cargo, .. } => {
33 let cargo_project_root = cargo.workspace_root().to_path_buf();
34 Some(Flycheck::new(config.clone(), cargo_project_root))
36 ProjectWorkspace::Json { .. } => {
37 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
43 /// `GlobalState` is the primary mutable state of the language server
45 /// The most interesting components are `vfs`, which stores a consistent
46 /// snapshot of the file systems, and `analysis_host`, which stores our
47 /// incremental salsa database.
49 pub struct GlobalState {
51 pub workspaces: Arc<Vec<ProjectWorkspace>>,
52 pub analysis_host: AnalysisHost,
53 pub loader: Box<dyn vfs::loader::Handle>,
54 pub task_receiver: Receiver<vfs::loader::Message>,
55 pub flycheck: Option<Flycheck>,
56 pub diagnostics: DiagnosticCollection,
57 pub proc_macro_client: ProcMacroClient,
58 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
59 pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
60 source_root_config: SourceRootConfig,
63 /// An immutable snapshot of the world's state at a point in time.
64 pub struct GlobalStateSnapshot {
66 pub workspaces: Arc<Vec<ProjectWorkspace>>,
67 pub analysis: Analysis,
68 pub check_fixes: CheckFixes,
69 pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
70 vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
75 workspaces: Vec<ProjectWorkspace>,
76 lru_capacity: Option<usize>,
79 let mut change = AnalysisChange::new();
81 let project_folders = ProjectFolders::new(&workspaces);
83 let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>();
84 let mut vfs = vfs::Vfs::default();
86 let proc_macro_client = match &config.proc_macro_srv {
87 None => ProcMacroClient::dummy(),
88 Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
92 "Failed to run ra_proc_macro_srv from path {}, error: {:?}",
96 ProcMacroClient::dummy()
102 let loader = vfs_notify::LoaderHandle::spawn(Box::new(move |msg| {
103 task_sender.send(msg).unwrap()
107 let watch = match config.files.watcher {
108 FilesWatcher::Client => vec![],
109 FilesWatcher::Notify => project_folders.watch,
111 loader.set_config(vfs::loader::Config { load: project_folders.load, watch });
113 // Create crate graph from all the workspaces
114 let mut crate_graph = CrateGraph::default();
115 let mut load = |path: &Path| {
116 let path = AbsPathBuf::try_from(path.to_path_buf()).ok()?;
117 let contents = loader.load_sync(&path);
118 let path = vfs::VfsPath::from(path);
119 vfs.set_file_contents(path.clone(), contents);
122 for ws in workspaces.iter() {
123 crate_graph.extend(ws.to_crate_graph(
124 config.cargo.target.as_deref(),
129 change.set_crate_graph(crate_graph);
131 let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
133 let mut analysis_host = AnalysisHost::new(lru_capacity);
134 analysis_host.apply_change(change);
135 let mut res = GlobalState {
137 workspaces: Arc::new(workspaces),
140 vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))),
142 latest_requests: Default::default(),
144 diagnostics: Default::default(),
146 source_root_config: project_folders.source_root_config,
148 res.process_changes();
152 pub fn update_configuration(&mut self, config: Config) {
153 self.analysis_host.update_lru_capacity(config.lru_capacity);
154 if config.check != self.config.check {
156 config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
159 self.config = config;
162 pub fn process_changes(&mut self) -> bool {
164 let mut change = AnalysisChange::new();
165 let (vfs, line_endings_map) = &mut *self.vfs.write();
166 let changed_files = vfs.take_changes();
167 if changed_files.is_empty() {
171 let fs_op = changed_files.iter().any(|it| it.is_created_or_deleted());
173 let roots = self.source_root_config.partition(&vfs);
174 change.set_roots(roots)
177 for file in changed_files {
178 let text = if file.exists() {
179 let bytes = vfs.file_contents(file.file_id).to_vec();
180 match String::from_utf8(bytes).ok() {
182 let (text, line_endings) = LineEndings::normalize(text);
183 line_endings_map.insert(file.file_id, line_endings);
191 change.change_file(file.file_id, text);
196 self.analysis_host.apply_change(change);
200 pub fn snapshot(&self) -> GlobalStateSnapshot {
201 GlobalStateSnapshot {
202 config: self.config.clone(),
203 workspaces: Arc::clone(&self.workspaces),
204 analysis: self.analysis_host.analysis(),
205 vfs: Arc::clone(&self.vfs),
206 latest_requests: Arc::clone(&self.latest_requests),
207 check_fixes: Arc::clone(&self.diagnostics.check_fixes),
211 pub fn maybe_collect_garbage(&mut self) {
212 self.analysis_host.maybe_collect_garbage()
215 pub fn collect_garbage(&mut self) {
216 self.analysis_host.collect_garbage()
219 pub(crate) fn complete_request(&mut self, request: RequestMetrics) {
220 self.latest_requests.write().record(request)
224 impl GlobalStateSnapshot {
225 pub(crate) fn analysis(&self) -> &Analysis {
229 pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
230 let path = from_proto::abs_path(url)?;
231 let path = path.into();
233 self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
237 pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
238 file_id_to_url(&self.vfs.read().0, id)
241 pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings {
242 self.vfs.read().1[&id]
245 pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
246 let mut base = self.vfs.read().0.file_path(file_id);
248 let path = base.join(path);
249 let path = path.as_path().unwrap();
250 url_from_abs_path(&path)
253 pub(crate) fn cargo_target_for_crate_root(
256 ) -> Option<(&CargoWorkspace, Target)> {
257 let file_id = self.analysis().crate_root(crate_id).ok()?;
258 let path = self.vfs.read().0.file_path(file_id);
259 let path = path.as_path()?;
260 self.workspaces.iter().find_map(|ws| match ws {
261 ProjectWorkspace::Cargo { cargo, .. } => {
262 cargo.target_by_root(&path).map(|it| (cargo, it))
264 ProjectWorkspace::Json { .. } => None,
268 pub fn status(&self) -> String {
269 let mut buf = String::new();
270 if self.workspaces.is_empty() {
271 buf.push_str("no workspaces\n")
273 buf.push_str("workspaces:\n");
274 for w in self.workspaces.iter() {
275 format_to!(buf, "{} packages loaded\n", w.n_packages());
278 buf.push_str("\nanalysis:\n");
283 .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
289 pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
290 let path = vfs.file_path(id);
291 let path = path.as_path().unwrap();
292 url_from_abs_path(&path)
296 pub(crate) struct ProjectFolders {
297 pub(crate) load: Vec<vfs::loader::Entry>,
298 pub(crate) watch: Vec<usize>,
299 pub(crate) source_root_config: SourceRootConfig,
302 impl ProjectFolders {
303 pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
304 let mut res = ProjectFolders::default();
305 let mut fsc = FileSetConfig::builder();
306 let mut local_filesets = vec![];
308 for root in workspaces.iter().flat_map(|it| it.to_roots()) {
309 let path = root.path().to_owned();
311 let mut file_set_roots: Vec<VfsPath> = vec![];
313 let entry = if root.is_member() {
314 vfs::loader::Entry::local_cargo_package(path.to_path_buf())
316 vfs::loader::Entry::cargo_package_dependency(path.to_path_buf())
318 res.load.push(entry);
319 if root.is_member() {
320 res.watch.push(res.load.len() - 1);
323 if let Some(out_dir) = root.out_dir() {
324 let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap();
325 res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
326 if root.is_member() {
327 res.watch.push(res.load.len() - 1);
329 file_set_roots.push(out_dir.into());
331 file_set_roots.push(path.to_path_buf().into());
333 if root.is_member() {
334 local_filesets.push(fsc.len());
336 fsc.add_file_set(file_set_roots)
339 let fsc = fsc.build();
340 res.source_root_config = SourceRootConfig { fsc, local_filesets };
346 #[derive(Default, Debug)]
347 pub(crate) struct SourceRootConfig {
348 pub(crate) fsc: FileSetConfig,
349 pub(crate) local_filesets: Vec<usize>,
352 impl SourceRootConfig {
353 pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
358 .map(|(idx, file_set)| {
359 let is_local = self.local_filesets.contains(&idx);
361 SourceRoot::new_local(file_set)
363 SourceRoot::new_library(file_set)