diff options
-rw-r--r-- | crates/libanalysis/src/lib.rs | 124 | ||||
-rw-r--r-- | crates/libanalysis/src/module_map.rs | 117 | ||||
-rw-r--r-- | crates/libanalysis/tests/tests.rs | 21 | ||||
-rw-r--r-- | crates/libsyntax2/src/ast/generated.rs | 14 | ||||
-rw-r--r-- | crates/libsyntax2/src/grammar.ron | 10 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 6 | ||||
-rw-r--r-- | crates/server/src/server_world.rs | 2 |
7 files changed, 247 insertions, 47 deletions
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index 63c4c77cf..d01144627 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs | |||
@@ -10,16 +10,18 @@ extern crate fst; | |||
10 | extern crate rayon; | 10 | extern crate rayon; |
11 | 11 | ||
12 | mod symbol_index; | 12 | mod symbol_index; |
13 | mod module_map; | ||
13 | 14 | ||
14 | use once_cell::sync::OnceCell; | 15 | use once_cell::sync::OnceCell; |
15 | use rayon::prelude::*; | 16 | use rayon::prelude::*; |
16 | 17 | ||
17 | use std::{ | 18 | use std::{ |
18 | fmt, | 19 | fmt, |
19 | path::{Path, PathBuf}, | 20 | mem, |
21 | path::{Path}, | ||
20 | sync::{ | 22 | sync::{ |
21 | Arc, | 23 | Arc, |
22 | atomic::{AtomicUsize, Ordering::SeqCst}, | 24 | atomic::{AtomicBool, Ordering::SeqCst}, |
23 | }, | 25 | }, |
24 | collections::hash_map::HashMap, | 26 | collections::hash_map::HashMap, |
25 | time::Instant, | 27 | time::Instant, |
@@ -32,7 +34,10 @@ use libsyntax2::{ | |||
32 | }; | 34 | }; |
33 | use libeditor::{LineIndex, FileSymbol, find_node}; | 35 | use libeditor::{LineIndex, FileSymbol, find_node}; |
34 | 36 | ||
35 | use self::symbol_index::FileSymbols; | 37 | use self::{ |
38 | symbol_index::FileSymbols, | ||
39 | module_map::ModuleMap, | ||
40 | }; | ||
36 | pub use self::symbol_index::Query; | 41 | pub use self::symbol_index::Query; |
37 | 42 | ||
38 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 43 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
@@ -42,11 +47,12 @@ pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync; | |||
42 | 47 | ||
43 | #[derive(Debug)] | 48 | #[derive(Debug)] |
44 | pub struct WorldState { | 49 | pub struct WorldState { |
50 | updates: Vec<FileId>, | ||
45 | data: Arc<WorldData> | 51 | data: Arc<WorldData> |
46 | } | 52 | } |
47 | 53 | ||
48 | #[derive(Clone)] | ||
49 | pub struct World { | 54 | pub struct World { |
55 | needs_reindex: AtomicBool, | ||
50 | file_resolver: Arc<FileResolver>, | 56 | file_resolver: Arc<FileResolver>, |
51 | data: Arc<WorldData>, | 57 | data: Arc<WorldData>, |
52 | } | 58 | } |
@@ -57,18 +63,48 @@ impl fmt::Debug for World { | |||
57 | } | 63 | } |
58 | } | 64 | } |
59 | 65 | ||
66 | impl Clone for World { | ||
67 | fn clone(&self) -> World { | ||
68 | World { | ||
69 | needs_reindex: AtomicBool::new(self.needs_reindex.load(SeqCst)), | ||
70 | file_resolver: Arc::clone(&self.file_resolver), | ||
71 | data: Arc::clone(&self.data), | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
60 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 76 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
61 | pub struct FileId(pub u32); | 77 | pub struct FileId(pub u32); |
62 | 78 | ||
63 | impl WorldState { | 79 | impl WorldState { |
64 | pub fn new() -> WorldState { | 80 | pub fn new() -> WorldState { |
65 | WorldState { | 81 | WorldState { |
66 | data: Arc::new(WorldData::default()) | 82 | updates: Vec::new(), |
83 | data: Arc::new(WorldData::default()), | ||
67 | } | 84 | } |
68 | } | 85 | } |
69 | 86 | ||
70 | pub fn snapshot(&self, file_resolver: impl Fn(FileId, &Path) -> Option<FileId> + 'static + Send + Sync) -> World { | 87 | pub fn snapshot( |
88 | &mut self, | ||
89 | file_resolver: impl Fn(FileId, &Path) -> Option<FileId> + 'static + Send + Sync, | ||
90 | ) -> World { | ||
91 | let needs_reindex = self.updates.len() >= INDEXING_THRESHOLD; | ||
92 | if !self.updates.is_empty() { | ||
93 | let updates = mem::replace(&mut self.updates, Vec::new()); | ||
94 | let data = self.data_mut(); | ||
95 | for file_id in updates { | ||
96 | let syntax = data.file_map | ||
97 | .get(&file_id) | ||
98 | .map(|it| it.syntax()); | ||
99 | data.module_map.update_file( | ||
100 | file_id, | ||
101 | syntax, | ||
102 | &file_resolver, | ||
103 | ); | ||
104 | } | ||
105 | } | ||
71 | World { | 106 | World { |
107 | needs_reindex: AtomicBool::new(needs_reindex), | ||
72 | file_resolver: Arc::new(file_resolver), | 108 | file_resolver: Arc::new(file_resolver), |
73 | data: self.data.clone() | 109 | data: self.data.clone() |
74 | } | 110 | } |
@@ -79,28 +115,28 @@ impl WorldState { | |||
79 | } | 115 | } |
80 | 116 | ||
81 | pub fn change_files(&mut self, changes: impl Iterator<Item=(FileId, Option<String>)>) { | 117 | pub fn change_files(&mut self, changes: impl Iterator<Item=(FileId, Option<String>)>) { |
82 | let data = self.data_mut(); | 118 | let mut updates = Vec::new(); |
83 | let mut cnt = 0; | 119 | { |
84 | for (id, text) in changes { | 120 | let data = self.data_mut(); |
85 | cnt += 1; | 121 | for (file_id, text) in changes { |
86 | data.file_map.remove(&id); | 122 | data.file_map.remove(&file_id); |
87 | if let Some(text) = text { | 123 | if let Some(text) = text { |
88 | let file_data = FileData::new(text); | 124 | let file_data = FileData::new(text); |
89 | data.file_map.insert(id, Arc::new(file_data)); | 125 | data.file_map.insert(file_id, Arc::new(file_data)); |
90 | } else { | 126 | } else { |
91 | data.file_map.remove(&id); | 127 | data.file_map.remove(&file_id); |
128 | } | ||
129 | updates.push(file_id); | ||
92 | } | 130 | } |
93 | } | 131 | } |
94 | *data.unindexed.get_mut() += cnt; | 132 | self.updates.extend(updates) |
95 | } | 133 | } |
96 | 134 | ||
97 | fn data_mut(&mut self) -> &mut WorldData { | 135 | fn data_mut(&mut self) -> &mut WorldData { |
98 | if Arc::get_mut(&mut self.data).is_none() { | 136 | if Arc::get_mut(&mut self.data).is_none() { |
99 | self.data = Arc::new(WorldData { | 137 | self.data = Arc::new(WorldData { |
100 | unindexed: AtomicUsize::new( | ||
101 | self.data.unindexed.load(SeqCst) | ||
102 | ), | ||
103 | file_map: self.data.file_map.clone(), | 138 | file_map: self.data.file_map.clone(), |
139 | module_map: self.data.module_map.clone(), | ||
104 | }); | 140 | }); |
105 | } | 141 | } |
106 | Arc::get_mut(&mut self.data).unwrap() | 142 | Arc::get_mut(&mut self.data).unwrap() |
@@ -131,6 +167,24 @@ impl World { | |||
131 | .collect() | 167 | .collect() |
132 | } | 168 | } |
133 | 169 | ||
170 | pub fn parent_module(&self, id: FileId) -> Vec<(FileId, FileSymbol)> { | ||
171 | let module_map = &self.data.module_map; | ||
172 | let id = module_map.file2module(id); | ||
173 | module_map | ||
174 | .parent_modules(id) | ||
175 | .into_iter() | ||
176 | .map(|(id, m)| { | ||
177 | let id = module_map.module2file(id); | ||
178 | let sym = FileSymbol { | ||
179 | name: m.name().unwrap().text(), | ||
180 | node_range: m.syntax().range(), | ||
181 | kind: MODULE, | ||
182 | }; | ||
183 | (id, sym) | ||
184 | }) | ||
185 | .collect() | ||
186 | } | ||
187 | |||
134 | pub fn approximately_resolve_symbol( | 188 | pub fn approximately_resolve_symbol( |
135 | &self, | 189 | &self, |
136 | id: FileId, | 190 | id: FileId, |
@@ -178,32 +232,22 @@ impl World { | |||
178 | Some(name) => name.text(), | 232 | Some(name) => name.text(), |
179 | None => return Vec::new(), | 233 | None => return Vec::new(), |
180 | }; | 234 | }; |
181 | let paths = &[ | 235 | let module_map = &self.data.module_map; |
182 | PathBuf::from(format!("../{}.rs", name)), | 236 | let id = module_map.file2module(id); |
183 | PathBuf::from(format!("../{}/mod.rs", name)), | 237 | module_map |
184 | ]; | 238 | .child_module_by_name(id, name.as_str()) |
185 | paths.iter() | 239 | .into_iter() |
186 | .filter_map(|path| self.resolve_relative_path(id, path)) | 240 | .map(|id| module_map.module2file(id)) |
187 | .collect() | 241 | .collect() |
188 | } | 242 | } |
189 | 243 | ||
190 | fn resolve_relative_path(&self, id: FileId, path: &Path) -> Option<FileId> { | ||
191 | (self.file_resolver)(id, path) | ||
192 | } | ||
193 | |||
194 | fn reindex(&self) { | 244 | fn reindex(&self) { |
195 | let data = &*self.data; | 245 | if self.needs_reindex.compare_and_swap(false, true, SeqCst) { |
196 | let unindexed = data.unindexed.load(SeqCst); | ||
197 | if unindexed < INDEXING_THRESHOLD { | ||
198 | return; | ||
199 | } | ||
200 | if unindexed == data.unindexed.compare_and_swap(unindexed, 0, SeqCst) { | ||
201 | let now = Instant::now(); | 246 | let now = Instant::now(); |
247 | let data = &*self.data; | ||
202 | data.file_map | 248 | data.file_map |
203 | .par_iter() | 249 | .par_iter() |
204 | .for_each(|(_, data)| { | 250 | .for_each(|(_, data)| drop(data.symbols())); |
205 | data.symbols(); | ||
206 | }); | ||
207 | info!("parallel indexing took {:?}", now.elapsed()); | 251 | info!("parallel indexing took {:?}", now.elapsed()); |
208 | } | 252 | } |
209 | } | 253 | } |
@@ -218,8 +262,8 @@ impl World { | |||
218 | 262 | ||
219 | #[derive(Default, Debug)] | 263 | #[derive(Default, Debug)] |
220 | struct WorldData { | 264 | struct WorldData { |
221 | unindexed: AtomicUsize, | ||
222 | file_map: HashMap<FileId, Arc<FileData>>, | 265 | file_map: HashMap<FileId, Arc<FileData>>, |
266 | module_map: ModuleMap, | ||
223 | } | 267 | } |
224 | 268 | ||
225 | #[derive(Debug)] | 269 | #[derive(Debug)] |
diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs new file mode 100644 index 000000000..9b4c778b6 --- /dev/null +++ b/crates/libanalysis/src/module_map.rs | |||
@@ -0,0 +1,117 @@ | |||
1 | use std::{ | ||
2 | path::{PathBuf}, | ||
3 | }; | ||
4 | |||
5 | use libsyntax2::{ | ||
6 | ast::{self, AstNode, NameOwner}, | ||
7 | SyntaxNode, ParsedFile, SmolStr, | ||
8 | }; | ||
9 | use {FileId, FileResolver}; | ||
10 | |||
11 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] | ||
12 | pub struct ModuleId(FileId); | ||
13 | |||
14 | #[derive(Clone, Debug, Default)] | ||
15 | pub struct ModuleMap { | ||
16 | links: Vec<Link>, | ||
17 | } | ||
18 | |||
19 | #[derive(Clone, Debug)] | ||
20 | struct Link { | ||
21 | owner: ModuleId, | ||
22 | syntax: SyntaxNode, | ||
23 | points_to: Vec<ModuleId>, | ||
24 | } | ||
25 | |||
26 | impl ModuleMap { | ||
27 | pub fn update_file( | ||
28 | &mut self, | ||
29 | file_id: FileId, | ||
30 | syntax: Option<&ParsedFile>, | ||
31 | file_resolver: &FileResolver, | ||
32 | ) { | ||
33 | let mod_id = ModuleId(file_id); | ||
34 | self.links.retain(|link| link.owner != mod_id); | ||
35 | match syntax { | ||
36 | None => { | ||
37 | for link in self.links.iter_mut() { | ||
38 | link.points_to.retain(|&x| x != mod_id); | ||
39 | } | ||
40 | } | ||
41 | Some(syntax) => { | ||
42 | self.links.extend( | ||
43 | syntax.ast().modules().filter_map(|it| { | ||
44 | Link::new(mod_id, it) | ||
45 | }) | ||
46 | ) | ||
47 | } | ||
48 | } | ||
49 | self.links.iter_mut().for_each(|link| { | ||
50 | link.resolve(file_resolver) | ||
51 | }) | ||
52 | } | ||
53 | |||
54 | pub fn module2file(&self, m: ModuleId) -> FileId { | ||
55 | m.0 | ||
56 | } | ||
57 | |||
58 | pub fn file2module(&self, file_id: FileId) -> ModuleId { | ||
59 | ModuleId(file_id) | ||
60 | } | ||
61 | |||
62 | pub fn child_module_by_name(&self, parent_mod: ModuleId, child_mod: &str) -> Vec<ModuleId> { | ||
63 | self.links | ||
64 | .iter() | ||
65 | .filter(|link| link.owner == parent_mod) | ||
66 | .filter(|link| link.name() == child_mod) | ||
67 | .filter_map(|it| it.points_to.first()) | ||
68 | .map(|&it| it) | ||
69 | .collect() | ||
70 | } | ||
71 | |||
72 | pub fn parent_modules<'a>(&'a self, m: ModuleId) -> impl Iterator<Item=(ModuleId, ast::Module<'a>)> + 'a { | ||
73 | self.links | ||
74 | .iter() | ||
75 | .filter(move |link| link.points_to.iter().any(|&it| it == m)) | ||
76 | .map(|link| { | ||
77 | (link.owner, link.ast()) | ||
78 | }) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | impl Link { | ||
83 | fn new(owner: ModuleId, module: ast::Module) -> Option<Link> { | ||
84 | if module.name().is_none() { | ||
85 | return None; | ||
86 | } | ||
87 | let link = Link { | ||
88 | owner, | ||
89 | syntax: module.syntax().owned(), | ||
90 | points_to: Vec::new(), | ||
91 | }; | ||
92 | Some(link) | ||
93 | } | ||
94 | |||
95 | fn name(&self) -> SmolStr { | ||
96 | self.ast().name() | ||
97 | .unwrap() | ||
98 | .text() | ||
99 | } | ||
100 | |||
101 | fn ast(&self) -> ast::Module { | ||
102 | ast::Module::cast(self.syntax.borrowed()) | ||
103 | .unwrap() | ||
104 | } | ||
105 | |||
106 | fn resolve(&mut self, file_resolver: &FileResolver) { | ||
107 | let name = self.name(); | ||
108 | let paths = &[ | ||
109 | PathBuf::from(format!("../{}.rs", name)), | ||
110 | PathBuf::from(format!("../{}/mod.rs", name)), | ||
111 | ]; | ||
112 | self.points_to = paths.iter() | ||
113 | .filter_map(|path| file_resolver(self.owner.0, path)) | ||
114 | .map(ModuleId) | ||
115 | .collect(); | ||
116 | } | ||
117 | } | ||
diff --git a/crates/libanalysis/tests/tests.rs b/crates/libanalysis/tests/tests.rs index 9ef5200af..931ab4183 100644 --- a/crates/libanalysis/tests/tests.rs +++ b/crates/libanalysis/tests/tests.rs | |||
@@ -43,3 +43,24 @@ fn test_resolve_module() { | |||
43 | &symbols, | 43 | &symbols, |
44 | ); | 44 | ); |
45 | } | 45 | } |
46 | |||
47 | #[test] | ||
48 | fn test_resolve_parent_module() { | ||
49 | let mut world = WorldState::new(); | ||
50 | world.change_file(FileId(1), Some("mod foo;".to_string())); | ||
51 | world.change_file(FileId(2), Some("".to_string())); | ||
52 | |||
53 | let snap = world.snapshot(|id, path| { | ||
54 | assert_eq!(id, FileId(1)); | ||
55 | if path == PathBuf::from("../foo/mod.rs") { | ||
56 | return None; | ||
57 | } | ||
58 | assert_eq!(path, PathBuf::from("../foo.rs")); | ||
59 | Some(FileId(2)) | ||
60 | }); | ||
61 | let symbols = snap.parent_module(FileId(2)); | ||
62 | assert_eq_dbg( | ||
63 | r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, | ||
64 | &symbols, | ||
65 | ); | ||
66 | } | ||
diff --git a/crates/libsyntax2/src/ast/generated.rs b/crates/libsyntax2/src/ast/generated.rs index e8a87eba5..610b5198c 100644 --- a/crates/libsyntax2/src/ast/generated.rs +++ b/crates/libsyntax2/src/ast/generated.rs | |||
@@ -127,6 +127,12 @@ impl<'a> File<'a> { | |||
127 | .children() | 127 | .children() |
128 | .filter_map(FnDef::cast) | 128 | .filter_map(FnDef::cast) |
129 | } | 129 | } |
130 | |||
131 | pub fn modules(self) -> impl Iterator<Item = Module<'a>> + 'a { | ||
132 | self.syntax() | ||
133 | .children() | ||
134 | .filter_map(Module::cast) | ||
135 | } | ||
130 | } | 136 | } |
131 | 137 | ||
132 | // FnDef | 138 | // FnDef |
@@ -239,7 +245,13 @@ impl<'a> AstNode<'a> for Module<'a> { | |||
239 | 245 | ||
240 | impl<'a> ast::NameOwner<'a> for Module<'a> {} | 246 | impl<'a> ast::NameOwner<'a> for Module<'a> {} |
241 | impl<'a> ast::AttrsOwner<'a> for Module<'a> {} | 247 | impl<'a> ast::AttrsOwner<'a> for Module<'a> {} |
242 | impl<'a> Module<'a> {} | 248 | impl<'a> Module<'a> { |
249 | pub fn modules(self) -> impl Iterator<Item = Module<'a>> + 'a { | ||
250 | self.syntax() | ||
251 | .children() | ||
252 | .filter_map(Module::cast) | ||
253 | } | ||
254 | } | ||
243 | 255 | ||
244 | // Name | 256 | // Name |
245 | #[derive(Debug, Clone, Copy)] | 257 | #[derive(Debug, Clone, Copy)] |
diff --git a/crates/libsyntax2/src/grammar.ron b/crates/libsyntax2/src/grammar.ron index abeffb2c3..8e644d3c4 100644 --- a/crates/libsyntax2/src/grammar.ron +++ b/crates/libsyntax2/src/grammar.ron | |||
@@ -218,7 +218,8 @@ Grammar( | |||
218 | ast: { | 218 | ast: { |
219 | "File": ( | 219 | "File": ( |
220 | collections: [ | 220 | collections: [ |
221 | ["functions", "FnDef"] | 221 | ["functions", "FnDef"], |
222 | ["modules", "Module"], | ||
222 | ] | 223 | ] |
223 | ), | 224 | ), |
224 | "FnDef": ( traits: ["NameOwner", "AttrsOwner"] ), | 225 | "FnDef": ( traits: ["NameOwner", "AttrsOwner"] ), |
@@ -231,7 +232,12 @@ Grammar( | |||
231 | "NamedField": ( traits: ["NameOwner", "AttrsOwner"] ), | 232 | "NamedField": ( traits: ["NameOwner", "AttrsOwner"] ), |
232 | "EnumDef": ( traits: ["NameOwner", "AttrsOwner"] ), | 233 | "EnumDef": ( traits: ["NameOwner", "AttrsOwner"] ), |
233 | "TraitDef": ( traits: ["NameOwner", "AttrsOwner"] ), | 234 | "TraitDef": ( traits: ["NameOwner", "AttrsOwner"] ), |
234 | "Module": ( traits: ["NameOwner", "AttrsOwner"] ), | 235 | "Module": ( |
236 | traits: ["NameOwner", "AttrsOwner"], | ||
237 | collections: [ | ||
238 | ["modules", "Module"] | ||
239 | ] | ||
240 | ), | ||
235 | "ConstDef": ( traits: ["NameOwner", "AttrsOwner"] ), | 241 | "ConstDef": ( traits: ["NameOwner", "AttrsOwner"] ), |
236 | "StaticDef": ( traits: ["NameOwner", "AttrsOwner"] ), | 242 | "StaticDef": ( traits: ["NameOwner", "AttrsOwner"] ), |
237 | "TypeDef": ( traits: ["NameOwner", "AttrsOwner"] ), | 243 | "TypeDef": ( traits: ["NameOwner", "AttrsOwner"] ), |
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs index ad7c480dc..12a903dac 100644 --- a/crates/server/src/main_loop/mod.rs +++ b/crates/server/src/main_loop/mod.rs | |||
@@ -94,7 +94,7 @@ pub(super) fn main_loop( | |||
94 | Event::Msg(msg) => { | 94 | Event::Msg(msg) => { |
95 | match msg { | 95 | match msg { |
96 | RawMsg::Request(req) => { | 96 | RawMsg::Request(req) => { |
97 | if !on_request(io, &state, pool, &task_sender, req)? { | 97 | if !on_request(io, &mut state, pool, &task_sender, req)? { |
98 | return Ok(()); | 98 | return Ok(()); |
99 | } | 99 | } |
100 | } | 100 | } |
@@ -114,7 +114,7 @@ pub(super) fn main_loop( | |||
114 | 114 | ||
115 | fn on_request( | 115 | fn on_request( |
116 | io: &mut Io, | 116 | io: &mut Io, |
117 | world: &ServerWorldState, | 117 | world: &mut ServerWorldState, |
118 | pool: &ThreadPool, | 118 | pool: &ThreadPool, |
119 | sender: &Sender<Task>, | 119 | sender: &Sender<Task>, |
120 | req: RawRequest, | 120 | req: RawRequest, |
@@ -252,7 +252,7 @@ fn on_notification( | |||
252 | fn handle_request_on_threadpool<R: req::ClientRequest>( | 252 | fn handle_request_on_threadpool<R: req::ClientRequest>( |
253 | req: &mut Option<RawRequest>, | 253 | req: &mut Option<RawRequest>, |
254 | pool: &ThreadPool, | 254 | pool: &ThreadPool, |
255 | world: &ServerWorldState, | 255 | world: &mut ServerWorldState, |
256 | sender: &Sender<Task>, | 256 | sender: &Sender<Task>, |
257 | f: fn(ServerWorld, R::Params) -> Result<R::Result>, | 257 | f: fn(ServerWorld, R::Params) -> Result<R::Result>, |
258 | ) -> Result<()> | 258 | ) -> Result<()> |
diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs index c0d2efb86..9850822cd 100644 --- a/crates/server/src/server_world.rs +++ b/crates/server/src/server_world.rs | |||
@@ -86,7 +86,7 @@ impl ServerWorldState { | |||
86 | Ok(()) | 86 | Ok(()) |
87 | } | 87 | } |
88 | 88 | ||
89 | pub fn snapshot(&self) -> ServerWorld { | 89 | pub fn snapshot(&mut self) -> ServerWorld { |
90 | let pm = self.path_map.clone(); | 90 | let pm = self.path_map.clone(); |
91 | ServerWorld { | 91 | ServerWorld { |
92 | analysis: self.analysis.snapshot(move |id, path| { | 92 | analysis: self.analysis.snapshot(move |id, path| { |