Skip to content

Commit 1c40ac7

Browse files
committed
VFS no longer stores all source files in memory
1 parent af40101 commit 1c40ac7

File tree

9 files changed

+130
-143
lines changed

9 files changed

+130
-143
lines changed

crates/load-cargo/src/lib.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,8 @@ fn load_crate_graph(
331331
}
332332
let changes = vfs.take_changes();
333333
for file in changes {
334-
if file.exists() {
335-
let contents = vfs.file_contents(file.file_id);
336-
if let Ok(text) = std::str::from_utf8(contents) {
334+
if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
335+
if let Ok(text) = std::str::from_utf8(&v) {
337336
analysis_change.change_file(file.file_id, Some(text.into()))
338337
}
339338
}

crates/rust-analyzer/src/global_state.rs

+37-46
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//!
44
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
55
6-
use std::time::Instant;
6+
use std::{collections::hash_map::Entry, time::Instant};
77

88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
@@ -21,7 +21,7 @@ use proc_macro_api::ProcMacroServer;
2121
use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts};
2222
use rustc_hash::{FxHashMap, FxHashSet};
2323
use triomphe::Arc;
24-
use vfs::{AnchoredPathBuf, Vfs};
24+
use vfs::{AnchoredPathBuf, ChangedFile, Vfs};
2525

2626
use crate::{
2727
config::{Config, ConfigError},
@@ -217,8 +217,8 @@ impl GlobalState {
217217
pub(crate) fn process_changes(&mut self) -> bool {
218218
let _p = profile::span("GlobalState::process_changes");
219219

220-
let mut file_changes = FxHashMap::default();
221-
let (change, changed_files, workspace_structure_change) = {
220+
let mut file_changes = FxHashMap::<_, (bool, ChangedFile)>::default();
221+
let (change, modified_files, workspace_structure_change) = {
222222
let mut change = Change::new();
223223
let mut guard = self.vfs.write();
224224
let changed_files = guard.0.take_changes();
@@ -233,64 +233,63 @@ impl GlobalState {
233233
// id that is followed by a delete we actually skip observing the file text from the
234234
// earlier event, to avoid problems later on.
235235
for changed_file in changed_files {
236-
use vfs::ChangeKind::*;
237-
238-
file_changes
239-
.entry(changed_file.file_id)
240-
.and_modify(|(change, just_created)| {
241-
// None -> Delete => keep
242-
// Create -> Delete => collapse
243-
//
244-
match (change, just_created, changed_file.change_kind) {
236+
use vfs::Change::*;
237+
match file_changes.entry(changed_file.file_id) {
238+
Entry::Occupied(mut o) => {
239+
let (just_created, change) = o.get_mut();
240+
match (&mut change.change, just_created, changed_file.change) {
245241
// latter `Delete` wins
246242
(change, _, Delete) => *change = Delete,
247243
// merge `Create` with `Create` or `Modify`
248-
(Create, _, Create | Modify) => {}
244+
(Create(prev), _, Create(new) | Modify(new)) => *prev = new,
249245
// collapse identical `Modify`es
250-
(Modify, _, Modify) => {}
246+
(Modify(prev), _, Modify(new)) => *prev = new,
251247
// equivalent to `Modify`
252-
(change @ Delete, just_created, Create) => {
253-
*change = Modify;
248+
(change @ Delete, just_created, Create(new)) => {
249+
*change = Modify(new);
254250
*just_created = true;
255251
}
256252
// shouldn't occur, but collapse into `Create`
257-
(change @ Delete, just_created, Modify) => {
258-
*change = Create;
253+
(change @ Delete, just_created, Modify(new)) => {
254+
*change = Create(new);
259255
*just_created = true;
260256
}
261257
// shouldn't occur, but collapse into `Modify`
262-
(Modify, _, Create) => {}
258+
(Modify(prev), _, Create(new)) => *prev = new,
263259
}
264-
})
265-
.or_insert((
266-
changed_file.change_kind,
267-
matches!(changed_file.change_kind, Create),
268-
));
260+
}
261+
Entry::Vacant(v) => {
262+
_ = v.insert((matches!(&changed_file.change, Create(_)), changed_file))
263+
}
264+
}
269265
}
270266

271267
let changed_files: Vec<_> = file_changes
272268
.into_iter()
273-
.filter(|(_, (change_kind, just_created))| {
274-
!matches!((change_kind, just_created), (vfs::ChangeKind::Delete, true))
269+
.filter(|(_, (just_created, change))| {
270+
!(*just_created && matches!(change.change, vfs::Change::Delete))
275271
})
276-
.map(|(file_id, (change_kind, _))| vfs::ChangedFile { file_id, change_kind })
272+
.map(|(file_id, (_, change))| vfs::ChangedFile { file_id, ..change })
277273
.collect();
278274

279275
let mut workspace_structure_change = None;
280276
// A file was added or deleted
281277
let mut has_structure_changes = false;
282278
let mut bytes = vec![];
283-
for file in &changed_files {
279+
let mut modified_files = vec![];
280+
for file in changed_files {
284281
let vfs_path = &vfs.file_path(file.file_id);
285282
if let Some(path) = vfs_path.as_path() {
286283
let path = path.to_path_buf();
287-
if reload::should_refresh_for_change(&path, file.change_kind) {
284+
if reload::should_refresh_for_change(&path, file.kind()) {
288285
workspace_structure_change = Some((path.clone(), false));
289286
}
290287
if file.is_created_or_deleted() {
291288
has_structure_changes = true;
292289
workspace_structure_change =
293290
Some((path, self.crate_graph_file_dependencies.contains(vfs_path)));
291+
} else {
292+
modified_files.push(file.file_id);
294293
}
295294
}
296295

@@ -299,10 +298,8 @@ impl GlobalState {
299298
self.diagnostics.clear_native_for(file.file_id);
300299
}
301300

302-
let text = if file.exists() {
303-
let bytes = vfs.file_contents(file.file_id).to_vec();
304-
305-
String::from_utf8(bytes).ok().and_then(|text| {
301+
let text = if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
302+
String::from_utf8(v).ok().and_then(|text| {
306303
// FIXME: Consider doing normalization in the `vfs` instead? That allows
307304
// getting rid of some locking
308305
let (text, line_endings) = LineEndings::normalize(text);
@@ -327,11 +324,10 @@ impl GlobalState {
327324
let roots = self.source_root_config.partition(vfs);
328325
change.set_roots(roots);
329326
}
330-
(change, changed_files, workspace_structure_change)
327+
(change, modified_files, workspace_structure_change)
331328
};
332329

333330
self.analysis_host.apply_change(change);
334-
335331
{
336332
let raw_database = self.analysis_host.raw_database();
337333
// FIXME: ideally we should only trigger a workspace fetch for non-library changes
@@ -343,13 +339,12 @@ impl GlobalState {
343339
force_crate_graph_reload,
344340
);
345341
}
346-
self.proc_macro_changed =
347-
changed_files.iter().filter(|file| !file.is_created_or_deleted()).any(|file| {
348-
let crates = raw_database.relevant_crates(file.file_id);
349-
let crate_graph = raw_database.crate_graph();
342+
self.proc_macro_changed = modified_files.into_iter().any(|file_id| {
343+
let crates = raw_database.relevant_crates(file_id);
344+
let crate_graph = raw_database.crate_graph();
350345

351-
crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
352-
});
346+
crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
347+
});
353348
}
354349

355350
true
@@ -494,10 +489,6 @@ impl GlobalStateSnapshot {
494489
})
495490
}
496491

497-
pub(crate) fn vfs_memory_usage(&self) -> usize {
498-
self.vfs_read().memory_usage()
499-
}
500-
501492
pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
502493
self.vfs.read().0.exists(file_id)
503494
}

crates/rust-analyzer/src/handlers/notification.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ pub(crate) fn handle_did_open_text_document(
5959
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
6060
let already_exists = state
6161
.mem_docs
62-
.insert(path.clone(), DocumentData::new(params.text_document.version))
62+
.insert(
63+
path.clone(),
64+
DocumentData::new(
65+
params.text_document.version,
66+
params.text_document.text.clone().into_bytes(),
67+
),
68+
)
6369
.is_err();
6470
if already_exists {
6571
tracing::error!("duplicate DidOpenTextDocument: {}", path);
@@ -76,28 +82,27 @@ pub(crate) fn handle_did_change_text_document(
7682
let _p = profile::span("handle_did_change_text_document");
7783

7884
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
79-
match state.mem_docs.get_mut(&path) {
85+
let data = match state.mem_docs.get_mut(&path) {
8086
Some(doc) => {
8187
// The version passed in DidChangeTextDocument is the version after all edits are applied
8288
// so we should apply it before the vfs is notified.
8389
doc.version = params.text_document.version;
90+
&mut doc.data
8491
}
8592
None => {
8693
tracing::error!("unexpected DidChangeTextDocument: {}", path);
8794
return Ok(());
8895
}
8996
};
9097

91-
let text = apply_document_changes(
98+
let new_contents = apply_document_changes(
9299
state.config.position_encoding(),
93-
|| {
94-
let vfs = &state.vfs.read().0;
95-
let file_id = vfs.file_id(&path).unwrap();
96-
std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into()
97-
},
100+
std::str::from_utf8(data).unwrap(),
98101
params.content_changes,
99-
);
100-
state.vfs.write().0.set_file_contents(path, Some(text.into_bytes()));
102+
)
103+
.into_bytes();
104+
*data = new_contents.clone();
105+
state.vfs.write().0.set_file_contents(path, Some(new_contents));
101106
}
102107
Ok(())
103108
}

crates/rust-analyzer/src/handlers/request.rs

-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ pub(crate) fn handle_analyzer_status(
103103
.collect::<Vec<&AbsPath>>()
104104
);
105105
}
106-
format_to!(buf, "\nVfs memory usage: {}\n", profile::Bytes::new(snap.vfs_memory_usage() as _));
107106
buf.push_str("\nAnalysis:\n");
108107
buf.push_str(
109108
&snap

crates/rust-analyzer/src/lsp/utils.rs

+16-19
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl GlobalState {
168168

169169
pub(crate) fn apply_document_changes(
170170
encoding: PositionEncoding,
171-
file_contents: impl FnOnce() -> String,
171+
file_contents: &str,
172172
mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
173173
) -> String {
174174
// If at least one of the changes is a full document change, use the last
@@ -179,7 +179,7 @@ pub(crate) fn apply_document_changes(
179179
let text = mem::take(&mut content_changes[idx].text);
180180
(text, &content_changes[idx + 1..])
181181
}
182-
None => (file_contents(), &content_changes[..]),
182+
None => (file_contents.to_owned(), &content_changes[..]),
183183
};
184184
if content_changes.is_empty() {
185185
return text;
@@ -276,61 +276,58 @@ mod tests {
276276
}
277277

278278
let encoding = PositionEncoding::Wide(WideEncoding::Utf16);
279-
let text = apply_document_changes(encoding, || String::new(), vec![]);
279+
let text = apply_document_changes(encoding, "", vec![]);
280280
assert_eq!(text, "");
281281
let text = apply_document_changes(
282282
encoding,
283-
|| text,
283+
&text,
284284
vec![TextDocumentContentChangeEvent {
285285
range: None,
286286
range_length: None,
287287
text: String::from("the"),
288288
}],
289289
);
290290
assert_eq!(text, "the");
291-
let text = apply_document_changes(encoding, || text, c![0, 3; 0, 3 => " quick"]);
291+
let text = apply_document_changes(encoding, &text, c![0, 3; 0, 3 => " quick"]);
292292
assert_eq!(text, "the quick");
293293
let text =
294-
apply_document_changes(encoding, || text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
294+
apply_document_changes(encoding, &text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
295295
assert_eq!(text, "quick foxes");
296-
let text = apply_document_changes(encoding, || text, c![0, 11; 0, 11 => "\ndream"]);
296+
let text = apply_document_changes(encoding, &text, c![0, 11; 0, 11 => "\ndream"]);
297297
assert_eq!(text, "quick foxes\ndream");
298-
let text = apply_document_changes(encoding, || text, c![1, 0; 1, 0 => "have "]);
298+
let text = apply_document_changes(encoding, &text, c![1, 0; 1, 0 => "have "]);
299299
assert_eq!(text, "quick foxes\nhave dream");
300300
let text = apply_document_changes(
301301
encoding,
302-
|| text,
302+
&text,
303303
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
304304
);
305305
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
306-
let text = apply_document_changes(
307-
encoding,
308-
|| text,
309-
c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"],
310-
);
306+
let text =
307+
apply_document_changes(encoding, &text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
311308
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
312309
let text = apply_document_changes(
313310
encoding,
314-
|| text,
311+
&text,
315312
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
316313
);
317314
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
318315
let text =
319-
apply_document_changes(encoding, || text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
316+
apply_document_changes(encoding, &text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
320317
assert_eq!(text, "the quick \nthey have quiet dreams\n");
321318

322319
let text = String::from("❤️");
323-
let text = apply_document_changes(encoding, || text, c![0, 0; 0, 0 => "a"]);
320+
let text = apply_document_changes(encoding, &text, c![0, 0; 0, 0 => "a"]);
324321
assert_eq!(text, "a❤️");
325322

326323
let text = String::from("a\nb");
327324
let text =
328-
apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
325+
apply_document_changes(encoding, &text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
329326
assert_eq!(text, "adcb");
330327

331328
let text = String::from("a\nb");
332329
let text =
333-
apply_document_changes(encoding, || text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
330+
apply_document_changes(encoding, &text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
334331
assert_eq!(text, "ațc\ncb");
335332
}
336333

crates/rust-analyzer/src/main_loop.rs

+2
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,8 @@ impl GlobalState {
576576
let vfs = &mut self.vfs.write().0;
577577
for (path, contents) in files {
578578
let path = VfsPath::from(path);
579+
// if the file is in mem docs, it's managed by the client via notifications
580+
// so only set it if its not in there
579581
if !self.mem_docs.contains(&path) {
580582
vfs.set_file_contents(path, contents);
581583
}

crates/rust-analyzer/src/mem_docs.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ impl MemDocs {
6262
#[derive(Debug, Clone)]
6363
pub(crate) struct DocumentData {
6464
pub(crate) version: i32,
65+
pub(crate) data: Vec<u8>,
6566
}
6667

6768
impl DocumentData {
68-
pub(crate) fn new(version: i32) -> Self {
69-
DocumentData { version }
69+
pub(crate) fn new(version: i32, data: Vec<u8>) -> Self {
70+
DocumentData { version, data }
7071
}
7172
}

crates/rust-analyzer/src/reload.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,9 @@ impl GlobalState {
503503
match vfs.file_id(&vfs_path) {
504504
Some(file_id) => Some(file_id),
505505
None => {
506-
if !self.mem_docs.contains(&vfs_path) {
507-
let contents = loader.handle.load_sync(path);
508-
vfs.set_file_contents(vfs_path.clone(), contents);
509-
}
506+
// FIXME: Consider not loading this here?
507+
let contents = loader.handle.load_sync(path);
508+
vfs.set_file_contents(vfs_path.clone(), contents);
510509
vfs.file_id(&vfs_path)
511510
}
512511
}

0 commit comments

Comments
 (0)