diff options
Diffstat (limited to 'crates/libanalysis/src/imp.rs')
-rw-r--r-- | crates/libanalysis/src/imp.rs | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/crates/libanalysis/src/imp.rs b/crates/libanalysis/src/imp.rs new file mode 100644 index 000000000..76f0c0c87 --- /dev/null +++ b/crates/libanalysis/src/imp.rs | |||
@@ -0,0 +1,291 @@ | |||
1 | use std::{ | ||
2 | sync::{ | ||
3 | Arc, | ||
4 | atomic::{AtomicBool, Ordering::SeqCst}, | ||
5 | }, | ||
6 | fmt, | ||
7 | time::Instant, | ||
8 | collections::HashMap, | ||
9 | panic, | ||
10 | }; | ||
11 | |||
12 | use libsyntax2::{ | ||
13 | TextUnit, TextRange, SmolStr, File, AstNode, | ||
14 | SyntaxKind::*, | ||
15 | ast::{self, NameOwner}, | ||
16 | }; | ||
17 | use rayon::prelude::*; | ||
18 | use once_cell::sync::OnceCell; | ||
19 | use libeditor::{self, FileSymbol, LineIndex, find_node_at_offset}; | ||
20 | |||
21 | use { | ||
22 | FileId, FileResolver, Query, Diagnostic, SourceChange, FileSystemEdit, | ||
23 | module_map::Problem, | ||
24 | symbol_index::FileSymbols, | ||
25 | module_map::ModuleMap, | ||
26 | }; | ||
27 | |||
28 | |||
29 | pub(crate) struct AnalysisImpl { | ||
30 | pub(crate) needs_reindex: AtomicBool, | ||
31 | pub(crate) file_resolver: Arc<FileResolver>, | ||
32 | pub(crate) data: Arc<WorldData>, | ||
33 | } | ||
34 | |||
35 | impl fmt::Debug for AnalysisImpl { | ||
36 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
37 | (&*self.data).fmt(f) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | impl Clone for AnalysisImpl { | ||
42 | fn clone(&self) -> AnalysisImpl { | ||
43 | AnalysisImpl { | ||
44 | needs_reindex: AtomicBool::new(self.needs_reindex.load(SeqCst)), | ||
45 | file_resolver: Arc::clone(&self.file_resolver), | ||
46 | data: Arc::clone(&self.data), | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | impl AnalysisImpl { | ||
52 | pub fn file_syntax(&self, file_id: FileId) -> File { | ||
53 | self.file_data(file_id).syntax().clone() | ||
54 | } | ||
55 | |||
56 | pub fn file_line_index(&self, id: FileId) -> LineIndex { | ||
57 | let data = self.file_data(id); | ||
58 | data | ||
59 | .lines | ||
60 | .get_or_init(|| LineIndex::new(&data.text)) | ||
61 | .clone() | ||
62 | } | ||
63 | |||
64 | pub fn world_symbols(&self, mut query: Query) -> Vec<(FileId, FileSymbol)> { | ||
65 | self.reindex(); | ||
66 | self.data.file_map.iter() | ||
67 | .flat_map(move |(id, data)| { | ||
68 | let symbols = data.symbols(); | ||
69 | query.process(symbols).into_iter().map(move |s| (*id, s)) | ||
70 | }) | ||
71 | .collect() | ||
72 | } | ||
73 | |||
74 | pub fn parent_module(&self, id: FileId) -> Vec<(FileId, FileSymbol)> { | ||
75 | let module_map = &self.data.module_map; | ||
76 | let id = module_map.file2module(id); | ||
77 | module_map | ||
78 | .parent_modules( | ||
79 | id, | ||
80 | &*self.file_resolver, | ||
81 | &|file_id| self.file_syntax(file_id), | ||
82 | ) | ||
83 | .into_iter() | ||
84 | .map(|(id, name, node)| { | ||
85 | let id = module_map.module2file(id); | ||
86 | let sym = FileSymbol { | ||
87 | name, | ||
88 | node_range: node.range(), | ||
89 | kind: MODULE, | ||
90 | }; | ||
91 | (id, sym) | ||
92 | }) | ||
93 | .collect() | ||
94 | } | ||
95 | |||
96 | pub fn approximately_resolve_symbol( | ||
97 | &self, | ||
98 | id: FileId, | ||
99 | offset: TextUnit, | ||
100 | ) -> Vec<(FileId, FileSymbol)> { | ||
101 | let file = self.file_syntax(id); | ||
102 | let syntax = file.syntax(); | ||
103 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, offset) { | ||
104 | return self.index_resolve(name_ref); | ||
105 | } | ||
106 | if let Some(name) = find_node_at_offset::<ast::Name>(syntax, offset) { | ||
107 | if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { | ||
108 | if module.has_semi() { | ||
109 | let file_ids = self.resolve_module(id, module); | ||
110 | |||
111 | let res = file_ids.into_iter().map(|id| { | ||
112 | let name = module.name() | ||
113 | .map(|n| n.text()) | ||
114 | .unwrap_or_else(|| SmolStr::new("")); | ||
115 | let symbol = FileSymbol { | ||
116 | name, | ||
117 | node_range: TextRange::offset_len(0.into(), 0.into()), | ||
118 | kind: MODULE, | ||
119 | }; | ||
120 | (id, symbol) | ||
121 | }).collect(); | ||
122 | |||
123 | return res; | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | vec![] | ||
128 | } | ||
129 | |||
130 | pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> { | ||
131 | let syntax = self.file_syntax(file_id); | ||
132 | let mut res = libeditor::diagnostics(&syntax) | ||
133 | .into_iter() | ||
134 | .map(|d| Diagnostic { range: d.range, message: d.msg, fix: None }) | ||
135 | .collect::<Vec<_>>(); | ||
136 | |||
137 | self.data.module_map.problems( | ||
138 | file_id, | ||
139 | &*self.file_resolver, | ||
140 | &|file_id| self.file_syntax(file_id), | ||
141 | |name_node, problem| { | ||
142 | let diag = match problem { | ||
143 | Problem::UnresolvedModule { candidate } => { | ||
144 | let create_file = FileSystemEdit::CreateFile { | ||
145 | anchor: file_id, | ||
146 | path: candidate.clone(), | ||
147 | }; | ||
148 | let fix = SourceChange { | ||
149 | label: "create module".to_string(), | ||
150 | source_file_edits: Vec::new(), | ||
151 | file_system_edits: vec![create_file], | ||
152 | cursor_position: None, | ||
153 | }; | ||
154 | Diagnostic { | ||
155 | range: name_node.syntax().range(), | ||
156 | message: "unresolved module".to_string(), | ||
157 | fix: Some(fix), | ||
158 | } | ||
159 | } | ||
160 | Problem::NotDirOwner { move_to, candidate } => { | ||
161 | let move_file = FileSystemEdit::MoveFile { file: file_id, path: move_to.clone() }; | ||
162 | let create_file = FileSystemEdit::CreateFile { anchor: file_id, path: move_to.join(candidate) }; | ||
163 | let fix = SourceChange { | ||
164 | label: "move file and create module".to_string(), | ||
165 | source_file_edits: Vec::new(), | ||
166 | file_system_edits: vec![move_file, create_file], | ||
167 | cursor_position: None, | ||
168 | }; | ||
169 | Diagnostic { | ||
170 | range: name_node.syntax().range(), | ||
171 | message: "can't declare module at this location".to_string(), | ||
172 | fix: Some(fix), | ||
173 | } | ||
174 | } | ||
175 | }; | ||
176 | res.push(diag) | ||
177 | } | ||
178 | ); | ||
179 | res | ||
180 | } | ||
181 | |||
182 | pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> { | ||
183 | let file = self.file_syntax(file_id); | ||
184 | let actions = vec![ | ||
185 | ("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())), | ||
186 | ("add `#[derive]`", libeditor::add_derive(&file, offset).map(|f| f())), | ||
187 | ("add impl", libeditor::add_impl(&file, offset).map(|f| f())), | ||
188 | ]; | ||
189 | let mut res = Vec::new(); | ||
190 | for (name, local_edit) in actions { | ||
191 | if let Some(local_edit) = local_edit { | ||
192 | res.push(SourceChange::from_local_edit( | ||
193 | file_id, name, local_edit | ||
194 | )) | ||
195 | } | ||
196 | } | ||
197 | res | ||
198 | } | ||
199 | |||
200 | fn index_resolve(&self, name_ref: ast::NameRef) -> Vec<(FileId, FileSymbol)> { | ||
201 | let name = name_ref.text(); | ||
202 | let mut query = Query::new(name.to_string()); | ||
203 | query.exact(); | ||
204 | query.limit(4); | ||
205 | self.world_symbols(query) | ||
206 | } | ||
207 | |||
208 | fn resolve_module(&self, id: FileId, module: ast::Module) -> Vec<FileId> { | ||
209 | let name = match module.name() { | ||
210 | Some(name) => name.text(), | ||
211 | None => return Vec::new(), | ||
212 | }; | ||
213 | let module_map = &self.data.module_map; | ||
214 | let id = module_map.file2module(id); | ||
215 | module_map | ||
216 | .child_module_by_name( | ||
217 | id, name.as_str(), | ||
218 | &*self.file_resolver, | ||
219 | &|file_id| self.file_syntax(file_id), | ||
220 | ) | ||
221 | .into_iter() | ||
222 | .map(|id| module_map.module2file(id)) | ||
223 | .collect() | ||
224 | } | ||
225 | |||
226 | fn reindex(&self) { | ||
227 | if self.needs_reindex.compare_and_swap(false, true, SeqCst) { | ||
228 | let now = Instant::now(); | ||
229 | let data = &*self.data; | ||
230 | data.file_map | ||
231 | .par_iter() | ||
232 | .for_each(|(_, data)| drop(data.symbols())); | ||
233 | info!("parallel indexing took {:?}", now.elapsed()); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | fn file_data(&self, file_id: FileId) -> Arc<FileData> { | ||
238 | match self.data.file_map.get(&file_id) { | ||
239 | Some(data) => data.clone(), | ||
240 | None => panic!("unknown file: {:?}", file_id), | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | |||
245 | #[derive(Default, Debug)] | ||
246 | pub(crate) struct WorldData { | ||
247 | pub(crate) file_map: HashMap<FileId, Arc<FileData>>, | ||
248 | pub(crate) module_map: ModuleMap, | ||
249 | } | ||
250 | |||
251 | #[derive(Debug)] | ||
252 | pub(crate) struct FileData { | ||
253 | pub(crate) text: String, | ||
254 | pub(crate) symbols: OnceCell<FileSymbols>, | ||
255 | pub(crate) syntax: OnceCell<File>, | ||
256 | pub(crate) lines: OnceCell<LineIndex>, | ||
257 | } | ||
258 | |||
259 | impl FileData { | ||
260 | pub(crate) fn new(text: String) -> FileData { | ||
261 | FileData { | ||
262 | text, | ||
263 | symbols: OnceCell::new(), | ||
264 | syntax: OnceCell::new(), | ||
265 | lines: OnceCell::new(), | ||
266 | } | ||
267 | } | ||
268 | |||
269 | fn syntax(&self) -> &File { | ||
270 | let text = &self.text; | ||
271 | let syntax = &self.syntax; | ||
272 | match panic::catch_unwind(panic::AssertUnwindSafe(|| syntax.get_or_init(|| File::parse(text)))) { | ||
273 | Ok(file) => file, | ||
274 | Err(err) => { | ||
275 | error!("Parser paniced on:\n------\n{}\n------\n", &self.text); | ||
276 | panic::resume_unwind(err) | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | fn syntax_transient(&self) -> File { | ||
282 | self.syntax.get().map(|s| s.clone()) | ||
283 | .unwrap_or_else(|| File::parse(&self.text)) | ||
284 | } | ||
285 | |||
286 | fn symbols(&self) -> &FileSymbols { | ||
287 | let syntax = self.syntax_transient(); | ||
288 | self.symbols | ||
289 | .get_or_init(|| FileSymbols::new(&syntax)) | ||
290 | } | ||
291 | } | ||