Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/load-cargo/src/lib.rs13
-rw-r--r--crates/rust-analyzer/src/handlers/notification.rs21
-rw-r--r--crates/rust-analyzer/src/main_loop.rs12
-rw-r--r--crates/vfs/src/file_set.rs8
-rw-r--r--crates/vfs/src/file_set/tests.rs20
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(&params.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]