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