]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! ide crate provides "ide-centric" APIs for the rust-analyzer. That is, |
2 | //! it generally operates with files and text ranges, and returns results as | |
3 | //! Strings, suitable for displaying to the human. | |
4 | //! | |
5 | //! What powers this API are the `RootDatabase` struct, which defines a `salsa` | |
6 | //! database, and the `hir` crate, where majority of the analysis happens. | |
7 | //! However, IDE specific bits of the analysis (most notably completion) happen | |
8 | //! in this crate. | |
9 | ||
10 | // For proving that RootDatabase is RefUnwindSafe. | |
11 | #![recursion_limit = "128"] | |
12 | #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] | |
13 | ||
14 | #[allow(unused)] | |
15 | macro_rules! eprintln { | |
16 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; | |
17 | } | |
18 | ||
19 | #[cfg(test)] | |
20 | mod fixture; | |
21 | ||
22 | mod markup; | |
23 | mod prime_caches; | |
24 | mod navigation_target; | |
25 | ||
26 | mod annotations; | |
27 | mod call_hierarchy; | |
28 | mod signature_help; | |
29 | mod doc_links; | |
30 | mod highlight_related; | |
31 | mod expand_macro; | |
32 | mod extend_selection; | |
33 | mod file_structure; | |
064997fb FG |
34 | mod folding_ranges; |
35 | mod goto_declaration; | |
36 | mod goto_definition; | |
37 | mod goto_implementation; | |
38 | mod goto_type_definition; | |
39 | mod hover; | |
40 | mod inlay_hints; | |
41 | mod join_lines; | |
42 | mod markdown_remove; | |
43 | mod matching_brace; | |
44 | mod moniker; | |
45 | mod move_item; | |
46 | mod parent_module; | |
47 | mod references; | |
48 | mod rename; | |
49 | mod runnables; | |
50 | mod ssr; | |
51 | mod static_index; | |
52 | mod status; | |
53 | mod syntax_highlighting; | |
54 | mod syntax_tree; | |
55 | mod typing; | |
56 | mod view_crate_graph; | |
57 | mod view_hir; | |
58 | mod view_item_tree; | |
59 | mod shuffle_crate_graph; | |
60 | ||
61 | use std::sync::Arc; | |
62 | ||
63 | use cfg::CfgOptions; | |
64 | use ide_db::{ | |
65 | base_db::{ | |
66 | salsa::{self, ParallelDatabase}, | |
67 | CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath, | |
68 | }, | |
69 | symbol_index, LineIndexDatabase, | |
70 | }; | |
71 | use syntax::SourceFile; | |
72 | ||
73 | use crate::navigation_target::{ToNav, TryToNav}; | |
74 | ||
75 | pub use crate::{ | |
2b03887a | 76 | annotations::{Annotation, AnnotationConfig, AnnotationKind, AnnotationLocation}, |
064997fb FG |
77 | call_hierarchy::CallItem, |
78 | expand_macro::ExpandedMacro, | |
79 | file_structure::{StructureNode, StructureNodeKind}, | |
80 | folding_ranges::{Fold, FoldKind}, | |
81 | highlight_related::{HighlightRelatedConfig, HighlightedRange}, | |
82 | hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, | |
83 | inlay_hints::{ | |
9c376795 | 84 | AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint, |
9ffffee4 FG |
85 | InlayHintLabel, InlayHintLabelPart, InlayHintsConfig, InlayKind, InlayTooltip, |
86 | LifetimeElisionHints, | |
064997fb FG |
87 | }, |
88 | join_lines::JoinLinesConfig, | |
89 | markup::Markup, | |
f2b60f7d | 90 | moniker::{MonikerDescriptorKind, MonikerKind, MonikerResult, PackageInformation}, |
064997fb FG |
91 | move_item::Direction, |
92 | navigation_target::NavigationTarget, | |
93 | prime_caches::ParallelPrimeCachesProgress, | |
94 | references::ReferenceSearchResult, | |
95 | rename::RenameError, | |
96 | runnables::{Runnable, RunnableKind, TestId}, | |
97 | signature_help::SignatureHelp, | |
98 | static_index::{StaticIndex, StaticIndexedFile, TokenId, TokenStaticData}, | |
99 | syntax_highlighting::{ | |
100 | tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, | |
f2b60f7d | 101 | HighlightConfig, HlRange, |
064997fb FG |
102 | }, |
103 | }; | |
104 | pub use hir::{Documentation, Semantics}; | |
105 | pub use ide_assists::{ | |
106 | Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, | |
107 | }; | |
108 | pub use ide_completion::{ | |
109 | CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, | |
110 | Snippet, SnippetScope, | |
111 | }; | |
112 | pub use ide_db::{ | |
113 | base_db::{ | |
114 | Cancelled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, | |
115 | SourceRoot, SourceRootId, | |
116 | }, | |
117 | label::Label, | |
9ffffee4 | 118 | line_index::{LineCol, LineIndex}, |
064997fb FG |
119 | search::{ReferenceCategory, SearchScope}, |
120 | source_change::{FileSystemEdit, SourceChange}, | |
121 | symbol_index::Query, | |
122 | RootDatabase, SymbolKind, | |
123 | }; | |
124 | pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, ExprFillDefaultMode, Severity}; | |
125 | pub use ide_ssr::SsrError; | |
126 | pub use syntax::{TextRange, TextSize}; | |
127 | pub use text_edit::{Indel, TextEdit}; | |
128 | ||
129 | pub type Cancellable<T> = Result<T, Cancelled>; | |
130 | ||
131 | /// Info associated with a text range. | |
132 | #[derive(Debug)] | |
133 | pub struct RangeInfo<T> { | |
134 | pub range: TextRange, | |
135 | pub info: T, | |
136 | } | |
137 | ||
138 | impl<T> RangeInfo<T> { | |
139 | pub fn new(range: TextRange, info: T) -> RangeInfo<T> { | |
140 | RangeInfo { range, info } | |
141 | } | |
142 | } | |
143 | ||
144 | /// `AnalysisHost` stores the current state of the world. | |
145 | #[derive(Debug)] | |
146 | pub struct AnalysisHost { | |
147 | db: RootDatabase, | |
148 | } | |
149 | ||
150 | impl AnalysisHost { | |
151 | pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { | |
152 | AnalysisHost { db: RootDatabase::new(lru_capacity) } | |
153 | } | |
154 | ||
155 | pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) { | |
156 | self.db.update_lru_capacity(lru_capacity); | |
157 | } | |
158 | ||
159 | /// Returns a snapshot of the current state, which you can query for | |
160 | /// semantic information. | |
161 | pub fn analysis(&self) -> Analysis { | |
162 | Analysis { db: self.db.snapshot() } | |
163 | } | |
164 | ||
165 | /// Applies changes to the current state of the world. If there are | |
166 | /// outstanding snapshots, they will be canceled. | |
167 | pub fn apply_change(&mut self, change: Change) { | |
168 | self.db.apply_change(change) | |
169 | } | |
170 | ||
171 | /// NB: this clears the database | |
172 | pub fn per_query_memory_usage(&mut self) -> Vec<(String, profile::Bytes)> { | |
173 | self.db.per_query_memory_usage() | |
174 | } | |
175 | pub fn request_cancellation(&mut self) { | |
176 | self.db.request_cancellation(); | |
177 | } | |
178 | pub fn raw_database(&self) -> &RootDatabase { | |
179 | &self.db | |
180 | } | |
181 | pub fn raw_database_mut(&mut self) -> &mut RootDatabase { | |
182 | &mut self.db | |
183 | } | |
184 | ||
185 | pub fn shuffle_crate_graph(&mut self) { | |
186 | shuffle_crate_graph::shuffle_crate_graph(&mut self.db); | |
187 | } | |
188 | } | |
189 | ||
190 | impl Default for AnalysisHost { | |
191 | fn default() -> AnalysisHost { | |
192 | AnalysisHost::new(None) | |
193 | } | |
194 | } | |
195 | ||
196 | /// Analysis is a snapshot of a world state at a moment in time. It is the main | |
197 | /// entry point for asking semantic information about the world. When the world | |
198 | /// state is advanced using `AnalysisHost::apply_change` method, all existing | |
199 | /// `Analysis` are canceled (most method return `Err(Canceled)`). | |
200 | #[derive(Debug)] | |
201 | pub struct Analysis { | |
202 | db: salsa::Snapshot<RootDatabase>, | |
203 | } | |
204 | ||
205 | // As a general design guideline, `Analysis` API are intended to be independent | |
206 | // from the language server protocol. That is, when exposing some functionality | |
207 | // we should think in terms of "what API makes most sense" and not in terms of | |
208 | // "what types LSP uses". Although currently LSP is the only consumer of the | |
209 | // API, the API should in theory be usable as a library, or via a different | |
210 | // protocol. | |
211 | impl Analysis { | |
212 | // Creates an analysis instance for a single file, without any external | |
213 | // dependencies, stdlib support or ability to apply changes. See | |
214 | // `AnalysisHost` for creating a fully-featured analysis. | |
215 | pub fn from_single_file(text: String) -> (Analysis, FileId) { | |
216 | let mut host = AnalysisHost::default(); | |
217 | let file_id = FileId(0); | |
218 | let mut file_set = FileSet::default(); | |
219 | file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string())); | |
220 | let source_root = SourceRoot::new_local(file_set); | |
221 | ||
222 | let mut change = Change::new(); | |
223 | change.set_roots(vec![source_root]); | |
224 | let mut crate_graph = CrateGraph::default(); | |
225 | // FIXME: cfg options | |
226 | // Default to enable test for single file. | |
227 | let mut cfg_options = CfgOptions::default(); | |
228 | cfg_options.insert_atom("test".into()); | |
229 | crate_graph.add_crate_root( | |
230 | file_id, | |
231 | Edition::CURRENT, | |
232 | None, | |
233 | None, | |
234 | cfg_options.clone(), | |
235 | cfg_options, | |
236 | Env::default(), | |
237 | Ok(Vec::new()), | |
238 | false, | |
2b03887a | 239 | CrateOrigin::CratesIo { repo: None, name: None }, |
9ffffee4 | 240 | Err("Analysis::from_single_file has no target layout".into()), |
064997fb FG |
241 | ); |
242 | change.change_file(file_id, Some(Arc::new(text))); | |
243 | change.set_crate_graph(crate_graph); | |
244 | host.apply_change(change); | |
245 | (host.analysis(), file_id) | |
246 | } | |
247 | ||
248 | /// Debug info about the current state of the analysis. | |
249 | pub fn status(&self, file_id: Option<FileId>) -> Cancellable<String> { | |
250 | self.with_db(|db| status::status(&*db, file_id)) | |
251 | } | |
252 | ||
253 | pub fn parallel_prime_caches<F>(&self, num_worker_threads: u8, cb: F) -> Cancellable<()> | |
254 | where | |
255 | F: Fn(ParallelPrimeCachesProgress) + Sync + std::panic::UnwindSafe, | |
256 | { | |
257 | self.with_db(move |db| prime_caches::parallel_prime_caches(db, num_worker_threads, &cb)) | |
258 | } | |
259 | ||
260 | /// Gets the text of the source file. | |
261 | pub fn file_text(&self, file_id: FileId) -> Cancellable<Arc<String>> { | |
262 | self.with_db(|db| db.file_text(file_id)) | |
263 | } | |
264 | ||
265 | /// Gets the syntax tree of the file. | |
266 | pub fn parse(&self, file_id: FileId) -> Cancellable<SourceFile> { | |
267 | self.with_db(|db| db.parse(file_id).tree()) | |
268 | } | |
269 | ||
270 | /// Returns true if this file belongs to an immutable library. | |
271 | pub fn is_library_file(&self, file_id: FileId) -> Cancellable<bool> { | |
272 | use ide_db::base_db::SourceDatabaseExt; | |
273 | self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library) | |
274 | } | |
275 | ||
276 | /// Gets the file's `LineIndex`: data structure to convert between absolute | |
277 | /// offsets and line/column representation. | |
278 | pub fn file_line_index(&self, file_id: FileId) -> Cancellable<Arc<LineIndex>> { | |
279 | self.with_db(|db| db.line_index(file_id)) | |
280 | } | |
281 | ||
282 | /// Selects the next syntactic nodes encompassing the range. | |
283 | pub fn extend_selection(&self, frange: FileRange) -> Cancellable<TextRange> { | |
284 | self.with_db(|db| extend_selection::extend_selection(db, frange)) | |
285 | } | |
286 | ||
287 | /// Returns position of the matching brace (all types of braces are | |
288 | /// supported). | |
289 | pub fn matching_brace(&self, position: FilePosition) -> Cancellable<Option<TextSize>> { | |
290 | self.with_db(|db| { | |
291 | let parse = db.parse(position.file_id); | |
292 | let file = parse.tree(); | |
293 | matching_brace::matching_brace(&file, position.offset) | |
294 | }) | |
295 | } | |
296 | ||
297 | /// Returns a syntax tree represented as `String`, for debug purposes. | |
298 | // FIXME: use a better name here. | |
299 | pub fn syntax_tree( | |
300 | &self, | |
301 | file_id: FileId, | |
302 | text_range: Option<TextRange>, | |
303 | ) -> Cancellable<String> { | |
304 | self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range)) | |
305 | } | |
306 | ||
307 | pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> { | |
308 | self.with_db(|db| view_hir::view_hir(db, position)) | |
309 | } | |
310 | ||
311 | pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> { | |
312 | self.with_db(|db| view_item_tree::view_item_tree(db, file_id)) | |
313 | } | |
314 | ||
315 | /// Renders the crate graph to GraphViz "dot" syntax. | |
316 | pub fn view_crate_graph(&self, full: bool) -> Cancellable<Result<String, String>> { | |
317 | self.with_db(|db| view_crate_graph::view_crate_graph(db, full)) | |
318 | } | |
319 | ||
320 | pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> { | |
321 | self.with_db(|db| expand_macro::expand_macro(db, position)) | |
322 | } | |
323 | ||
324 | /// Returns an edit to remove all newlines in the range, cleaning up minor | |
325 | /// stuff like trailing commas. | |
326 | pub fn join_lines(&self, config: &JoinLinesConfig, frange: FileRange) -> Cancellable<TextEdit> { | |
327 | self.with_db(|db| { | |
328 | let parse = db.parse(frange.file_id); | |
329 | join_lines::join_lines(config, &parse.tree(), frange.range) | |
330 | }) | |
331 | } | |
332 | ||
333 | /// Returns an edit which should be applied when opening a new line, fixing | |
334 | /// up minor stuff like continuing the comment. | |
335 | /// The edit will be a snippet (with `$0`). | |
336 | pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> { | |
337 | self.with_db(|db| typing::on_enter(db, position)) | |
338 | } | |
339 | ||
340 | /// Returns an edit which should be applied after a character was typed. | |
341 | /// | |
342 | /// This is useful for some on-the-fly fixups, like adding `;` to `let =` | |
343 | /// automatically. | |
344 | pub fn on_char_typed( | |
345 | &self, | |
346 | position: FilePosition, | |
347 | char_typed: char, | |
348 | autoclose: bool, | |
349 | ) -> Cancellable<Option<SourceChange>> { | |
350 | // Fast path to not even parse the file. | |
351 | if !typing::TRIGGER_CHARS.contains(char_typed) { | |
352 | return Ok(None); | |
353 | } | |
354 | if char_typed == '<' && !autoclose { | |
355 | return Ok(None); | |
356 | } | |
357 | ||
358 | self.with_db(|db| typing::on_char_typed(db, position, char_typed)) | |
359 | } | |
360 | ||
361 | /// Returns a tree representation of symbols in the file. Useful to draw a | |
362 | /// file outline. | |
363 | pub fn file_structure(&self, file_id: FileId) -> Cancellable<Vec<StructureNode>> { | |
364 | self.with_db(|db| file_structure::file_structure(&db.parse(file_id).tree())) | |
365 | } | |
366 | ||
367 | /// Returns a list of the places in the file where type hints can be displayed. | |
368 | pub fn inlay_hints( | |
369 | &self, | |
370 | config: &InlayHintsConfig, | |
371 | file_id: FileId, | |
487cf647 | 372 | range: Option<TextRange>, |
064997fb FG |
373 | ) -> Cancellable<Vec<InlayHint>> { |
374 | self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config)) | |
375 | } | |
376 | ||
377 | /// Returns the set of folding ranges. | |
378 | pub fn folding_ranges(&self, file_id: FileId) -> Cancellable<Vec<Fold>> { | |
379 | self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree())) | |
380 | } | |
381 | ||
382 | /// Fuzzy searches for a symbol. | |
383 | pub fn symbol_search(&self, query: Query) -> Cancellable<Vec<NavigationTarget>> { | |
384 | self.with_db(|db| { | |
385 | symbol_index::world_symbols(db, query) | |
386 | .into_iter() // xx: should we make this a par iter? | |
387 | .filter_map(|s| s.try_to_nav(db)) | |
388 | .collect::<Vec<_>>() | |
389 | }) | |
390 | } | |
391 | ||
392 | /// Returns the definitions from the symbol at `position`. | |
393 | pub fn goto_definition( | |
394 | &self, | |
395 | position: FilePosition, | |
396 | ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> { | |
397 | self.with_db(|db| goto_definition::goto_definition(db, position)) | |
398 | } | |
399 | ||
400 | /// Returns the declaration from the symbol at `position`. | |
401 | pub fn goto_declaration( | |
402 | &self, | |
403 | position: FilePosition, | |
404 | ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> { | |
405 | self.with_db(|db| goto_declaration::goto_declaration(db, position)) | |
406 | } | |
407 | ||
408 | /// Returns the impls from the symbol at `position`. | |
409 | pub fn goto_implementation( | |
410 | &self, | |
411 | position: FilePosition, | |
412 | ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> { | |
413 | self.with_db(|db| goto_implementation::goto_implementation(db, position)) | |
414 | } | |
415 | ||
416 | /// Returns the type definitions for the symbol at `position`. | |
417 | pub fn goto_type_definition( | |
418 | &self, | |
419 | position: FilePosition, | |
420 | ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> { | |
421 | self.with_db(|db| goto_type_definition::goto_type_definition(db, position)) | |
422 | } | |
423 | ||
424 | /// Finds all usages of the reference at point. | |
425 | pub fn find_all_refs( | |
426 | &self, | |
427 | position: FilePosition, | |
428 | search_scope: Option<SearchScope>, | |
429 | ) -> Cancellable<Option<Vec<ReferenceSearchResult>>> { | |
430 | self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope)) | |
431 | } | |
432 | ||
064997fb FG |
433 | /// Returns a short text describing element at position. |
434 | pub fn hover( | |
435 | &self, | |
436 | config: &HoverConfig, | |
437 | range: FileRange, | |
438 | ) -> Cancellable<Option<RangeInfo<HoverResult>>> { | |
439 | self.with_db(|db| hover::hover(db, range, config)) | |
440 | } | |
441 | ||
442 | /// Returns moniker of symbol at position. | |
443 | pub fn moniker( | |
444 | &self, | |
445 | position: FilePosition, | |
446 | ) -> Cancellable<Option<RangeInfo<Vec<moniker::MonikerResult>>>> { | |
447 | self.with_db(|db| moniker::moniker(db, position)) | |
448 | } | |
449 | ||
450 | /// Return URL(s) for the documentation of the symbol under the cursor. | |
451 | pub fn external_docs( | |
452 | &self, | |
453 | position: FilePosition, | |
454 | ) -> Cancellable<Option<doc_links::DocumentationLink>> { | |
455 | self.with_db(|db| doc_links::external_docs(db, &position)) | |
456 | } | |
457 | ||
458 | /// Computes parameter information at the given position. | |
459 | pub fn signature_help(&self, position: FilePosition) -> Cancellable<Option<SignatureHelp>> { | |
460 | self.with_db(|db| signature_help::signature_help(db, position)) | |
461 | } | |
462 | ||
463 | /// Computes call hierarchy candidates for the given file position. | |
464 | pub fn call_hierarchy( | |
465 | &self, | |
466 | position: FilePosition, | |
467 | ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> { | |
468 | self.with_db(|db| call_hierarchy::call_hierarchy(db, position)) | |
469 | } | |
470 | ||
471 | /// Computes incoming calls for the given file position. | |
472 | pub fn incoming_calls(&self, position: FilePosition) -> Cancellable<Option<Vec<CallItem>>> { | |
473 | self.with_db(|db| call_hierarchy::incoming_calls(db, position)) | |
474 | } | |
475 | ||
476 | /// Computes outgoing calls for the given file position. | |
477 | pub fn outgoing_calls(&self, position: FilePosition) -> Cancellable<Option<Vec<CallItem>>> { | |
478 | self.with_db(|db| call_hierarchy::outgoing_calls(db, position)) | |
479 | } | |
480 | ||
481 | /// Returns a `mod name;` declaration which created the current module. | |
482 | pub fn parent_module(&self, position: FilePosition) -> Cancellable<Vec<NavigationTarget>> { | |
483 | self.with_db(|db| parent_module::parent_module(db, position)) | |
484 | } | |
485 | ||
486 | /// Returns crates this file belongs too. | |
2b03887a FG |
487 | pub fn crates_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> { |
488 | self.with_db(|db| parent_module::crates_for(db, file_id)) | |
489 | } | |
490 | ||
491 | /// Returns crates this file belongs too. | |
492 | pub fn transitive_rev_deps(&self, crate_id: CrateId) -> Cancellable<Vec<CrateId>> { | |
493 | self.with_db(|db| db.crate_graph().transitive_rev_deps(crate_id).collect()) | |
494 | } | |
495 | ||
496 | /// Returns crates this file *might* belong too. | |
497 | pub fn relevant_crates_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> { | |
498 | self.with_db(|db| db.relevant_crates(file_id).iter().copied().collect()) | |
064997fb FG |
499 | } |
500 | ||
501 | /// Returns the edition of the given crate. | |
502 | pub fn crate_edition(&self, crate_id: CrateId) -> Cancellable<Edition> { | |
503 | self.with_db(|db| db.crate_graph()[crate_id].edition) | |
504 | } | |
505 | ||
506 | /// Returns the root file of the given crate. | |
507 | pub fn crate_root(&self, crate_id: CrateId) -> Cancellable<FileId> { | |
508 | self.with_db(|db| db.crate_graph()[crate_id].root_file_id) | |
509 | } | |
510 | ||
511 | /// Returns the set of possible targets to run for the current file. | |
512 | pub fn runnables(&self, file_id: FileId) -> Cancellable<Vec<Runnable>> { | |
513 | self.with_db(|db| runnables::runnables(db, file_id)) | |
514 | } | |
515 | ||
516 | /// Returns the set of tests for the given file position. | |
517 | pub fn related_tests( | |
518 | &self, | |
519 | position: FilePosition, | |
520 | search_scope: Option<SearchScope>, | |
521 | ) -> Cancellable<Vec<Runnable>> { | |
522 | self.with_db(|db| runnables::related_tests(db, position, search_scope)) | |
523 | } | |
524 | ||
525 | /// Computes syntax highlighting for the given file | |
f2b60f7d FG |
526 | pub fn highlight( |
527 | &self, | |
528 | highlight_config: HighlightConfig, | |
529 | file_id: FileId, | |
530 | ) -> Cancellable<Vec<HlRange>> { | |
531 | self.with_db(|db| syntax_highlighting::highlight(db, highlight_config, file_id, None)) | |
064997fb FG |
532 | } |
533 | ||
534 | /// Computes all ranges to highlight for a given item in a file. | |
535 | pub fn highlight_related( | |
536 | &self, | |
537 | config: HighlightRelatedConfig, | |
538 | position: FilePosition, | |
539 | ) -> Cancellable<Option<Vec<HighlightedRange>>> { | |
540 | self.with_db(|db| { | |
541 | highlight_related::highlight_related(&Semantics::new(db), config, position) | |
542 | }) | |
543 | } | |
544 | ||
545 | /// Computes syntax highlighting for the given file range. | |
f2b60f7d FG |
546 | pub fn highlight_range( |
547 | &self, | |
548 | highlight_config: HighlightConfig, | |
549 | frange: FileRange, | |
550 | ) -> Cancellable<Vec<HlRange>> { | |
064997fb | 551 | self.with_db(|db| { |
f2b60f7d | 552 | syntax_highlighting::highlight(db, highlight_config, frange.file_id, Some(frange.range)) |
064997fb FG |
553 | }) |
554 | } | |
555 | ||
556 | /// Computes syntax highlighting for the given file. | |
557 | pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancellable<String> { | |
558 | self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow)) | |
559 | } | |
560 | ||
561 | /// Computes completions at the given position. | |
562 | pub fn completions( | |
563 | &self, | |
564 | config: &CompletionConfig, | |
565 | position: FilePosition, | |
566 | trigger_character: Option<char>, | |
567 | ) -> Cancellable<Option<Vec<CompletionItem>>> { | |
568 | self.with_db(|db| { | |
569 | ide_completion::completions(db, config, position, trigger_character).map(Into::into) | |
570 | }) | |
571 | } | |
572 | ||
573 | /// Resolves additional completion data at the position given. | |
574 | pub fn resolve_completion_edits( | |
575 | &self, | |
576 | config: &CompletionConfig, | |
577 | position: FilePosition, | |
578 | imports: impl IntoIterator<Item = (String, String)> + std::panic::UnwindSafe, | |
579 | ) -> Cancellable<Vec<TextEdit>> { | |
580 | Ok(self | |
581 | .with_db(|db| ide_completion::resolve_completion_edits(db, config, position, imports))? | |
582 | .unwrap_or_default()) | |
583 | } | |
584 | ||
585 | /// Computes the set of diagnostics for the given file. | |
586 | pub fn diagnostics( | |
587 | &self, | |
588 | config: &DiagnosticsConfig, | |
589 | resolve: AssistResolveStrategy, | |
590 | file_id: FileId, | |
591 | ) -> Cancellable<Vec<Diagnostic>> { | |
592 | self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id)) | |
593 | } | |
594 | ||
595 | /// Convenience function to return assists + quick fixes for diagnostics | |
596 | pub fn assists_with_fixes( | |
597 | &self, | |
598 | assist_config: &AssistConfig, | |
599 | diagnostics_config: &DiagnosticsConfig, | |
600 | resolve: AssistResolveStrategy, | |
601 | frange: FileRange, | |
602 | ) -> Cancellable<Vec<Assist>> { | |
603 | let include_fixes = match &assist_config.allowed { | |
604 | Some(it) => it.iter().any(|&it| it == AssistKind::None || it == AssistKind::QuickFix), | |
605 | None => true, | |
606 | }; | |
607 | ||
608 | self.with_db(|db| { | |
609 | let diagnostic_assists = if include_fixes { | |
610 | ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) | |
611 | .into_iter() | |
612 | .flat_map(|it| it.fixes.unwrap_or_default()) | |
613 | .filter(|it| it.target.intersect(frange.range).is_some()) | |
614 | .collect() | |
615 | } else { | |
616 | Vec::new() | |
617 | }; | |
618 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | |
619 | let assists = ide_assists::assists(db, assist_config, resolve, frange); | |
620 | ||
621 | let mut res = diagnostic_assists; | |
622 | res.extend(ssr_assists.into_iter()); | |
623 | res.extend(assists.into_iter()); | |
624 | ||
625 | res | |
626 | }) | |
627 | } | |
628 | ||
629 | /// Returns the edit required to rename reference at the position to the new | |
630 | /// name. | |
631 | pub fn rename( | |
632 | &self, | |
633 | position: FilePosition, | |
634 | new_name: &str, | |
635 | ) -> Cancellable<Result<SourceChange, RenameError>> { | |
636 | self.with_db(|db| rename::rename(db, position, new_name)) | |
637 | } | |
638 | ||
639 | pub fn prepare_rename( | |
640 | &self, | |
641 | position: FilePosition, | |
642 | ) -> Cancellable<Result<RangeInfo<()>, RenameError>> { | |
643 | self.with_db(|db| rename::prepare_rename(db, position)) | |
644 | } | |
645 | ||
646 | pub fn will_rename_file( | |
647 | &self, | |
648 | file_id: FileId, | |
649 | new_name_stem: &str, | |
650 | ) -> Cancellable<Option<SourceChange>> { | |
651 | self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem)) | |
652 | } | |
653 | ||
654 | pub fn structural_search_replace( | |
655 | &self, | |
656 | query: &str, | |
657 | parse_only: bool, | |
658 | resolve_context: FilePosition, | |
659 | selections: Vec<FileRange>, | |
660 | ) -> Cancellable<Result<SourceChange, SsrError>> { | |
661 | self.with_db(|db| { | |
662 | let rule: ide_ssr::SsrRule = query.parse()?; | |
663 | let mut match_finder = | |
664 | ide_ssr::MatchFinder::in_context(db, resolve_context, selections)?; | |
665 | match_finder.add_rule(rule)?; | |
666 | let edits = if parse_only { Default::default() } else { match_finder.edits() }; | |
667 | Ok(SourceChange::from(edits)) | |
668 | }) | |
669 | } | |
670 | ||
671 | pub fn annotations( | |
672 | &self, | |
673 | config: &AnnotationConfig, | |
674 | file_id: FileId, | |
675 | ) -> Cancellable<Vec<Annotation>> { | |
676 | self.with_db(|db| annotations::annotations(db, config, file_id)) | |
677 | } | |
678 | ||
679 | pub fn resolve_annotation(&self, annotation: Annotation) -> Cancellable<Annotation> { | |
680 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) | |
681 | } | |
682 | ||
683 | pub fn move_item( | |
684 | &self, | |
685 | range: FileRange, | |
686 | direction: Direction, | |
687 | ) -> Cancellable<Option<TextEdit>> { | |
688 | self.with_db(|db| move_item::move_item(db, range, direction)) | |
689 | } | |
690 | ||
691 | /// Performs an operation on the database that may be canceled. | |
692 | /// | |
693 | /// rust-analyzer needs to be able to answer semantic questions about the | |
694 | /// code while the code is being modified. A common problem is that a | |
695 | /// long-running query is being calculated when a new change arrives. | |
696 | /// | |
697 | /// We can't just apply the change immediately: this will cause the pending | |
698 | /// query to see inconsistent state (it will observe an absence of | |
699 | /// repeatable read). So what we do is we **cancel** all pending queries | |
700 | /// before applying the change. | |
701 | /// | |
702 | /// Salsa implements cancellation by unwinding with a special value and | |
703 | /// catching it on the API boundary. | |
704 | fn with_db<F, T>(&self, f: F) -> Cancellable<T> | |
705 | where | |
706 | F: FnOnce(&RootDatabase) -> T + std::panic::UnwindSafe, | |
707 | { | |
708 | Cancelled::catch(|| f(&self.db)) | |
709 | } | |
710 | } | |
711 | ||
712 | #[test] | |
713 | fn analysis_is_send() { | |
714 | fn is_send<T: Send>() {} | |
715 | is_send::<Analysis>(); | |
716 | } |