Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/load-cargo/src/lib.rs | 13 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/handlers/notification.rs | 21 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 12 | ||||
| -rw-r--r-- | crates/vfs/src/file_set.rs | 8 | ||||
| -rw-r--r-- | crates/vfs/src/file_set/tests.rs | 20 |
5 files changed, 67 insertions, 7 deletions
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index fb082d3209..f5c5cb432d 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -408,6 +408,19 @@ impl SourceRootConfig { .collect() } + /// Returns whether `path` belongs to a library (non-local) source root, such as the + /// sysroot sources or a cargo registry dependency. + /// + /// Paths that belong to no configured file set are *not* considered library files, as + /// files outside of any loaded workspace (for example scratch files) fall into the + /// catch-all file set despite being client-editable. + pub fn path_is_library(&self, path: &VfsPath) -> bool { + match self.fsc.classify_path(path) { + Some(idx) => !self.local_filesets.contains(&(idx as u64)), + None => false, + } + } + /// Maps local source roots to their parent source roots by bytewise comparing of root paths . /// If a `SourceRoot` doesn't have a parent and is local then it is not contained in this mapping but it can be asserted that it is a root `SourceRoot`. pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> { diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index d392c1ecbf..93c3aec756 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -84,8 +84,12 @@ pub(crate) fn handle_did_open_text_document( return Ok(()); } - let contents = params.text_document.text.into_bytes(); - state.vfs.write().0.set_file_contents(path, Some(contents)); + // Library files are immutable: the client never becomes authoritative over their + // contents, disk is the truth. + if !state.source_root_config.path_is_library(&path) { + let contents = params.text_document.text.into_bytes(); + state.vfs.write().0.set_file_contents(path, Some(contents)); + } if state.config.discover_workspace_config().is_some() { tracing::debug!("queuing task"); let _ = state @@ -120,7 +124,10 @@ pub(crate) fn handle_did_change_text_document( .into_bytes(); if *data != new_contents { data.clone_from(&new_contents); - state.vfs.write().0.set_file_contents(path, Some(new_contents)); + // Library files are immutable, changes to them are ignored. + if !state.source_root_config.path_is_library(&path) { + state.vfs.write().0.set_file_contents(path, Some(new_contents)); + } } } Ok(()) @@ -156,6 +163,14 @@ pub(crate) fn handle_did_save_text_document( params: DidSaveTextDocumentParams, ) -> anyhow::Result<()> { if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) { + // Library files are immutable and not watched, so the save is the only chance to + // pick up the changed disk contents. + if state.source_root_config.path_is_library(&vfs_path) + && let Some(path) = vfs_path.as_path() + { + state.loader.handle.invalidate(path.to_path_buf()); + } + let snap = state.snapshot(); let file_id = try_default!(snap.vfs_path_to_file_id(&vfs_path)?); let sr = snap.analysis.source_root_id(file_id)?; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index b4727360e5..972570b925 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -995,10 +995,14 @@ impl GlobalState { } let path = VfsPath::from(path); - // if the file is in mem docs, it's managed by the client via notifications - // so only set it if its not in there - if !self.mem_docs.contains(&path) - && (is_changed || vfs.file_id(&path).is_none()) + // If the file is in mem docs, it's managed by the client via + // notifications so only set it if its not in there. Library files are + // exempt from that authority as they are considered immutable, for + // them disk is always the source of truth. + let is_library = self.source_root_config.path_is_library(&path); + let client_is_authoritative = !is_library && self.mem_docs.contains(&path); + if !client_is_authoritative + && (is_changed || is_library || vfs.file_id(&path).is_none()) { vfs.set_file_contents(path, contents); } diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index 0c41ede5b5..c25cda2d36 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs @@ -128,6 +128,14 @@ impl FileSetConfig { self.map.stream().into_byte_vec() } + /// Returns the index of the set `path` would be partitioned into, or `None` if it + /// belongs to none of the configured sets (that is, the catch-all set for everything + /// else). + pub fn classify_path(&self, path: &VfsPath) -> Option<usize> { + let idx = self.classify(path, &mut Vec::new()); + (idx != self.len() - 1).then_some(idx) + } + /// Returns the set index for the given `path`. /// /// `scratch_space` is used as a buffer and will be entirely replaced. diff --git a/crates/vfs/src/file_set/tests.rs b/crates/vfs/src/file_set/tests.rs index 3cdb60dcb2..24b7438d5f 100644 --- a/crates/vfs/src/file_set/tests.rs +++ b/crates/vfs/src/file_set/tests.rs @@ -41,6 +41,26 @@ fn name_prefix() { assert_eq!(partition, vec![1, 1, 0]); } +#[test] +fn classify_path() { + let mut file_set = FileSetConfig::builder(); + file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]); + file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo/bar/baz".into())]); + let file_set = file_set.build(); + + let classify = |path: &str| file_set.classify_path(&VfsPath::new_virtual_path(path.into())); + assert_eq!(classify("/foo/src/lib.rs"), Some(0)); + assert_eq!(classify("/foo/bar/baz/lib.rs"), Some(1)); + assert_eq!(classify("/quux/lib.rs"), None); +} + +#[test] +fn classify_path_default_config() { + let file_set = FileSetConfig::default(); + let path = VfsPath::new_virtual_path("/foo/lib.rs".into()); + assert_eq!(file_set.classify_path(&path), None); +} + /// Ensure that we don't consider `/foo/bar_baz.rs` to be in the /// `/foo/bar/` root. #[test] |