4 collections::{HashMap, HashSet},
8 use crate::line_index::{LineEndings, LineIndex, OffsetEncoding};
11 LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange,
14 use ide_db::LineIndexDatabase;
15 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
16 use scip::types as scip_types;
21 load_cargo::{load_workspace, LoadCargoConfig},
26 pub fn run(self) -> Result<()> {
27 eprintln!("Generating SCIP start...");
28 let now = Instant::now();
29 let cargo_config = CargoConfig::default();
31 let no_progress = &|s| (eprintln!("rust-analyzer: Loading {}", s));
32 let load_cargo_config = LoadCargoConfig {
33 load_out_dirs_from_check: true,
34 with_proc_macro: true,
37 let path = vfs::AbsPathBuf::assert(env::current_dir()?.join(&self.path));
38 let rootpath = path.normalize();
39 let manifest = ProjectManifest::discover_single(&path)?;
41 let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
43 let (host, vfs, _) = load_workspace(workspace, &load_cargo_config)?;
44 let db = host.raw_database();
45 let analysis = host.analysis();
47 let si = StaticIndex::compute(&analysis);
49 let mut index = scip_types::Index {
50 metadata: Some(scip_types::Metadata {
51 version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
52 tool_info: Some(scip_types::ToolInfo {
53 name: "rust-analyzer".to_owned(),
54 version: "0.1".to_owned(),
59 project_root: format!(
64 .ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
67 text_document_encoding: scip_types::TextEncoding::UTF8.into(),
74 let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
75 let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
77 for file in si.files {
78 let mut local_count = 0;
79 let mut new_local_symbol = || {
80 let new_symbol = scip::types::Symbol::new_local(local_count);
86 let StaticIndexedFile { file_id, tokens, .. } = file;
87 let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) {
88 Some(relative_path) => relative_path,
92 let line_index = LineIndex {
93 index: db.line_index(file_id),
94 encoding: OffsetEncoding::Utf8,
95 endings: LineEndings::Unix,
98 let mut doc = scip_types::Document {
100 language: "rust".to_string(),
104 tokens.into_iter().for_each(|(range, id)| {
105 let token = si.tokens.get(id).unwrap();
107 let mut occurrence = scip_types::Occurrence::default();
108 occurrence.range = text_range_to_scip_range(&line_index, range);
109 occurrence.symbol = match tokens_to_symbol.get(&id) {
110 Some(symbol) => symbol.clone(),
112 let symbol = match &token.moniker {
113 Some(moniker) => moniker_to_symbol(&moniker),
114 None => new_local_symbol(),
117 let symbol = scip::symbol::format_symbol(symbol);
118 tokens_to_symbol.insert(id, symbol.clone());
123 if let Some(def) = token.definition {
124 if def.range == range {
125 occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32;
128 if !symbols_emitted.contains(&id) {
129 symbols_emitted.insert(id);
131 let mut symbol_info = scip_types::SymbolInformation::default();
132 symbol_info.symbol = occurrence.symbol.clone();
133 if let Some(hover) = &token.hover {
134 if !hover.markup.as_str().is_empty() {
135 symbol_info.documentation = vec![hover.markup.as_str().to_string()];
139 doc.symbols.push(symbol_info)
143 doc.occurrences.push(occurrence);
146 if doc.occurrences.is_empty() {
150 index.documents.push(doc);
153 scip::write_message_to_file("index.scip", index)
154 .map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?;
156 eprintln!("Generating SCIP finished {:?}", now.elapsed());
161 fn get_relative_filepath(
163 rootpath: &vfs::AbsPathBuf,
164 file_id: ide::FileId,
165 ) -> Option<String> {
166 Some(vfs.file_path(file_id).as_path()?.strip_prefix(&rootpath)?.as_ref().to_str()?.to_string())
169 // SCIP Ranges have a (very large) optimization that ranges if they are on the same line
170 // only encode as a vector of [start_line, start_col, end_col].
172 // This transforms a line index into the optimized SCIP Range.
173 fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
174 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
175 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
177 if start_line == end_line {
178 vec![start_line as i32, start_col as i32, end_col as i32]
180 vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
184 fn new_descriptor_str(
186 suffix: scip_types::descriptor::Suffix,
187 ) -> scip_types::Descriptor {
188 scip_types::Descriptor {
189 name: name.to_string(),
190 disambiguator: "".to_string(),
191 suffix: suffix.into(),
196 fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_types::Descriptor {
197 let mut name = name.to_string();
198 if name.contains("'") {
199 name = format!("`{}`", name);
202 new_descriptor_str(name.as_str(), suffix)
205 /// Loosely based on `def_to_moniker`
207 /// Only returns a Symbol when it's a non-local symbol.
208 /// So if the visibility isn't outside of a document, then it will return None
209 fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
210 use scip_types::descriptor::Suffix::*;
212 let package_name = moniker.package_information.name.clone();
213 let version = moniker.package_information.version.clone();
214 let descriptors = moniker
222 MonikerDescriptorKind::Namespace => Namespace,
223 MonikerDescriptorKind::Type => Type,
224 MonikerDescriptorKind::Term => Term,
225 MonikerDescriptorKind::Method => Method,
226 MonikerDescriptorKind::TypeParameter => TypeParameter,
227 MonikerDescriptorKind::Parameter => Parameter,
228 MonikerDescriptorKind::Macro => Macro,
229 MonikerDescriptorKind::Meta => Meta,
236 scheme: "rust-analyzer".into(),
237 package: Some(scip_types::Package {
238 manager: "cargo".to_string(),
253 use ide::{AnalysisHost, FilePosition};
254 use ide_db::defs::IdentClass;
255 use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token};
256 use scip::symbol::format_symbol;
257 use syntax::SyntaxKind::*;
258 use syntax::{AstNode, T};
260 fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) {
261 let mut host = AnalysisHost::default();
262 let change_fixture = ChangeFixture::parse(ra_fixture);
263 host.raw_database_mut().apply_change(change_fixture.change);
264 let (file_id, range_or_offset) =
265 change_fixture.file_position.expect("expected a marker ($0)");
266 let offset = range_or_offset.expect_offset();
267 (host, FilePosition { file_id, offset })
270 /// If expected == "", then assert that there are no symbols (this is basically local symbol)
272 fn check_symbol(ra_fixture: &str, expected: &str) {
273 let (host, position) = position(ra_fixture);
275 let FilePosition { file_id, offset } = position;
277 let db = host.raw_database();
278 let sema = &Semantics::new(db);
279 let file = sema.parse(file_id).syntax().clone();
280 let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
289 kind if kind.is_trivia() => 0,
295 .descend_into_macros(original_token.clone())
297 .filter_map(|token| {
298 IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
299 it.into_iter().flat_map(|def| {
300 let module = def.module(db).unwrap();
301 let current_crate = module.krate();
303 match MonikerResult::from_def(sema.db, def, current_crate) {
304 Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)),
311 .collect::<Vec<_>>();
314 assert_eq!(0, navs.len(), "must have no symbols {:?}", navs);
318 assert_eq!(1, navs.len(), "must have one symbol {:?}", navs);
320 let res = navs.get(0).unwrap();
321 let formatted = format_symbol(res.clone());
322 assert_eq!(formatted, expected);
329 //- /lib.rs crate:main deps:foo
330 use foo::example_mod::func;
334 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
335 pub mod example_mod {
339 "rust-analyzer cargo foo 0.1.0 example_mod/func().",
344 fn symbol_for_trait() {
347 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
354 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
359 fn symbol_for_trait_constant() {
362 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
365 const MY_CONST$0: u8;
369 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
374 fn symbol_for_trait_type() {
377 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
384 // "foo::module::MyTrait::MyType",
385 "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]",
390 fn symbol_for_trait_impl_function() {
393 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
401 impl MyTrait for MyStruct {
406 // "foo::module::MyStruct::MyTrait::func",
407 "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().",
412 fn symbol_for_field() {
415 //- /lib.rs crate:main deps:foo
418 let x = St { a$0: 2 };
420 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
425 "rust-analyzer cargo foo 0.1.0 St#a.",
430 fn local_symbol_for_local() {
433 //- /lib.rs crate:main deps:foo
434 use foo::module::func;
438 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git