Skip to content

fix: Some file watching related vfs fixes #16913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/rust-analyzer/src/handlers/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ pub(crate) fn handle_did_change_watched_files(
state: &mut GlobalState,
params: DidChangeWatchedFilesParams,
) -> anyhow::Result<()> {
for change in params.changes {
for change in params.changes.iter().unique_by(|&it| &it.uri) {
if let Ok(path) = from_proto::abs_path(&change.uri) {
state.loader.handle.invalidate(path);
}
Expand Down
21 changes: 12 additions & 9 deletions crates/rust-analyzer/src/reload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,19 @@ impl GlobalState {
.flat_map(|ws| ws.to_roots())
.filter(|it| it.is_local)
.flat_map(|root| {
root.include.into_iter().flat_map(|it| {
[
format!("{it}/**/*.rs"),
format!("{it}/**/Cargo.toml"),
format!("{it}/**/Cargo.lock"),
]
})
root.include
.into_iter()
.flat_map(|it| [(it.clone(), "**/*.rs"), (it, "**/Cargo.{lock,toml}")])
})
.map(|glob_pattern| lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
.map(|(base, pat)| lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::Relative(
lsp_types::RelativePattern {
base_uri: lsp_types::OneOf::Right(
lsp_types::Url::from_file_path(base).unwrap(),
),
pattern: pat.to_owned(),
},
),
kind: None,
})
.collect(),
Expand Down
2 changes: 1 addition & 1 deletion crates/vfs-notify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl NotifyActor {
Message::Invalidate(path) => {
let contents = read(path.as_path());
let files = vec![(path, contents)];
self.send(loader::Message::Loaded { files });
self.send(loader::Message::Changed { files });
}
},
Event::NotifyEvent(event) => {
Expand Down
52 changes: 43 additions & 9 deletions crates/vfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,21 @@ impl nohash_hasher::IsEnabled for FileId {}
pub struct Vfs {
interner: PathInterner,
data: Vec<FileState>,
// FIXME: This should be a HashMap<FileId, ChangeFile>
// right now we do a nasty deduplication in GlobalState::process_changes that would be a lot
// easier to handle here on insertion.
changes: Vec<ChangedFile>,
// The above FIXME would then also get rid of this probably
created_this_cycle: Vec<FileId>,
}

#[derive(Copy, PartialEq, PartialOrd, Clone)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub enum FileState {
/// The file has been created this cycle.
Created,
/// The file exists.
Exists,
/// The file is deleted.
Deleted,
}

Expand All @@ -121,6 +130,11 @@ impl ChangedFile {
matches!(self.change, Change::Create(_) | Change::Delete)
}

/// Returns `true` if the change is [`Create`](ChangeKind::Create).
pub fn is_created(&self) -> bool {
matches!(self.change, Change::Create(_))
}

/// Returns `true` if the change is [`Modify`](ChangeKind::Modify).
pub fn is_modified(&self) -> bool {
matches!(self.change, Change::Modify(_))
Expand Down Expand Up @@ -160,7 +174,9 @@ pub enum ChangeKind {
impl Vfs {
/// Id of the given path if it exists in the `Vfs` and is not deleted.
pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
self.interner.get(path).filter(|&it| matches!(self.get(it), FileState::Exists))
self.interner
.get(path)
.filter(|&it| matches!(self.get(it), FileState::Exists | FileState::Created))
}

/// File path corresponding to the given `file_id`.
Expand All @@ -178,7 +194,9 @@ impl Vfs {
pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
(0..self.data.len())
.map(|it| FileId(it as u32))
.filter(move |&file_id| matches!(self.get(file_id), FileState::Exists))
.filter(move |&file_id| {
matches!(self.get(file_id), FileState::Exists | FileState::Created)
})
.map(move |file_id| {
let path = self.interner.lookup(file_id);
(file_id, path)
Expand All @@ -193,27 +211,43 @@ impl Vfs {
/// [`FileId`] for it.
pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool {
let file_id = self.alloc_file_id(path);
let change_kind = match (self.get(file_id), contents) {
let state = self.get(file_id);
let change_kind = match (state, contents) {
(FileState::Deleted, None) => return false,
(FileState::Deleted, Some(v)) => Change::Create(v),
(FileState::Exists, None) => Change::Delete,
(FileState::Exists, Some(v)) => Change::Modify(v),
(FileState::Exists | FileState::Created, None) => Change::Delete,
(FileState::Exists | FileState::Created, Some(v)) => Change::Modify(v),
};
self.data[file_id.0 as usize] = match change_kind {
Change::Create(_) => {
self.created_this_cycle.push(file_id);
FileState::Created
}
// If the file got created this cycle, make sure we keep it that way even
// if a modify comes in
Change::Modify(_) if matches!(state, FileState::Created) => FileState::Created,
Change::Modify(_) => FileState::Exists,
Change::Delete => FileState::Deleted,
};
let changed_file = ChangedFile { file_id, change: change_kind };
self.data[file_id.0 as usize] =
if changed_file.exists() { FileState::Exists } else { FileState::Deleted };
self.changes.push(changed_file);
true
}

/// Drain and returns all the changes in the `Vfs`.
pub fn take_changes(&mut self) -> Vec<ChangedFile> {
for file_id in self.created_this_cycle.drain(..) {
if self.data[file_id.0 as usize] == FileState::Created {
// downgrade the file from `Created` to `Exists` as the cycle is done
self.data[file_id.0 as usize] = FileState::Exists;
}
}
mem::take(&mut self.changes)
}

/// Provides a panic-less way to verify file_id validity.
pub fn exists(&self, file_id: FileId) -> bool {
matches!(self.get(file_id), FileState::Exists)
matches!(self.get(file_id), FileState::Exists | FileState::Created)
}

/// Returns the id associated with `path`
Expand Down
2 changes: 1 addition & 1 deletion crates/vfs/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub enum Message {
/// The [`Config`] version.
config_version: u32,
},
/// The handle loaded the following files' content.
/// The handle loaded the following files' content for the first time.
Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
/// The handle loaded the following files' content.
Changed { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
Expand Down