1 //! FIXME: write short doc here
8 use crossbeam_channel::{unbounded, Receiver};
9 use lsp_server::ErrorCode;
11 use parking_lot::RwLock;
13 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData,
16 use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
17 use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
18 use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
19 use relative_path::RelativePathBuf;
22 main_loop::pending_requests::{CompletedRequest, LatestRequests},
26 #[derive(Debug, Clone)]
28 pub publish_decorations: bool,
29 pub supports_location_link: bool,
30 pub line_folding_only: bool,
33 /// `WorldState` is the primary mutable state of the language server
35 /// The most interesting components are `vfs`, which stores a consistent
36 /// snapshot of the file systems, and `analysis_host`, which stores our
37 /// incremental salsa database.
39 pub struct WorldState {
41 //FIXME: this belongs to `LoopState` rather than to `WorldState`
42 pub roots_to_scan: usize,
43 pub roots: Vec<PathBuf>,
44 pub workspaces: Arc<Vec<ProjectWorkspace>>,
45 pub analysis_host: AnalysisHost,
46 pub vfs: Arc<RwLock<Vfs>>,
47 pub task_receiver: Receiver<VfsTask>,
48 pub latest_requests: Arc<RwLock<LatestRequests>>,
51 /// An immutable snapshot of the world's state at a point in time.
52 pub struct WorldSnapshot {
54 pub workspaces: Arc<Vec<ProjectWorkspace>>,
55 pub analysis: Analysis,
56 pub vfs: Arc<RwLock<Vfs>>,
57 pub latest_requests: Arc<RwLock<LatestRequests>>,
62 folder_roots: Vec<PathBuf>,
63 workspaces: Vec<ProjectWorkspace>,
64 lru_capacity: Option<usize>,
65 exclude_globs: &[Glob],
68 feature_flags: FeatureFlags,
70 let mut change = AnalysisChange::new();
72 let mut roots = Vec::new();
73 roots.extend(folder_roots.iter().map(|path| {
74 let mut filter = RustPackageFilterBuilder::default().set_member(true);
75 for glob in exclude_globs.iter() {
76 filter = filter.exclude(glob.clone());
78 RootEntry::new(path.clone(), filter.into_vfs_filter())
80 for ws in workspaces.iter() {
81 roots.extend(ws.to_roots().into_iter().map(|pkg_root| {
83 RustPackageFilterBuilder::default().set_member(pkg_root.is_member());
84 for glob in exclude_globs.iter() {
85 filter = filter.exclude(glob.clone());
87 RootEntry::new(pkg_root.path().clone(), filter.into_vfs_filter())
90 let (task_sender, task_receiver) = unbounded();
91 let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
92 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
93 let roots_to_scan = vfs_roots.len();
95 let vfs_root_path = vfs.root2path(r);
96 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
97 change.add_root(SourceRootId(r.0), is_local);
98 change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string());
101 // FIXME: Read default cfgs from config
102 let default_cfg_options = {
103 let mut opts = get_rustc_cfg_options();
104 opts.insert_atom("test".into());
105 opts.insert_atom("debug_assertion".into());
109 // Create crate graph from all the workspaces
110 let mut crate_graph = CrateGraph::default();
111 let mut load = |path: &std::path::Path| {
112 let vfs_file = vfs.load(path);
113 vfs_file.map(|f| FileId(f.0))
115 for ws in workspaces.iter() {
116 let (graph, crate_names) = ws.to_crate_graph(&default_cfg_options, &mut load);
117 let shift = crate_graph.extend(graph);
118 for (crate_id, name) in crate_names {
119 change.set_debug_crate_name(crate_id.shift(shift), name)
122 change.set_crate_graph(crate_graph);
124 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags);
125 analysis_host.apply_change(change);
130 workspaces: Arc::new(workspaces),
132 vfs: Arc::new(RwLock::new(vfs)),
134 latest_requests: Default::default(),
138 /// Returns a vec of libraries
139 /// FIXME: better API here
140 pub fn process_changes(
142 ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)> {
143 let changes = self.vfs.write().commit_changes();
144 if changes.is_empty() {
147 let mut libs = Vec::new();
148 let mut change = AnalysisChange::new();
151 VfsChange::AddRoot { root, files } => {
152 let root_path = self.vfs.read().root2path(root);
153 let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
155 self.roots_to_scan -= 1;
156 for (file, path, text) in files {
157 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
162 .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text))
164 libs.push((SourceRootId(root.0), files));
167 VfsChange::AddFile { root, file, path, text } => {
168 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
170 VfsChange::RemoveFile { root, file, path } => {
171 change.remove_file(SourceRootId(root.0), FileId(file.0), path)
173 VfsChange::ChangeFile { file, text } => {
174 change.change_file(FileId(file.0), text);
178 self.analysis_host.apply_change(change);
182 pub fn add_lib(&mut self, data: LibraryData) {
183 self.roots_to_scan -= 1;
184 let mut change = AnalysisChange::new();
185 change.add_library(data);
186 self.analysis_host.apply_change(change);
189 pub fn snapshot(&self) -> WorldSnapshot {
191 options: self.options.clone(),
192 workspaces: Arc::clone(&self.workspaces),
193 analysis: self.analysis_host.analysis(),
194 vfs: Arc::clone(&self.vfs),
195 latest_requests: Arc::clone(&self.latest_requests),
199 pub fn maybe_collect_garbage(&mut self) {
200 self.analysis_host.maybe_collect_garbage()
203 pub fn collect_garbage(&mut self) {
204 self.analysis_host.collect_garbage()
207 pub fn complete_request(&mut self, request: CompletedRequest) {
208 self.latest_requests.write().record(request)
211 pub fn feature_flags(&self) -> &FeatureFlags {
212 self.analysis_host.feature_flags()
217 pub fn analysis(&self) -> &Analysis {
221 pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> {
222 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
223 let file = self.vfs.read().path2file(&path).ok_or_else(|| {
224 // Show warning as this file is outside current workspace
226 code: ErrorCode::InvalidRequest as i32,
227 message: "Rust file outside current workspace is not supported yet.".to_string(),
233 pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
234 let path = self.vfs.read().file2path(VfsFile(id.0));
235 let url = Url::from_file_path(&path)
236 .map_err(|_| format!("can't convert path to url: {}", path.display()))?;
240 pub fn file_line_endings(&self, id: FileId) -> LineEndings {
241 self.vfs.read().file_line_endings(VfsFile(id.0))
244 pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> {
245 let base = self.vfs.read().root2path(VfsRoot(root.0));
246 let path = path.to_path(base);
247 let url = Url::from_file_path(&path)
248 .map_err(|_| format!("can't convert path to url: {}", path.display()))?;
252 pub fn status(&self) -> String {
253 let mut res = String::new();
254 if self.workspaces.is_empty() {
255 res.push_str("no workspaces\n")
257 res.push_str("workspaces:\n");
258 for w in self.workspaces.iter() {
259 res += &format!("{} packages loaded\n", w.n_packages());
262 res.push_str("\nanalysis:\n");
267 .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
272 pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
273 let path = self.vfs.read().file2path(VfsFile(file_id.0));
274 self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
277 pub fn feature_flags(&self) -> &FeatureFlags {
278 self.analysis.feature_flags()