aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/lib.rs
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/ide/src/lib.rs
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/ide/src/lib.rs')
-rw-r--r--crates/ide/src/lib.rs516
1 files changed, 516 insertions, 0 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
new file mode 100644
index 000000000..570790384
--- /dev/null
+++ b/crates/ide/src/lib.rs
@@ -0,0 +1,516 @@
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)]
14macro_rules! eprintln {
15 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
16}
17
18pub mod mock_analysis;
19
20mod markup;
21mod prime_caches;
22mod display;
23
24mod call_hierarchy;
25mod call_info;
26mod completion;
27mod diagnostics;
28mod expand_macro;
29mod extend_selection;
30mod file_structure;
31mod folding_ranges;
32mod goto_definition;
33mod goto_implementation;
34mod goto_type_definition;
35mod hover;
36mod inlay_hints;
37mod join_lines;
38mod matching_brace;
39mod parent_module;
40mod references;
41mod runnables;
42mod status;
43mod syntax_highlighting;
44mod syntax_tree;
45mod typing;
46mod link_rewrite;
47
48use std::sync::Arc;
49
50use base_db::{
51 salsa::{self, ParallelDatabase},
52 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
53};
54use cfg::CfgOptions;
55use ide_db::{
56 symbol_index::{self, FileSymbol},
57 LineIndexDatabase,
58};
59use syntax::{SourceFile, TextRange, TextSize};
60
61use crate::display::ToNav;
62
63pub use crate::{
64 call_hierarchy::CallItem,
65 call_info::CallInfo,
66 completion::{
67 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
68 },
69 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
70 display::NavigationTarget,
71 expand_macro::ExpandedMacro,
72 file_structure::StructureNode,
73 folding_ranges::{Fold, FoldKind},
74 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
75 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
76 markup::Markup,
77 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
78 runnables::{Runnable, RunnableKind, TestId},
79 syntax_highlighting::{
80 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
81 },
82};
83
84pub use assists::{Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist};
85pub use base_db::{
86 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
87 SourceRootId,
88};
89pub use hir::{Documentation, Semantics};
90pub use ide_db::{
91 change::AnalysisChange,
92 label::Label,
93 line_index::{LineCol, LineIndex},
94 search::SearchScope,
95 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
96 symbol_index::Query,
97 RootDatabase,
98};
99pub use ssr::SsrError;
100pub use text_edit::{Indel, TextEdit};
101
102pub type Cancelable<T> = Result<T, Canceled>;
103
104/// Info associated with a text range.
105#[derive(Debug)]
106pub struct RangeInfo<T> {
107 pub range: TextRange,
108 pub info: T,
109}
110
111impl<T> RangeInfo<T> {
112 pub fn new(range: TextRange, info: T) -> RangeInfo<T> {
113 RangeInfo { range, info }
114 }
115}
116
117/// `AnalysisHost` stores the current state of the world.
118#[derive(Debug)]
119pub struct AnalysisHost {
120 db: RootDatabase,
121}
122
123impl AnalysisHost {
124 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
125 AnalysisHost { db: RootDatabase::new(lru_capacity) }
126 }
127
128 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
129 self.db.update_lru_capacity(lru_capacity);
130 }
131
132 /// Returns a snapshot of the current state, which you can query for
133 /// semantic information.
134 pub fn analysis(&self) -> Analysis {
135 Analysis { db: self.db.snapshot() }
136 }
137
138 /// Applies changes to the current state of the world. If there are
139 /// outstanding snapshots, they will be canceled.
140 pub fn apply_change(&mut self, change: AnalysisChange) {
141 self.db.apply_change(change)
142 }
143
144 pub fn maybe_collect_garbage(&mut self) {
145 self.db.maybe_collect_garbage();
146 }
147
148 pub fn collect_garbage(&mut self) {
149 self.db.collect_garbage();
150 }
151 /// NB: this clears the database
152 pub fn per_query_memory_usage(&mut self) -> Vec<(String, profile::Bytes)> {
153 self.db.per_query_memory_usage()
154 }
155 pub fn request_cancellation(&mut self) {
156 self.db.request_cancellation();
157 }
158 pub fn raw_database(&self) -> &RootDatabase {
159 &self.db
160 }
161 pub fn raw_database_mut(&mut self) -> &mut RootDatabase {
162 &mut self.db
163 }
164}
165
166impl Default for AnalysisHost {
167 fn default() -> AnalysisHost {
168 AnalysisHost::new(None)
169 }
170}
171
172/// Analysis is a snapshot of a world state at a moment in time. It is the main
173/// entry point for asking semantic information about the world. When the world
174/// state is advanced using `AnalysisHost::apply_change` method, all existing
175/// `Analysis` are canceled (most method return `Err(Canceled)`).
176#[derive(Debug)]
177pub struct Analysis {
178 db: salsa::Snapshot<RootDatabase>,
179}
180
181// As a general design guideline, `Analysis` API are intended to be independent
182// from the language server protocol. That is, when exposing some functionality
183// we should think in terms of "what API makes most sense" and not in terms of
184// "what types LSP uses". Although currently LSP is the only consumer of the
185// API, the API should in theory be usable as a library, or via a different
186// protocol.
187impl Analysis {
188 // Creates an analysis instance for a single file, without any extenal
189 // dependencies, stdlib support or ability to apply changes. See
190 // `AnalysisHost` for creating a fully-featured analysis.
191 pub fn from_single_file(text: String) -> (Analysis, FileId) {
192 let mut host = AnalysisHost::default();
193 let file_id = FileId(0);
194 let mut file_set = FileSet::default();
195 file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
196 let source_root = SourceRoot::new_local(file_set);
197
198 let mut change = AnalysisChange::new();
199 change.set_roots(vec![source_root]);
200 let mut crate_graph = CrateGraph::default();
201 // FIXME: cfg options
202 // Default to enable test for single file.
203 let mut cfg_options = CfgOptions::default();
204 cfg_options.insert_atom("test".into());
205 crate_graph.add_crate_root(
206 file_id,
207 Edition::Edition2018,
208 None,
209 cfg_options,
210 Env::default(),
211 Default::default(),
212 );
213 change.change_file(file_id, Some(Arc::new(text)));
214 change.set_crate_graph(crate_graph);
215 host.apply_change(change);
216 (host.analysis(), file_id)
217 }
218
219 /// Debug info about the current state of the analysis.
220 pub fn status(&self) -> Cancelable<String> {
221 self.with_db(|db| status::status(&*db))
222 }
223
224 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> {
225 self.with_db(|db| prime_caches::prime_caches(db, files))
226 }
227
228 /// Gets the text of the source file.
229 pub fn file_text(&self, file_id: FileId) -> Cancelable<Arc<String>> {
230 self.with_db(|db| db.file_text(file_id))
231 }
232
233 /// Gets the syntax tree of the file.
234 pub fn parse(&self, file_id: FileId) -> Cancelable<SourceFile> {
235 self.with_db(|db| db.parse(file_id).tree())
236 }
237
238 /// Gets the file's `LineIndex`: data structure to convert between absolute
239 /// offsets and line/column representation.
240 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> {
241 self.with_db(|db| db.line_index(file_id))
242 }
243
244 /// Selects the next syntactic nodes encompassing the range.
245 pub fn extend_selection(&self, frange: FileRange) -> Cancelable<TextRange> {
246 self.with_db(|db| extend_selection::extend_selection(db, frange))
247 }
248
249 /// Returns position of the matching brace (all types of braces are
250 /// supported).
251 pub fn matching_brace(&self, position: FilePosition) -> Cancelable<Option<TextSize>> {
252 self.with_db(|db| {
253 let parse = db.parse(position.file_id);
254 let file = parse.tree();
255 matching_brace::matching_brace(&file, position.offset)
256 })
257 }
258
259 /// Returns a syntax tree represented as `String`, for debug purposes.
260 // FIXME: use a better name here.
261 pub fn syntax_tree(
262 &self,
263 file_id: FileId,
264 text_range: Option<TextRange>,
265 ) -> Cancelable<String> {
266 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
267 }
268
269 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
270 self.with_db(|db| expand_macro::expand_macro(db, position))
271 }
272
273 /// Returns an edit to remove all newlines in the range, cleaning up minor
274 /// stuff like trailing commas.
275 pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> {
276 self.with_db(|db| {
277 let parse = db.parse(frange.file_id);
278 join_lines::join_lines(&parse.tree(), frange.range)
279 })
280 }
281
282 /// Returns an edit which should be applied when opening a new line, fixing
283 /// up minor stuff like continuing the comment.
284 /// The edit will be a snippet (with `$0`).
285 pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> {
286 self.with_db(|db| typing::on_enter(&db, position))
287 }
288
289 /// Returns an edit which should be applied after a character was typed.
290 ///
291 /// This is useful for some on-the-fly fixups, like adding `;` to `let =`
292 /// automatically.
293 pub fn on_char_typed(
294 &self,
295 position: FilePosition,
296 char_typed: char,
297 ) -> Cancelable<Option<SourceChange>> {
298 // Fast path to not even parse the file.
299 if !typing::TRIGGER_CHARS.contains(char_typed) {
300 return Ok(None);
301 }
302 self.with_db(|db| typing::on_char_typed(&db, position, char_typed))
303 }
304
305 /// Returns a tree representation of symbols in the file. Useful to draw a
306 /// file outline.
307 pub fn file_structure(&self, file_id: FileId) -> Cancelable<Vec<StructureNode>> {
308 self.with_db(|db| file_structure::file_structure(&db.parse(file_id).tree()))
309 }
310
311 /// Returns a list of the places in the file where type hints can be displayed.
312 pub fn inlay_hints(
313 &self,
314 file_id: FileId,
315 config: &InlayHintsConfig,
316 ) -> Cancelable<Vec<InlayHint>> {
317 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config))
318 }
319
320 /// Returns the set of folding ranges.
321 pub fn folding_ranges(&self, file_id: FileId) -> Cancelable<Vec<Fold>> {
322 self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree()))
323 }
324
325 /// Fuzzy searches for a symbol.
326 pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> {
327 self.with_db(|db| {
328 symbol_index::world_symbols(db, query)
329 .into_iter()
330 .map(|s| s.to_nav(db))
331 .collect::<Vec<_>>()
332 })
333 }
334
335 /// Returns the definitions from the symbol at `position`.
336 pub fn goto_definition(
337 &self,
338 position: FilePosition,
339 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
340 self.with_db(|db| goto_definition::goto_definition(db, position))
341 }
342
343 /// Returns the impls from the symbol at `position`.
344 pub fn goto_implementation(
345 &self,
346 position: FilePosition,
347 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
348 self.with_db(|db| goto_implementation::goto_implementation(db, position))
349 }
350
351 /// Returns the type definitions for the symbol at `position`.
352 pub fn goto_type_definition(
353 &self,
354 position: FilePosition,
355 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
356 self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
357 }
358
359 /// Finds all usages of the reference at point.
360 pub fn find_all_refs(
361 &self,
362 position: FilePosition,
363 search_scope: Option<SearchScope>,
364 ) -> Cancelable<Option<ReferenceSearchResult>> {
365 self.with_db(|db| {
366 references::find_all_refs(&Semantics::new(db), position, search_scope).map(|it| it.info)
367 })
368 }
369
370 /// Returns a short text describing element at position.
371 pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> {
372 self.with_db(|db| hover::hover(db, position))
373 }
374
375 /// Computes parameter information for the given call expression.
376 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
377 self.with_db(|db| call_info::call_info(db, position))
378 }
379
380 /// Computes call hierarchy candidates for the given file position.
381 pub fn call_hierarchy(
382 &self,
383 position: FilePosition,
384 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
385 self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
386 }
387
388 /// Computes incoming calls for the given file position.
389 pub fn incoming_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
390 self.with_db(|db| call_hierarchy::incoming_calls(db, position))
391 }
392
393 /// Computes incoming calls for the given file position.
394 pub fn outgoing_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
395 self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
396 }
397
398 /// Returns a `mod name;` declaration which created the current module.
399 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
400 self.with_db(|db| parent_module::parent_module(db, position))
401 }
402
403 /// Returns crates this file belongs too.
404 pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
405 self.with_db(|db| parent_module::crate_for(db, file_id))
406 }
407
408 /// Returns the edition of the given crate.
409 pub fn crate_edition(&self, crate_id: CrateId) -> Cancelable<Edition> {
410 self.with_db(|db| db.crate_graph()[crate_id].edition)
411 }
412
413 /// Returns the root file of the given crate.
414 pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> {
415 self.with_db(|db| db.crate_graph()[crate_id].root_file_id)
416 }
417
418 /// Returns the set of possible targets to run for the current file.
419 pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
420 self.with_db(|db| runnables::runnables(db, file_id))
421 }
422
423 /// Computes syntax highlighting for the given file
424 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
425 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
426 }
427
428 /// Computes syntax highlighting for the given file range.
429 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> {
430 self.with_db(|db| {
431 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
432 })
433 }
434
435 /// Computes syntax highlighting for the given file.
436 pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable<String> {
437 self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
438 }
439
440 /// Computes completions at the given position.
441 pub fn completions(
442 &self,
443 config: &CompletionConfig,
444 position: FilePosition,
445 ) -> Cancelable<Option<Vec<CompletionItem>>> {
446 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
447 }
448
449 /// Computes resolved assists with source changes for the given position.
450 pub fn resolved_assists(
451 &self,
452 config: &AssistConfig,
453 frange: FileRange,
454 ) -> Cancelable<Vec<ResolvedAssist>> {
455 self.with_db(|db| assists::Assist::resolved(db, config, frange))
456 }
457
458 /// Computes unresolved assists (aka code actions aka intentions) for the given
459 /// position.
460 pub fn unresolved_assists(
461 &self,
462 config: &AssistConfig,
463 frange: FileRange,
464 ) -> Cancelable<Vec<Assist>> {
465 self.with_db(|db| Assist::unresolved(db, config, frange))
466 }
467
468 /// Computes the set of diagnostics for the given file.
469 pub fn diagnostics(
470 &self,
471 config: &DiagnosticsConfig,
472 file_id: FileId,
473 ) -> Cancelable<Vec<Diagnostic>> {
474 self.with_db(|db| diagnostics::diagnostics(db, config, file_id))
475 }
476
477 /// Returns the edit required to rename reference at the position to the new
478 /// name.
479 pub fn rename(
480 &self,
481 position: FilePosition,
482 new_name: &str,
483 ) -> Cancelable<Option<RangeInfo<SourceChange>>> {
484 self.with_db(|db| references::rename(db, position, new_name))
485 }
486
487 pub fn structural_search_replace(
488 &self,
489 query: &str,
490 parse_only: bool,
491 resolve_context: FilePosition,
492 selections: Vec<FileRange>,
493 ) -> Cancelable<Result<SourceChange, SsrError>> {
494 self.with_db(|db| {
495 let rule: ssr::SsrRule = query.parse()?;
496 let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections);
497 match_finder.add_rule(rule)?;
498 let edits = if parse_only { Vec::new() } else { match_finder.edits() };
499 Ok(SourceChange::from(edits))
500 })
501 }
502
503 /// Performs an operation on that may be Canceled.
504 fn with_db<F, T>(&self, f: F) -> Cancelable<T>
505 where
506 F: FnOnce(&RootDatabase) -> T + std::panic::UnwindSafe,
507 {
508 self.db.catch_canceled(f)
509 }
510}
511
512#[test]
513fn analysis_is_send() {
514 fn is_send<T: Send>() {}
515 is_send::<Analysis>();
516}