4 collections
::{HashMap, HashSet}
,
9 cli
::load_cargo
::ProcMacroServerChoice
,
10 line_index
::{LineEndings, LineIndex, PositionEncoding}
,
14 LineCol
, MonikerDescriptorKind
, StaticIndex
, StaticIndexedFile
, TextRange
, TokenId
,
17 use ide_db
::LineIndexDatabase
;
18 use project_model
::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource}
;
19 use scip
::types
as scip_types
;
24 load_cargo
::{load_workspace, LoadCargoConfig}
,
29 pub fn run(self) -> Result
<()> {
30 eprintln
!("Generating SCIP start...");
31 let now
= Instant
::now();
32 let mut cargo_config
= CargoConfig
::default();
33 cargo_config
.sysroot
= Some(RustLibSource
::Discover
);
35 let no_progress
= &|s
| (eprintln
!("rust-analyzer: Loading {s}"));
36 let load_cargo_config
= LoadCargoConfig
{
37 load_out_dirs_from_check
: true,
38 with_proc_macro_server
: ProcMacroServerChoice
::Sysroot
,
41 let path
= vfs
::AbsPathBuf
::assert(env
::current_dir()?
.join(&self.path
));
42 let rootpath
= path
.normalize();
43 let manifest
= ProjectManifest
::discover_single(&path
)?
;
45 let workspace
= ProjectWorkspace
::load(manifest
, &cargo_config
, no_progress
)?
;
48 load_workspace(workspace
, &cargo_config
.extra_env
, &load_cargo_config
)?
;
49 let db
= host
.raw_database();
50 let analysis
= host
.analysis();
52 let si
= StaticIndex
::compute(&analysis
);
54 let metadata
= scip_types
::Metadata
{
55 version
: scip_types
::ProtocolVersion
::UnspecifiedProtocolVersion
.into(),
56 tool_info
: Some(scip_types
::ToolInfo
{
57 name
: "rust-analyzer".to_owned(),
58 version
: "0.1".to_owned(),
60 special_fields
: Default
::default(),
63 project_root
: format
!(
68 .ok_or(anyhow
::anyhow
!("Unable to normalize project_root path"))?
71 text_document_encoding
: scip_types
::TextEncoding
::UTF8
.into(),
72 special_fields
: Default
::default(),
74 let mut documents
= Vec
::new();
76 let mut symbols_emitted
: HashSet
<TokenId
> = HashSet
::default();
77 let mut tokens_to_symbol
: HashMap
<TokenId
, String
> = HashMap
::new();
79 for StaticIndexedFile { file_id, tokens, .. }
in si
.files
{
80 let mut local_count
= 0;
81 let mut new_local_symbol
= || {
82 let new_symbol
= scip
::types
::Symbol
::new_local(local_count
);
88 let relative_path
= match get_relative_filepath(&vfs
, &rootpath
, file_id
) {
89 Some(relative_path
) => relative_path
,
93 let line_index
= LineIndex
{
94 index
: db
.line_index(file_id
),
95 encoding
: PositionEncoding
::Utf8
,
96 endings
: LineEndings
::Unix
,
99 let mut occurrences
= Vec
::new();
100 let mut symbols
= Vec
::new();
102 tokens
.into_iter().for_each(|(text_range
, id
)| {
103 let token
= si
.tokens
.get(id
).unwrap();
105 let range
= text_range_to_scip_range(&line_index
, text_range
);
106 let symbol
= tokens_to_symbol
109 let symbol
= token_to_symbol(token
).unwrap_or_else(&mut new_local_symbol
);
110 scip
::symbol
::format_symbol(symbol
)
114 let mut symbol_roles
= Default
::default();
116 if let Some(def
) = token
.definition
{
117 if def
.range
== text_range
{
118 symbol_roles
|= scip_types
::SymbolRole
::Definition
as i32;
121 if symbols_emitted
.insert(id
) {
122 let documentation
= token
125 .map(|hover
| hover
.markup
.as_str())
126 .filter(|it
| !it
.is_empty())
127 .map(|it
| vec
![it
.to_owned()]);
128 let symbol_info
= scip_types
::SymbolInformation
{
129 symbol
: symbol
.clone(),
130 documentation
: documentation
.unwrap_or_default(),
131 relationships
: Vec
::new(),
132 special_fields
: Default
::default(),
135 symbols
.push(symbol_info
)
139 occurrences
.push(scip_types
::Occurrence
{
143 override_documentation
: Vec
::new(),
144 syntax_kind
: Default
::default(),
145 diagnostics
: Vec
::new(),
146 special_fields
: Default
::default(),
150 if occurrences
.is_empty() {
154 documents
.push(scip_types
::Document
{
156 language
: "rust".to_string(),
159 special_fields
: Default
::default(),
163 let index
= scip_types
::Index
{
164 metadata
: Some(metadata
).into(),
166 external_symbols
: Vec
::new(),
167 special_fields
: Default
::default(),
170 scip
::write_message_to_file("index.scip", index
)
171 .map_err(|err
| anyhow
::anyhow
!("Failed to write scip to file: {}", err
))?
;
173 eprintln
!("Generating SCIP finished {:?}", now
.elapsed());
178 fn get_relative_filepath(
180 rootpath
: &vfs
::AbsPathBuf
,
181 file_id
: ide
::FileId
,
182 ) -> Option
<String
> {
183 Some(vfs
.file_path(file_id
).as_path()?
.strip_prefix(rootpath
)?
.as_ref().to_str()?
.to_string())
186 // SCIP Ranges have a (very large) optimization that ranges if they are on the same line
187 // only encode as a vector of [start_line, start_col, end_col].
189 // This transforms a line index into the optimized SCIP Range.
190 fn text_range_to_scip_range(line_index
: &LineIndex
, range
: TextRange
) -> Vec
<i32> {
191 let LineCol { line: start_line, col: start_col }
= line_index
.index
.line_col(range
.start());
192 let LineCol { line: end_line, col: end_col }
= line_index
.index
.line_col(range
.end());
194 if start_line
== end_line
{
195 vec
![start_line
as i32, start_col
as i32, end_col
as i32]
197 vec
![start_line
as i32, start_col
as i32, end_line
as i32, end_col
as i32]
201 fn new_descriptor_str(
203 suffix
: scip_types
::descriptor
::Suffix
,
204 ) -> scip_types
::Descriptor
{
205 scip_types
::Descriptor
{
206 name
: name
.to_string(),
207 disambiguator
: "".to_string(),
208 suffix
: suffix
.into(),
209 special_fields
: Default
::default(),
213 fn new_descriptor(name
: Name
, suffix
: scip_types
::descriptor
::Suffix
) -> scip_types
::Descriptor
{
214 let mut name
= name
.to_string();
215 if name
.contains("'") {
216 name
= format
!("`{name}`");
219 new_descriptor_str(name
.as_str(), suffix
)
222 /// Loosely based on `def_to_moniker`
224 /// Only returns a Symbol when it's a non-local symbol.
225 /// So if the visibility isn't outside of a document, then it will return None
226 fn token_to_symbol(token
: &TokenStaticData
) -> Option
<scip_types
::Symbol
> {
227 use scip_types
::descriptor
::Suffix
::*;
229 let moniker
= token
.moniker
.as_ref()?
;
231 let package_name
= moniker
.package_information
.name
.clone();
232 let version
= moniker
.package_information
.version
.clone();
233 let descriptors
= moniker
241 MonikerDescriptorKind
::Namespace
=> Namespace
,
242 MonikerDescriptorKind
::Type
=> Type
,
243 MonikerDescriptorKind
::Term
=> Term
,
244 MonikerDescriptorKind
::Method
=> Method
,
245 MonikerDescriptorKind
::TypeParameter
=> TypeParameter
,
246 MonikerDescriptorKind
::Parameter
=> Parameter
,
247 MonikerDescriptorKind
::Macro
=> Macro
,
248 MonikerDescriptorKind
::Meta
=> Meta
,
254 Some(scip_types
::Symbol
{
255 scheme
: "rust-analyzer".into(),
256 package
: Some(scip_types
::Package
{
257 manager
: "cargo".to_string(),
259 version
: version
.unwrap_or_else(|| ".".to_string()),
260 special_fields
: Default
::default(),
264 special_fields
: Default
::default(),
271 use ide
::{AnalysisHost, FilePosition, StaticIndex, TextSize}
;
272 use ide_db
::base_db
::fixture
::ChangeFixture
;
273 use scip
::symbol
::format_symbol
;
275 fn position(ra_fixture
: &str) -> (AnalysisHost
, FilePosition
) {
276 let mut host
= AnalysisHost
::default();
277 let change_fixture
= ChangeFixture
::parse(ra_fixture
);
278 host
.raw_database_mut().apply_change(change_fixture
.change
);
279 let (file_id
, range_or_offset
) =
280 change_fixture
.file_position
.expect("expected a marker ($0)");
281 let offset
= range_or_offset
.expect_offset();
282 (host
, FilePosition { file_id, offset }
)
285 /// If expected == "", then assert that there are no symbols (this is basically local symbol)
287 fn check_symbol(ra_fixture
: &str, expected
: &str) {
288 let (host
, position
) = position(ra_fixture
);
290 let analysis
= host
.analysis();
291 let si
= StaticIndex
::compute(&analysis
);
293 let FilePosition { file_id, offset }
= position
;
295 let mut found_symbol
= None
;
296 for file
in &si
.files
{
297 if file
.file_id
!= file_id
{
300 for &(range
, id
) in &file
.tokens
{
301 if range
.contains(offset
- TextSize
::from(1)) {
302 let token
= si
.tokens
.get(id
).unwrap();
303 found_symbol
= token_to_symbol(token
);
310 assert
!(found_symbol
.is_none(), "must have no symbols {found_symbol:?}");
314 assert
!(found_symbol
.is_some(), "must have one symbol {found_symbol:?}");
315 let res
= found_symbol
.unwrap();
316 let formatted
= format_symbol(res
);
317 assert_eq
!(formatted
, expected
);
324 //- /lib.rs crate:main deps:foo
325 use foo::example_mod::func;
329 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
330 pub mod example_mod {
334 "rust-analyzer cargo foo 0.1.0 example_mod/func().",
339 fn symbol_for_trait() {
342 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
349 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
354 fn symbol_for_trait_constant() {
357 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
360 const MY_CONST$0: u8;
364 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
369 fn symbol_for_trait_type() {
372 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
379 // "foo::module::MyTrait::MyType",
380 "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]",
385 fn symbol_for_trait_impl_function() {
388 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
396 impl MyTrait for MyStruct {
401 // "foo::module::MyStruct::MyTrait::func",
402 "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().",
407 fn symbol_for_field() {
410 //- /lib.rs crate:main deps:foo
413 let x = St { a$0: 2 };
415 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
420 "rust-analyzer cargo foo 0.1.0 St#a.",
425 fn local_symbol_for_local() {
428 //- /lib.rs crate:main deps:foo
429 use foo::module::func;
433 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
445 fn global_symbol_for_pub_struct() {
448 //- /lib.rs crate:main
452 let _bar = foo::Bar { i: 0 };
459 "rust-analyzer cargo main . foo/Bar#",
464 fn global_symbol_for_pub_struct_reference() {
467 //- /lib.rs crate:main
471 let _bar = foo::Bar$0 { i: 0 };
478 "rust-analyzer cargo main . foo/Bar#",